1# mode: run
2# tag: pickle
3
4import cython
5import sys
6
7if sys.version_info[0] < 3:
8    __doc__ = """
9    >>> import cPickle
10    >>> a = A(5); a
11    A(5)
12    >>> cPickle.loads(cPickle.dumps(a))
13    A(5)
14
15    >>> b = B(0, 1); b
16    B(x=0, y=1)
17    >>> cPickle.loads(cPickle.dumps(b))
18    B(x=0, y=1)
19    """
20
21cdef class A:
22    """
23    >>> a = A(3); a
24    A(3)
25    >>> import pickle
26    >>> pickle.loads(pickle.dumps(a))
27    A(3)
28    """
29
30    cdef int value
31
32    def __init__(self, value):
33        self.value = value
34
35    def __repr__(self):
36        return "A(%s)" % self.value
37
38    def __reduce__(self):
39        return A, (self.value,)
40
41cdef class B:
42    """
43    >>> b = B(x=37, y=389); b
44    B(x=37, y=389)
45    >>> import pickle
46    >>> pickle.loads(pickle.dumps(b))
47    B(x=37, y=389)
48    """
49
50    cdef int x, y
51
52    def __cinit__(self):
53        self.x = self.y = -1
54
55    def __init__(self, x=0, y=0):
56        self.x = x
57        self.y = y
58
59    def __repr__(self):
60        return "%s(x=%s, y=%s)" % (self.__class__.__name__, self.x, self.y)
61
62    def __reduce__(self):
63        return makeObj, (type(self), {'x': self.x, 'y': self.y})
64
65def makeObj(obj_type, kwds):
66    return obj_type(**kwds)
67
68
69cdef class C(B):
70    """
71    >>> import pickle
72    >>> pickle.loads(pickle.dumps(C(x=37, y=389)))
73    C(x=37, y=389)
74    """
75    pass
76
77
78@cython.auto_pickle(True)  # Not needed, just to test the directive.
79cdef class DefaultReduce(object):
80    """
81    >>> a = DefaultReduce(11, 'abc'); a
82    DefaultReduce(i=11, s='abc')
83    >>> import pickle
84    >>> pickle.loads(pickle.dumps(a))
85    DefaultReduce(i=11, s='abc')
86    >>> pickle.loads(pickle.dumps(DefaultReduce(i=11, s=None)))
87    DefaultReduce(i=11, s=None)
88    """
89
90    cdef readonly int i
91    cdef readonly str s
92
93    def __init__(self, i=0, s=None):
94        self.i = i
95        self.s = s
96
97    def __repr__(self):
98        return "DefaultReduce(i=%s, s=%r)" % (self.i, self.s)
99
100
101cdef class DefaultReduceSubclass(DefaultReduce):
102    """
103    >>> a = DefaultReduceSubclass(i=11, s='abc', x=1.5); a
104    DefaultReduceSubclass(i=11, s='abc', x=1.5)
105    >>> import pickle
106    >>> pickle.loads(pickle.dumps(a))
107    DefaultReduceSubclass(i=11, s='abc', x=1.5)
108    """
109
110    cdef double x
111
112    def __init__(self, **kwargs):
113        self.x = kwargs.pop('x', 0)
114        super(DefaultReduceSubclass, self).__init__(**kwargs)
115
116    def __repr__(self):
117        return "DefaultReduceSubclass(i=%s, s=%r, x=%s)" % (self.i, self.s, self.x)
118
119
120cdef class result(DefaultReduceSubclass):
121    """
122    >>> a = result(i=11, s='abc', x=1.5); a
123    result(i=11, s='abc', x=1.5)
124    >>> import pickle
125    >>> pickle.loads(pickle.dumps(a))
126    result(i=11, s='abc', x=1.5)
127    """
128
129    def __repr__(self):
130        return "result(i=%s, s=%r, x=%s)" % (self.i, self.s, self.x)
131
132
133class DefaultReducePySubclass(DefaultReduce):
134    """
135    >>> a = DefaultReducePySubclass(i=11, s='abc', x=1.5); a
136    DefaultReducePySubclass(i=11, s='abc', x=1.5)
137    >>> import pickle
138    >>> pickle.loads(pickle.dumps(a))
139    DefaultReducePySubclass(i=11, s='abc', x=1.5)
140
141    >>> a.self_reference = a
142    >>> a2 = pickle.loads(pickle.dumps(a))
143    >>> a2.self_reference is a2
144    True
145    """
146    def __init__(self, **kwargs):
147        self.x = kwargs.pop('x', 0)
148        super(DefaultReducePySubclass, self).__init__(**kwargs)
149
150    def __repr__(self):
151        return "DefaultReducePySubclass(i=%s, s=%r, x=%s)" % (self.i, self.s, self.x)
152
153
154cdef class NoReduceDueToIntPtr(object):
155    """
156    >>> import pickle
157    >>> pickle.dumps(NoReduceDueToIntPtr())
158    Traceback (most recent call last):
159    ...
160    TypeError: self.int_ptr cannot be converted to a Python object for pickling
161    """
162    cdef int* int_ptr
163
164cdef class NoReduceDueToNontrivialCInit(object):
165    """
166    >>> import pickle
167    >>> pickle.dumps(NoReduceDueToNontrivialCInit(None))
168    Traceback (most recent call last):
169    ...
170    TypeError: no default __reduce__ due to non-trivial __cinit__
171    """
172    def __cinit__(self, arg):
173        pass
174
175
176cdef class NoMembers(object):
177    """
178    >>> import pickle
179    >>> pickle.loads(pickle.dumps(NoMembers()))
180    NoMembers()
181    """
182    def __repr__(self):
183        return "NoMembers()"
184
185
186cdef class NoPyMembers(object):
187    """
188    >>> import pickle
189    >>> pickle.loads(pickle.dumps(NoPyMembers(2, 1.75)))
190    NoPyMembers(ii=[2, 4, 8], x=1.75)
191    """
192    cdef int[3] ii
193    cdef double x
194
195    def __init__(self, i, x):
196        self.ii[0] = i
197        self.ii[1] = i * i
198        self.ii[2] = i * i * i
199        self.x = x
200
201    def __repr__(self):
202        return "%s(ii=%s, x=%s)" % (type(self).__name__, self.ii, self.x)
203
204class NoPyMembersPySubclass(NoPyMembers):
205    """
206    >>> import pickle
207    >>> pickle.loads(pickle.dumps(NoPyMembersPySubclass(2, 1.75, 'xyz')))
208    NoPyMembersPySubclass(ii=[2, 4, 8], x=1.75, s='xyz')
209    """
210    def __init__(self, i, x, s):
211        super(NoPyMembersPySubclass, self).__init__(i, x)
212        self.s = s
213    def __repr__(self):
214        return (super(NoPyMembersPySubclass, self).__repr__()
215                [:-1] + ', s=%r)' % self.s)
216
217
218cdef struct MyStruct:
219    int i
220    double x
221
222cdef class StructMemberDefault(object):
223    """
224    >>> import pickle
225    >>> s = StructMemberDefault(1, 1.5); s
226    StructMemberDefault(i=1, x=1.5)
227    >>> pickle.dumps(s)   # doctest: +ELLIPSIS
228    Traceback (most recent call last):
229    TypeError: ...my_struct...
230    """
231
232    cdef MyStruct my_struct
233
234    def __init__(self, i, x):
235        self.my_struct.i = i
236        self.my_struct.x = x
237
238    def __repr__(self):
239        return "%s(i=%s, x=%s)" % (
240            type(self).__name__, self.my_struct.i, self.my_struct.x)
241
242@cython.auto_pickle(True)  # Forced due to the (inherited) struct attribute.
243cdef class StructMemberForcedPickle(StructMemberDefault):
244    """
245    >>> import pickle
246    >>> s = StructMemberForcedPickle(1, 1.5); s
247    StructMemberForcedPickle(i=1, x=1.5)
248    >>> pickle.loads(pickle.dumps(s))
249    StructMemberForcedPickle(i=1, x=1.5)
250    """
251
252
253cdef _unset = object()
254
255# Test cyclic references.
256cdef class Wrapper(object):
257  """
258  >>> import pickle
259  >>> w = Wrapper(); w
260  Wrapper(...)
261  >>> w2 = pickle.loads(pickle.dumps(w)); w2
262  Wrapper(...)
263  >>> w2.ref is w2
264  True
265
266  >>> pickle.loads(pickle.dumps(Wrapper(DefaultReduce(1, 'xyz'))))
267  Wrapper(DefaultReduce(i=1, s='xyz'))
268  >>> L = [None]
269  >>> L[0] = L
270  >>> w = Wrapper(L)
271  >>> pickle.loads(pickle.dumps(Wrapper(L)))
272  Wrapper([[...]])
273
274  >>> L[0] = w   # Don't print this one out...
275  >>> w2 = pickle.loads(pickle.dumps(w))
276  >>> w2.ref[0] is w2
277  True
278  """
279  cdef public object ref
280  def __init__(self, ref=_unset):
281      if ref is _unset:
282          self.ref = self
283      else:
284          self.ref = ref
285  def __repr__(self):
286      if self.ref is self:
287          return "Wrapper(...)"
288      else:
289          return "Wrapper(%r)" % self.ref
290
291
292# Non-regression test for pickling bound and unbound methods of non-extension
293# classes
294if sys.version_info[:2] >= (3, 5):
295    # builtin methods not picklable for python <= 3.4
296    class MyClass(object):
297        """
298        >>> import pickle
299        >>> pickle.loads(pickle.dumps(MyClass.my_method)) is MyClass.my_method
300        True
301        >>> bound_method = pickle.loads(pickle.dumps(MyClass().my_method))
302        >>> bound_method(1)
303        1
304        """
305        def my_method(self, x):
306            return x
307