Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #20212 - __reduce__ should only be defined for Py3+.

  • Loading branch information...
commit e24d486fbc0b1c42abe8b54217ff428e449c48cc 1 parent 4ba1c2e
Daniel Lindsley authored
23  django/utils/functional.py
@@ -5,6 +5,11 @@
5 5
 
6 6
 from django.utils import six
7 7
 
  8
+try:
  9
+    import copyreg
  10
+except ImportError:
  11
+    import copy_reg as copyreg
  12
+
8 13
 
9 14
 # You can't trivially replace this with `functools.partial` because this binds
10 15
 # to classes and returns bound instances, whereas functools.partial (on
@@ -323,15 +328,23 @@ def __getstate__(self):
323 328
             self._setup()
324 329
         return self._wrapped.__dict__
325 330
 
326  
-    # Python 3.3 will call __reduce__ when pickling; these methods are needed
327  
-    # to serialize and deserialize correctly. They are not called in earlier
328  
-    # versions of Python.
  331
+    # Python 3.3 will call __reduce__ when pickling; this method is needed
  332
+    # to serialize and deserialize correctly.
329 333
     @classmethod
330 334
     def __newobj__(cls, *args):
331 335
         return cls.__new__(cls, *args)
332 336
 
333  
-    def __reduce__(self):
334  
-        return (self.__newobj__, (self.__class__,), self.__getstate__())
  337
+    def __reduce_ex__(self, proto):
  338
+        if proto >= 2:
  339
+            # On Py3, since the default protocol is 3, pickle uses the
  340
+            # ``__newobj__`` method (& more efficient opcodes) for writing.
  341
+            return (self.__newobj__, (self.__class__,), self.__getstate__())
  342
+        else:
  343
+            # On Py2, the default protocol is 0 (for back-compat) & the above
  344
+            # code fails miserably (see regression test). Instead, we return
  345
+            # exactly what's returned if there's no ``__reduce__`` method at
  346
+            # all.
  347
+            return (copyreg._reconstructor, (self.__class__, object, None), self.__getstate__())
335 348
 
336 349
     # Return a meaningful representation of the lazy object for debugging
337 350
     # without evaluating the wrapped object.
22  tests/utils_tests/test_simplelazyobject.py
@@ -161,3 +161,25 @@ def test_not_equal(self):
161 161
         self.assertNotEqual(lazy1, lazy3)
162 162
         self.assertTrue(lazy1 != lazy3)
163 163
         self.assertFalse(lazy1 != lazy2)
  164
+
  165
+    def test_pickle_py2_regression(self):
  166
+        from django.contrib.auth.models import User
  167
+
  168
+        # See ticket #20212
  169
+        user = User.objects.create_user('johndoe', 'john@example.com', 'pass')
  170
+        x = SimpleLazyObject(lambda: user)
  171
+
  172
+        # This would fail with "TypeError: can't pickle instancemethod objects",
  173
+        # only on Python 2.X.
  174
+        pickled = pickle.dumps(x)
  175
+
  176
+        # Try the variant protocol levels.
  177
+        pickled = pickle.dumps(x, 0)
  178
+        pickled = pickle.dumps(x, 1)
  179
+        pickled = pickle.dumps(x, 2)
  180
+
  181
+        if not six.PY3:
  182
+            import cPickle
  183
+
  184
+            # This would fail with "TypeError: expected string or Unicode object, NoneType found".
  185
+            pickled = cPickle.dumps(x)

0 notes on commit e24d486

Please sign in to comment.
Something went wrong with that request. Please try again.