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

This commit is contained in:
Daniel Lindsley 2013-05-20 22:17:56 -07:00
parent 4ba1c2e785
commit e24d486fbc
2 changed files with 40 additions and 5 deletions

View File

@ -5,6 +5,11 @@ import sys
from django.utils import six from django.utils import six
try:
import copyreg
except ImportError:
import copy_reg as copyreg
# You can't trivially replace this with `functools.partial` because this binds # You can't trivially replace this with `functools.partial` because this binds
# to classes and returns bound instances, whereas functools.partial (on # to classes and returns bound instances, whereas functools.partial (on
@ -323,15 +328,23 @@ class SimpleLazyObject(LazyObject):
self._setup() self._setup()
return self._wrapped.__dict__ return self._wrapped.__dict__
# Python 3.3 will call __reduce__ when pickling; these methods are needed # Python 3.3 will call __reduce__ when pickling; this method is needed
# to serialize and deserialize correctly. They are not called in earlier # to serialize and deserialize correctly.
# versions of Python.
@classmethod @classmethod
def __newobj__(cls, *args): def __newobj__(cls, *args):
return cls.__new__(cls, *args) return cls.__new__(cls, *args)
def __reduce__(self): def __reduce_ex__(self, proto):
return (self.__newobj__, (self.__class__,), self.__getstate__()) if proto >= 2:
# On Py3, since the default protocol is 3, pickle uses the
# ``__newobj__`` method (& more efficient opcodes) for writing.
return (self.__newobj__, (self.__class__,), self.__getstate__())
else:
# On Py2, the default protocol is 0 (for back-compat) & the above
# code fails miserably (see regression test). Instead, we return
# exactly what's returned if there's no ``__reduce__`` method at
# all.
return (copyreg._reconstructor, (self.__class__, object, None), self.__getstate__())
# Return a meaningful representation of the lazy object for debugging # Return a meaningful representation of the lazy object for debugging
# without evaluating the wrapped object. # without evaluating the wrapped object.

View File

@ -161,3 +161,25 @@ class TestUtilsSimpleLazyObject(TestCase):
self.assertNotEqual(lazy1, lazy3) self.assertNotEqual(lazy1, lazy3)
self.assertTrue(lazy1 != lazy3) self.assertTrue(lazy1 != lazy3)
self.assertFalse(lazy1 != lazy2) self.assertFalse(lazy1 != lazy2)
def test_pickle_py2_regression(self):
from django.contrib.auth.models import User
# See ticket #20212
user = User.objects.create_user('johndoe', 'john@example.com', 'pass')
x = SimpleLazyObject(lambda: user)
# This would fail with "TypeError: can't pickle instancemethod objects",
# only on Python 2.X.
pickled = pickle.dumps(x)
# Try the variant protocol levels.
pickled = pickle.dumps(x, 0)
pickled = pickle.dumps(x, 1)
pickled = pickle.dumps(x, 2)
if not six.PY3:
import cPickle
# This would fail with "TypeError: expected string or Unicode object, NoneType found".
pickled = cPickle.dumps(x)