Commit 1f85154c authored by Robert Bradshaw's avatar Robert Bradshaw

Avoid pickling objects with struct attributes.

Extern structs may only be partially declared.
parent 7603976e
...@@ -1603,6 +1603,7 @@ if VALUE is not None: ...@@ -1603,6 +1603,7 @@ if VALUE is not None:
if node.scope.directives['auto_pickle'] is False: # None means attempt it. if node.scope.directives['auto_pickle'] is False: # None means attempt it.
# Old behavior of not doing anything. # Old behavior of not doing anything.
return return
auto_pickle_forced = node.scope.directives['auto_pickle'] is True
all_members = [] all_members = []
cls = node.entry.type cls = node.entry.type
...@@ -1627,14 +1628,22 @@ if VALUE is not None: ...@@ -1627,14 +1628,22 @@ if VALUE is not None:
or not e.type.can_coerce_from_pyobject(env)) or not e.type.can_coerce_from_pyobject(env))
] ]
if cinit or non_py: structs = [e for e in all_members if e.type.is_struct_or_union]
if cinit or non_py or (structs and not auto_pickle_forced):
if cinit: if cinit:
# TODO(robertwb): We could allow this if __cinit__ has no require arguments. # TODO(robertwb): We could allow this if __cinit__ has no require arguments.
msg = 'no default __reduce__ due to non-trivial __cinit__' msg = 'no default __reduce__ due to non-trivial __cinit__'
else: elif non_py:
msg = "%s cannot be converted to a Python object for pickling" % ','.join("self.%s" % e.name for e in non_py) msg = "%s cannot be converted to a Python object for pickling" % ','.join("self.%s" % e.name for e in non_py)
else:
# Extern structs may be only partially defined.
# TODO(robertwb): Limit the restriction to extern
# (and recursively extern-containing) structs.
msg = ("Pickling of struct members such as %s must be explicitly requested "
"with @auto_pickle(True)" % ','.join("self.%s" % e.name for e in structs))
if node.scope.directives['auto_pickle'] is True: if auto_pickle_forced:
error(node.pos, msg) error(node.pos, msg)
pickle_func = TreeFragment(u""" pickle_func = TreeFragment(u"""
......
...@@ -167,43 +167,71 @@ cdef class NoMembers(object): ...@@ -167,43 +167,71 @@ cdef class NoMembers(object):
return "NoMembers()" return "NoMembers()"
cdef struct MyStruct:
int i
double x
cdef class NoPyMembers(object): cdef class NoPyMembers(object):
""" """
>>> import pickle >>> import pickle
>>> pickle.loads(pickle.dumps(NoPyMembers(2, 1.75))) >>> pickle.loads(pickle.dumps(NoPyMembers(2, 1.75)))
NoPyMembers(ii=[2, 4, 8], x=1.75, my_struct=(3, 2.75)) NoPyMembers(ii=[2, 4, 8], x=1.75)
""" """
cdef int[3] ii cdef int[3] ii
cdef double x cdef double x
cdef MyStruct my_struct
def __init__(self, i, x): def __init__(self, i, x):
self.ii[0] = i self.ii[0] = i
self.ii[1] = i * i self.ii[1] = i * i
self.ii[2] = i * i * i self.ii[2] = i * i * i
self.x = x self.x = x
self.my_struct = MyStruct(i+1, x+1)
def __repr__(self): def __repr__(self):
return "NoPyMembers(ii=%s, x=%s, my_struct=(%s, %s))" % ( return "%s(ii=%s, x=%s)" % (type(self).__name__, self.ii, self.x)
self.ii, self.x, self.my_struct.i, self.my_struct.x)
class NoPyMembersPySubclass(NoPyMembers): class NoPyMembersPySubclass(NoPyMembers):
""" """
>>> import pickle >>> import pickle
>>> pickle.loads(pickle.dumps(NoPyMembersPySubclass(2, 1.75, 'xyz'))) >>> pickle.loads(pickle.dumps(NoPyMembersPySubclass(2, 1.75, 'xyz')))
NoPyMembersPySubclass(ii=[2, 4, 8], x=1.75, my_struct=(3, 2.75), s='xyz') NoPyMembersPySubclass(ii=[2, 4, 8], x=1.75, s='xyz')
""" """
def __init__(self, i, x, s): def __init__(self, i, x, s):
super(NoPyMembersPySubclass, self).__init__(i, x) super(NoPyMembersPySubclass, self).__init__(i, x)
self.s = s self.s = s
def __repr__(self): def __repr__(self):
return super(NoPyMembersPySubclass, self).__repr__().replace( return (super(NoPyMembersPySubclass, self).__repr__()
'NoPyMembers', 'NoPyMembersPySubclass')[:-1] + ', s=%r)' % self.s [:-1] + ', s=%r)' % self.s)
cdef struct MyStruct:
int i
double x
cdef class StructMemberDefault(object):
"""
>>> import pickle
>>> s = StructMemberDefault(1, 1.5); s
StructMemberDefault(i=1, x=1.5)
>>> pickle.dumps(s) # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: ...my_struct...
"""
cdef MyStruct my_struct
def __init__(self, i, x):
self.my_struct.i = i
self.my_struct.x = x
def __repr__(self):
return "%s(i=%s, x=%s)" % (
type(self).__name__, self.my_struct.i, self.my_struct.x)
@cython.auto_pickle(True) # Forced due to the (inherited) struct attribute.
cdef class StructMemberForcedPickle(StructMemberDefault):
"""
>>> import pickle
>>> s = StructMemberForcedPickle(1, 1.5); s
StructMemberForcedPickle(i=1, x=1.5)
>>> pickle.loads(pickle.dumps(s))
StructMemberForcedPickle(i=1, x=1.5)
"""
cdef _unset = object() cdef _unset = object()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment