1use super::{PyDict, PyDictRef, PyList, PyStr, PyStrRef, PyType, PyTypeRef, PyUtf8StrRef};
2use crate::common::hash::PyHash;
3use crate::types::PyTypeFlags;
4use crate::{
5 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
6 class::PyClassImpl,
7 convert::ToPyResult,
8 function::{
9 ArgumentError, Either, FromArgs, FuncArgs, Param, PyArithmeticValue, PyComparisonValue,
10 PySetterValue,
11 },
12 types::{Constructor, Initializer, PyComparisonOp},
13};
14use itertools::Itertools;
15
16#[pyclass(module = false, name = "object")]
17#[derive(Debug)]
18pub struct PyBaseObject;
19
20impl PyPayload for PyBaseObject {
21 #[inline]
22 fn class(ctx: &Context) -> &'static Py<PyType> {
23 ctx.types.object_type
24 }
25}
26
27pub struct ObjectArgs;
28
29impl FromArgs for ObjectArgs {
30 const PARAMS: Option<&'static [Param]> = Some(&[]);
31
32 fn from_args(_vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
33 core::mem::take(args);
34 Ok(Self)
35 }
36}
37
38impl Constructor for PyBaseObject {
39 type Args = ObjectArgs;
40
41 fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
43 if !args.args.is_empty() || !args.kwargs.is_empty() {
44 let tp_new = cls.get_attr(identifier!(vm, __new__));
46 let object_new = vm.ctx.types.object_type.get_attr(identifier!(vm, __new__));
47
48 if let (Some(tp_new), Some(object_new)) = (tp_new, object_new) {
49 if !tp_new.is(&object_new) {
50 return Err(vm.new_type_error(
53 "object.__new__() takes exactly one argument (the type to instantiate)",
54 ));
55 }
56
57 let tp_init = cls.get_attr(identifier!(vm, __init__));
60 let object_init = vm.ctx.types.object_type.get_attr(identifier!(vm, __init__));
61
62 if let (Some(tp_init), Some(object_init)) = (tp_init, object_init)
63 && tp_init.is(&object_init)
64 {
65 return Err(vm.new_type_error(format!("{}() takes no arguments", cls.name())));
68 }
69 }
72 }
73
74 if let Some(abs_methods) = cls.get_attr(identifier!(vm, __abstractmethods__)) {
76 let methods: Vec<PyUtf8StrRef> = abs_methods.try_to_value(vm)?;
77 let unimplemented_abstract_method_count = methods.len();
78 if unimplemented_abstract_method_count > 0 {
79 let methods: String = Itertools::intersperse(
80 methods.iter().map(|name| name.as_str().to_owned()),
81 "', '".to_owned(),
82 )
83 .collect();
84 let name = cls.name().to_string();
85 let noun = if unimplemented_abstract_method_count == 1 {
86 "method"
87 } else {
88 "methods"
89 };
90 return Err(vm.new_type_error(format!(
91 "class {name} without an implementation for abstract {noun} '{methods}'"
92 )));
93 }
94 }
95
96 generic_alloc(cls, 0, vm)
97 }
98
99 fn py_new(_cls: &Py<PyType>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
100 unimplemented!("use slot_new")
101 }
102}
103
104#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
105pub(crate) fn generic_alloc(cls: PyTypeRef, _nitems: usize, vm: &VirtualMachine) -> PyResult {
106 let dict = if cls
109 .slots
110 .flags
111 .has_feature(crate::types::PyTypeFlags::HAS_DICT)
112 {
113 Some(vm.ctx.new_dict())
114 } else {
115 None
116 };
117 Ok(crate::PyRef::new_ref(PyBaseObject, cls, dict).into())
118}
119
120impl Initializer for PyBaseObject {
121 type Args = ObjectArgs;
122
123 fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
125 if args.is_empty() {
126 return Ok(());
127 }
128
129 let typ = zelf.class();
130 let object_type = &vm.ctx.types.object_type;
131
132 let typ_init = typ.slots.init.load().map(|f| crate::types::fn_addr(f));
133 let object_init = object_type
134 .slots
135 .init
136 .load()
137 .map(|f| crate::types::fn_addr(f));
138
139 if typ_init != object_init {
141 return Err(vm.new_type_error(
142 "object.__init__() takes exactly one argument (the instance to initialize)",
143 ));
144 }
145
146 if let (Some(typ_new), Some(object_new)) = (
148 typ.get_attr(identifier!(vm, __new__)),
149 object_type.get_attr(identifier!(vm, __new__)),
150 ) && typ_new.is(&object_new)
151 {
152 return Err(vm.new_type_error(format!(
153 "{}.__init__() takes exactly one argument (the instance to initialize)",
154 typ.name()
155 )));
156 }
157
158 Ok(())
160 }
161
162 fn init(_zelf: &Py<Self>, _args: Self::Args, _vm: &VirtualMachine) -> PyResult<()> {
163 unreachable!("slot_init is defined")
164 }
165}
166
167fn type_slot_names(typ: &Py<PyType>, vm: &VirtualMachine) -> PyResult<Option<super::PyListRef>> {
169 if let Some(slot_names) = typ.get_direct_attr(identifier!(vm.ctx, __slotnames__)) {
171 return match_class!(match slot_names {
172 l @ super::PyList => Ok(Some(l)),
173 _n @ super::PyNone => Ok(None),
174 other => Err(vm.new_type_error(format!(
175 "{:.200}.__slotnames__ should be a list or None, not {:.200}",
176 typ.name(),
177 other.class().name()
178 ))),
179 });
180 }
181
182 let copyreg = vm.import("copyreg", 0)?;
185 let copyreg_slotnames = copyreg.get_attr("_slotnames", vm)?;
186 let slot_names = copyreg_slotnames.call((typ.to_owned(),), vm)?;
187 let result = match_class!(match slot_names {
188 l @ super::PyList => Some(l),
189 _n @ super::PyNone => None,
190 _ => return Err(vm.new_type_error("copyreg._slotnames didn't return a list or None")),
191 });
192 Ok(result)
193}
194
195fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) -> PyResult {
197 if required && obj.class().slots().itemsize > 0 {
199 return Err(vm.new_type_error(format!("cannot pickle {:.200} objects", obj.class().name())));
200 }
201
202 let state = if obj.dict().is_none_or(|d| d.is_empty()) {
203 vm.ctx.none()
204 } else {
205 let Some(state) = obj.dict() else {
207 return Ok(vm.ctx.none());
208 };
209 state.into()
210 };
211
212 let slot_names = type_slot_names(obj.class(), vm)?;
213
214 if required {
215 let basicsize = vm.ctx.types.object_type.slots().basicsize;
219 if obj.class().slots().basicsize > basicsize {
220 return Err(vm.new_type_error(format!("cannot pickle '{}' object", obj.class().name())));
221 }
222 }
223
224 if let Some(slot_names) = slot_names {
225 let slot_names_len = slot_names.__len__();
226 if slot_names_len > 0 {
227 let slots = vm.ctx.new_dict();
228 for i in 0..slot_names_len {
229 let borrowed_names = slot_names.borrow_vec();
230 if borrowed_names.len() != slot_names_len {
232 return Err(vm.new_runtime_error("__slotnames__ changed size during iteration"));
233 }
234 let name = borrowed_names[i].downcast_ref::<PyStr>().unwrap();
235 let Ok(value) = obj.get_attr(name, vm) else {
236 continue;
237 };
238 slots.set_item(name.as_wtf8(), value, vm).unwrap();
239 }
240
241 if !slots.is_empty() {
242 return (state, slots).to_pyresult(vm);
243 }
244 }
245 }
246
247 Ok(state)
248}
249
250#[pyclass(with(Constructor, Initializer), flags(BASETYPE))]
280impl PyBaseObject {
281 #[pymethod]
282 fn __getstate__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
283 object_getstate_default(&zelf, false, vm)
284 }
285
286 #[pyslot]
287 fn slot_richcompare(
288 zelf: &PyObject,
289 other: &PyObject,
290 op: PyComparisonOp,
291 vm: &VirtualMachine,
292 ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
293 Self::cmp(zelf, other, op, vm).map(Either::B)
294 }
295
296 #[inline(always)]
297 fn cmp(
298 zelf: &PyObject,
299 other: &PyObject,
300 op: PyComparisonOp,
301 vm: &VirtualMachine,
302 ) -> PyResult<PyComparisonValue> {
303 let res = match op {
304 PyComparisonOp::Eq => {
305 if zelf.is(other) {
306 PyComparisonValue::Implemented(true)
307 } else {
308 PyComparisonValue::NotImplemented
309 }
310 }
311 PyComparisonOp::Ne => {
312 let cmp = zelf.class().slots().richcompare.load().unwrap();
313 let value = match cmp(zelf, other, PyComparisonOp::Eq, vm)? {
314 Either::A(obj) => PyArithmeticValue::from_object(vm, obj)
315 .map(|obj| obj.try_to_bool(vm))
316 .transpose()?,
317 Either::B(value) => value,
318 };
319 value.map(|v| !v)
320 }
321 _ => PyComparisonValue::NotImplemented,
322 };
323 Ok(res)
324 }
325
326 #[pyslot]
328 pub(crate) fn slot_setattro(
329 obj: &PyObject,
330 attr_name: &Py<PyStr>,
331 value: PySetterValue,
332 vm: &VirtualMachine,
333 ) -> PyResult<()> {
334 obj.generic_setattr(attr_name, value, vm)
335 }
336
337 #[pyslot]
339 fn slot_str(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
340 zelf.repr(vm)
342 }
343
344 #[pyslot]
345 fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyStrRef> {
346 let class = zelf.class();
347 let module = class.__module__(vm).ok();
349 match (
350 class
351 .__qualname__(vm)
352 .downcast_ref::<PyStr>()
353 .map(|n| n.as_wtf8()),
354 module
355 .as_ref()
356 .and_then(|m| m.downcast_ref::<PyStr>())
357 .map(|m| m.as_wtf8()),
358 ) {
359 (None, _) => Err(vm.new_type_error("Unknown qualified name")),
360 (Some(qualname), Some(module)) if module != "builtins" => Ok(PyStr::from(format!(
361 "<{}.{} object at {:#x}>",
362 module,
363 qualname,
364 zelf.get_id()
365 ))
366 .into_ref(&vm.ctx)),
367 _ => Ok(PyStr::from(format!(
368 "<{} object at {:#x}>",
369 class.slot_name(),
370 zelf.get_id()
371 ))
372 .into_ref(&vm.ctx)),
373 }
374 }
375
376 #[pyclassmethod]
377 fn __subclasshook__(_cls: PyTypeRef, _object: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
378 vm.ctx.not_implemented()
379 }
380
381 #[pyclassmethod]
382 fn __init_subclass__(_cls: PyTypeRef) {}
383
384 #[pymethod]
385 pub fn __dir__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyList> {
386 let mut names: Vec<PyObjectRef> = Vec::new();
387
388 match zelf.get_attr(identifier!(vm, __dict__), vm) {
389 Ok(obj) => {
390 if let Ok(dict) = obj.downcast::<PyDict>() {
391 names.extend(
392 dict.into_iter()
393 .filter_map(|(k, _)| k.downcast_ref::<PyStr>().is_some().then_some(k)),
394 );
395 }
396 }
397 Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => {}
398 Err(e) => return Err(e),
399 }
400
401 match zelf.get_attr(identifier!(vm, __class__), vm) {
402 Ok(cls_obj) => {
403 if let Some(cls) = cls_obj.downcast_ref::<PyType>() {
404 for (name, _) in cls.get_attributes(&vm.ctx) {
405 names.push(name.to_object());
406 }
407 }
408 }
409 Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => {}
410 Err(e) => return Err(e),
411 }
412
413 let lst = PyList::from(names);
414 lst.sort(Default::default(), vm)?;
415 Ok(lst)
416 }
417
418 #[pymethod]
419 fn __format__(
420 obj: PyObjectRef,
421 format_spec: PyStrRef,
422 vm: &VirtualMachine,
423 ) -> PyResult<PyStrRef> {
424 if !format_spec.is_empty() {
425 return Err(vm.new_type_error(format!(
426 "unsupported format string passed to {}.__format__",
427 obj.class().slot_name()
428 )));
429 }
430 obj.str(vm)
431 }
432
433 #[pygetset]
434 fn __class__(obj: PyObjectRef) -> PyTypeRef {
435 obj.class().to_owned()
436 }
437
438 #[pygetset(setter)]
439 fn set___class__(
440 instance: PyObjectRef,
441 value: PyObjectRef,
442 vm: &VirtualMachine,
443 ) -> PyResult<()> {
444 match value.downcast::<PyType>() {
445 Ok(cls) => {
446 let current_cls = instance.class();
447 let both_module = current_cls.fast_issubclass(vm.ctx.types.module_type)
448 && cls.fast_issubclass(vm.ctx.types.module_type);
449 let both_mutable = !current_cls
450 .slots
451 .flags
452 .has_feature(PyTypeFlags::IMMUTABLETYPE)
453 && !cls.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE);
454 if both_mutable || both_module {
456 super::type_::compatible_for_assignment(current_cls, &cls, "__class__", vm)?;
457 instance.set_class(cls, vm);
458 crate::stdlib::_testinternalcapi::note_set_class();
459 Ok(())
460 } else {
461 Err(vm.new_type_error(
462 "__class__ assignment only supported for mutable types or ModuleType subclasses",
463 ))
464 }
465 }
466 Err(value) => {
467 let value_class = value.class();
468 let type_repr = &value_class.name();
469 Err(vm.new_type_error(format!(
470 "__class__ must be set to a class, not '{type_repr}' object"
471 )))
472 }
473 }
474 }
475
476 #[pyslot]
480 pub(crate) fn getattro(obj: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
481 vm_trace!("object.__getattribute__({:?}, {:?})", obj, name);
482 obj.as_object().generic_getattr(name, vm)
483 }
484
485 #[pymethod]
486 fn __reduce__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
487 common_reduce(zelf, 0, vm)
488 }
489
490 #[pymethod]
491 fn __reduce_ex__(zelf: PyObjectRef, protocol: usize, vm: &VirtualMachine) -> PyResult {
492 let __reduce__ = identifier!(vm, __reduce__);
493 if let Some(reduce) = vm.get_attribute_opt(&zelf, __reduce__)? {
494 let object_reduce = vm.ctx.types.object_type.get_attr(__reduce__).unwrap();
495 let typ_obj: PyObjectRef = zelf.class().to_owned().into();
496 let class_reduce = typ_obj.get_attr(__reduce__, vm)?;
497 if !class_reduce.is(&object_reduce) {
498 return reduce.call((), vm);
499 }
500 }
501 common_reduce(zelf, protocol, vm)
502 }
503
504 #[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
505 #[pyslot]
506 fn slot_hash(zelf: &PyObject, _vm: &VirtualMachine) -> PyResult<PyHash> {
507 Ok(zelf.get_id() as _)
508 }
509
510 #[pymethod]
511 fn __sizeof__(zelf: PyObjectRef) -> usize {
512 zelf.class()
514 .slots()
515 .basicsize
516 .saturating_sub(crate::object::SIZEOF_PYOBJECT_HEAD)
517 }
518}
519
520pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef> {
521 if let Some(dict) = obj.dict() {
522 Ok(dict)
523 } else {
524 match obj.instance_dict() {
525 Some(d) => Ok(d.get_or_insert(vm)),
526 None => Err(vm.new_attribute_error("This object has no __dict__")),
527 }
528 }
529}
530pub(crate) fn object_set_dict(
531 obj: PyObjectRef,
532 value: PySetterValue,
533 vm: &VirtualMachine,
534) -> PyResult<()> {
535 let dict = match value {
536 PySetterValue::Assign(value) => Some(downcast_dict(value, vm)?),
537 PySetterValue::Delete => None,
538 };
539 if let Some(instance_dict) = obj.instance_dict() {
540 instance_dict.invalidate_inline_values();
541 }
542 obj.set_dict(dict)
543 .map_err(|_| vm.new_attribute_error("This object has no __dict__"))
544}
545
546fn downcast_dict(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef> {
549 value.downcast::<PyDict>().map_err(|value| {
550 vm.new_type_error(format!(
551 "__dict__ must be set to a dictionary, not a '{}'",
552 value.class().name()
553 ))
554 })
555}
556
557pub fn object_generic_set_dict(
559 obj: PyObjectRef,
560 value: PySetterValue,
561 vm: &VirtualMachine,
562) -> PyResult<()> {
563 if matches!(value, PySetterValue::Delete) {
564 return Err(vm.new_type_error("cannot delete __dict__"));
565 }
566 object_set_dict(obj, value, vm)
567}
568
569pub(crate) fn init(ctx: &'static Context) {
570 ctx.types
573 .object_type
574 .slots()
575 .alloc
576 .store(Some(generic_alloc));
577 ctx.types
578 .object_type
579 .slots
580 .init
581 .store(Some(<PyBaseObject as Initializer>::slot_init));
582 PyBaseObject::extend_class(ctx, ctx.types.object_type);
583}
584
585fn get_new_arguments(
588 obj: &PyObject,
589 vm: &VirtualMachine,
590) -> PyResult<(Option<super::PyTupleRef>, Option<super::PyDictRef>)> {
591 if let Some(getnewargs_ex) = vm.get_special_method(obj, identifier!(vm, __getnewargs_ex__))? {
593 let newargs = getnewargs_ex.invoke((), vm)?;
594
595 let newargs_tuple: PyRef<super::PyTuple> = newargs.downcast().map_err(|obj| {
596 vm.new_type_error(format!(
597 "__getnewargs_ex__ should return a tuple, not '{}'",
598 obj.class().name()
599 ))
600 })?;
601
602 if newargs_tuple.as_slice().len() != 2 {
603 return Err(vm.new_value_error(format!(
604 "__getnewargs_ex__ should return a tuple of length 2, not {}",
605 newargs_tuple.as_slice().len()
606 )));
607 }
608
609 let args = newargs_tuple.as_slice()[0].clone();
610 let kwargs = newargs_tuple.as_slice()[1].clone();
611
612 let args_tuple: PyRef<super::PyTuple> = args.downcast().map_err(|obj| {
613 vm.new_type_error(format!(
614 "first item of the tuple returned by __getnewargs_ex__ must be a tuple, not '{}'",
615 obj.class().name()
616 ))
617 })?;
618
619 let kwargs_dict: PyRef<super::PyDict> = kwargs.downcast().map_err(|obj| {
620 vm.new_type_error(format!(
621 "second item of the tuple returned by __getnewargs_ex__ must be a dict, not '{}'",
622 obj.class().name()
623 ))
624 })?;
625
626 return Ok((Some(args_tuple), Some(kwargs_dict)));
627 }
628
629 if let Some(getnewargs) = vm.get_special_method(obj, identifier!(vm, __getnewargs__))? {
631 let args = getnewargs.invoke((), vm)?;
632
633 let args_tuple: PyRef<super::PyTuple> = args.downcast().map_err(|obj| {
634 vm.new_type_error(format!(
635 "__getnewargs__ should return a tuple, not '{}'",
636 obj.class().name()
637 ))
638 })?;
639
640 return Ok((Some(args_tuple), None));
641 }
642
643 Ok((None, None))
645}
646
647fn is_getstate_overridden(obj: &PyObject, vm: &VirtualMachine) -> bool {
649 let obj_cls = obj.class();
650 let object_type = vm.ctx.types.object_type;
651
652 if obj_cls.is(object_type) {
654 return false;
655 }
656
657 if let Some(getstate) = obj_cls.get_attr(identifier!(vm, __getstate__))
660 && let Some(obj_getstate) = object_type.get_attr(identifier!(vm, __getstate__))
661 {
662 return !getstate.is(&obj_getstate);
663 }
664 false
665}
666
667fn object_getstate(obj: &PyObject, required: bool, vm: &VirtualMachine) -> PyResult {
669 if !is_getstate_overridden(obj, vm) {
671 return object_getstate_default(obj, required, vm);
672 }
673
674 let getstate = obj.get_attr(identifier!(vm, __getstate__), vm)?;
676 getstate.call((), vm)
677}
678
679fn get_items_iter(obj: &PyObject, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
681 let listitems: PyObjectRef = if obj.fast_isinstance(vm.ctx.types.list_type) {
682 obj.get_iter(vm)?.into()
683 } else {
684 vm.ctx.none()
685 };
686
687 let dictitems: PyObjectRef = if obj.fast_isinstance(vm.ctx.types.dict_type) {
688 let items = vm.call_method(obj, "items", ())?;
689 items.get_iter(vm)?.into()
690 } else {
691 vm.ctx.none()
692 };
693
694 Ok((listitems, dictitems))
695}
696
697fn reduce_newobj(obj: &PyObject, vm: &VirtualMachine) -> PyResult {
699 let cls = obj.class();
701 if cls.slots.new.load().is_none() {
702 return Err(vm.new_type_error(format!("cannot pickle '{}' object", cls.name())));
703 }
704
705 let (args, kwargs) = get_new_arguments(obj, vm)?;
706
707 let copyreg = vm.import("copyreg", 0)?;
708
709 let has_args = args.is_some();
710
711 let (newobj, newargs): (PyObjectRef, PyObjectRef) = if kwargs.is_none()
712 || kwargs.as_ref().is_some_and(|k| k.is_empty())
713 {
714 let newobj = copyreg.get_attr("__newobj__", vm)?;
716
717 let args_vec: Vec<PyObjectRef> = args.map(|a| a.as_slice().to_vec()).unwrap_or_default();
718
719 let mut newargs_vec: Vec<PyObjectRef> = vec![cls.to_owned().into()];
721 newargs_vec.extend(args_vec);
722 let newargs = vm.ctx.new_tuple(newargs_vec);
723
724 (newobj, newargs.into())
725 } else {
726 let Some(args) = args else {
728 return Err(vm.new_system_error("bad internal call"));
729 };
730 let newobj = copyreg.get_attr("__newobj_ex__", vm)?;
732 let args_tuple: PyObjectRef = args.into();
733 let kwargs_dict: PyObjectRef =
734 kwargs.map_or_else(|| vm.ctx.new_dict().into(), |k| k.into());
735
736 let newargs = vm
737 .ctx
738 .new_tuple(vec![cls.to_owned().into(), args_tuple, kwargs_dict]);
739 (newobj, newargs.into())
740 };
741
742 let is_list = obj.fast_isinstance(vm.ctx.types.list_type);
745 let is_dict = obj.fast_isinstance(vm.ctx.types.dict_type);
746 let required = !(has_args || is_list || is_dict);
747
748 let state = object_getstate(obj, required, vm)?;
749
750 let (listitems, dictitems) = get_items_iter(obj, vm)?;
751
752 let result = vm
753 .ctx
754 .new_tuple(vec![newobj, newargs, state, listitems, dictitems]);
755 Ok(result.into())
756}
757
758fn common_reduce(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
759 if proto >= 2 {
760 reduce_newobj(&obj, vm)
761 } else {
762 let copyreg = vm.import("copyreg", 0)?;
763 let reduce_ex = copyreg.get_attr("_reduce_ex", vm)?;
764 reduce_ex.call((obj, proto), vm)
765 }
766}