Skip to content

Commit 79b75b6

Browse files
committed
Implement object.__reduce__
1 parent 4d0623e commit 79b75b6

File tree

3 files changed

+128
-5
lines changed

3 files changed

+128
-5
lines changed

vm/Lib/__reducelib.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Modified from code from the PyPy project:
2+
# https://bitbucket.org/pypy/pypy/src/default/pypy/objspace/std/objectobject.py
3+
4+
# The MIT License
5+
6+
# Permission is hereby granted, free of charge, to any person
7+
# obtaining a copy of this software and associated documentation
8+
# files (the "Software"), to deal in the Software without
9+
# restriction, including without limitation the rights to use,
10+
# copy, modify, merge, publish, distribute, sublicense, and/or
11+
# sell copies of the Software, and to permit persons to whom the
12+
# Software is furnished to do so, subject to the following conditions:
13+
14+
# The above copyright notice and this permission notice shall be included
15+
# in all copies or substantial portions of the Software.
16+
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23+
# DEALINGS IN THE SOFTWARE.
24+
25+
import copyreg
26+
27+
28+
def _abstract_method_error(typ):
29+
methods = ", ".join(sorted(typ.__abstractmethods__))
30+
err = "Can't instantiate abstract class %s with abstract methods %s"
31+
raise TypeError(err % (typ.__name__, methods))
32+
33+
34+
def reduce_2(obj):
35+
cls = obj.__class__
36+
37+
try:
38+
getnewargs = obj.__getnewargs__
39+
except AttributeError:
40+
args = ()
41+
else:
42+
args = getnewargs()
43+
if not isinstance(args, tuple):
44+
raise TypeError("__getnewargs__ should return a tuple")
45+
46+
try:
47+
getstate = obj.__getstate__
48+
except AttributeError:
49+
state = getattr(obj, "__dict__", None)
50+
names = slotnames(cls) # not checking for list
51+
if names is not None:
52+
slots = {}
53+
for name in names:
54+
try:
55+
value = getattr(obj, name)
56+
except AttributeError:
57+
pass
58+
else:
59+
slots[name] = value
60+
if slots:
61+
state = state, slots
62+
else:
63+
state = getstate()
64+
65+
listitems = iter(obj) if isinstance(obj, list) else None
66+
dictitems = obj.iteritems() if isinstance(obj, dict) else None
67+
68+
newobj = copyreg.__newobj__
69+
70+
args2 = (cls,) + args
71+
return newobj, args2, state, listitems, dictitems
72+
73+
74+
def slotnames(cls):
75+
if not isinstance(cls, type):
76+
return None
77+
78+
try:
79+
return cls.__dict__["__slotnames__"]
80+
except KeyError:
81+
pass
82+
83+
slotnames = copyreg._slotnames(cls)
84+
if not isinstance(slotnames, list) and slotnames is not None:
85+
raise TypeError("copyreg._slotnames didn't return a list or None")
86+
return slotnames

vm/src/frozen.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ pub fn get_module_inits() -> HashMap<String, FrozenModule> {
1515
file = "Lib/_bootstrap_external.py",
1616
module_name = "_frozen_importlib_external",
1717
));
18+
modules.extend(py_compile_bytecode!(
19+
file = "../Lib/copyreg.py",
20+
module_name = "copyreg",
21+
));
22+
modules.extend(py_compile_bytecode!(
23+
file = "Lib/__reducelib.py",
24+
module_name = "__reducelib",
25+
));
1826

1927
#[cfg(feature = "freeze-stdlib")]
2028
{
21-
modules.extend(py_compile_bytecode!(dir = "../Lib/",));
29+
modules.extend(py_compile_bytecode!(dir = "../Lib/"));
2230
}
2331

2432
modules

vm/src/obj/objobject.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use super::objdict::PyDictRef;
22
use super::objlist::PyList;
33
use super::objstr::PyStringRef;
4-
use super::objtype;
5-
use crate::function::PyFuncArgs;
4+
use super::objtype::{self, PyClassRef};
5+
use crate::function::{OptionalArg, PyFuncArgs};
66
use crate::obj::objproperty::PropertyBuilder;
7-
use crate::obj::objtype::PyClassRef;
87
use crate::pyhash;
98
use crate::pyobject::{
109
IdProtocol, ItemProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue,
@@ -186,6 +185,8 @@ pub fn init(context: &PyContext) {
186185
"__format__" => context.new_rustfunc(object_format),
187186
"__getattribute__" => context.new_rustfunc(object_getattribute),
188187
"__subclasshook__" => context.new_classmethod(object_subclasshook),
188+
"__reduce__" => context.new_rustfunc(object_reduce),
189+
"__reduce_ex__" => context.new_rustfunc(object_reduce_ex),
189190
"__doc__" => context.new_str(object_doc.to_string()),
190191
});
191192
}
@@ -211,7 +212,7 @@ fn object_dict(object: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef>
211212
if let Some(ref dict) = object.dict {
212213
Ok(dict.clone())
213214
} else {
214-
Err(vm.new_type_error("TypeError: no dictionary.".to_string()))
215+
Err(vm.new_attribute_error("no dictionary.".to_string()))
215216
}
216217
}
217218

@@ -230,3 +231,31 @@ fn object_getattribute(obj: PyObjectRef, name: PyStringRef, vm: &VirtualMachine)
230231
vm.generic_getattribute(obj.clone(), name.clone())?
231232
.ok_or_else(|| vm.new_attribute_error(format!("{} has no attribute '{}'", obj, name)))
232233
}
234+
235+
fn object_reduce(obj: PyObjectRef, proto: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult {
236+
common_reduce(obj, proto.unwrap_or(0), vm)
237+
}
238+
239+
fn object_reduce_ex(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
240+
let cls = obj.class();
241+
if let Some(reduce) = objtype::class_get_attr(&cls, "__reduce__") {
242+
let object_reduce =
243+
objtype::class_get_attr(&vm.ctx.types.object_type, "__reduce__").unwrap();
244+
if !reduce.is(&object_reduce) {
245+
return vm.invoke(&reduce, vec![]);
246+
}
247+
}
248+
common_reduce(obj, proto, vm)
249+
}
250+
251+
fn common_reduce(obj: PyObjectRef, proto: usize, vm: &VirtualMachine) -> PyResult {
252+
if proto >= 2 {
253+
let reducelib = vm.import("__reducelib", &[], 0)?;
254+
let reduce_2 = vm.get_attribute(reducelib, "reduce_2")?;
255+
vm.invoke(&reduce_2, vec![obj])
256+
} else {
257+
let copyreg = vm.import("copyreg", &[], 0)?;
258+
let reduce_ex = vm.get_attribute(copyreg, "_reduce_ex")?;
259+
vm.invoke(&reduce_ex, vec![obj, vm.new_int(proto)])
260+
}
261+
}

0 commit comments

Comments
 (0)