Allow SimpleLazyObjects to return None without constantly being reevaluated, also proxy ``__nonzero__``, and do some codecleanup as well.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16308 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
632dfa2338
commit
60cf3f2f84
|
@ -1,3 +1,4 @@
|
||||||
|
import operator
|
||||||
from functools import wraps, update_wrapper
|
from functools import wraps, update_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@ -164,6 +165,14 @@ def allow_lazy(func, *resultclasses):
|
||||||
return lazy(func, *resultclasses)(*args, **kwargs)
|
return lazy(func, *resultclasses)(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
empty = object()
|
||||||
|
def new_method_proxy(func):
|
||||||
|
def inner(self, *args):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup()
|
||||||
|
return func(self._wrapped, *args)
|
||||||
|
return inner
|
||||||
|
|
||||||
class LazyObject(object):
|
class LazyObject(object):
|
||||||
"""
|
"""
|
||||||
A wrapper for another class that can be used to delay instantiation of the
|
A wrapper for another class that can be used to delay instantiation of the
|
||||||
|
@ -173,26 +182,23 @@ class LazyObject(object):
|
||||||
instantiation. If you don't need to do that, use SimpleLazyObject.
|
instantiation. If you don't need to do that, use SimpleLazyObject.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._wrapped = None
|
self._wrapped = empty
|
||||||
|
|
||||||
def __getattr__(self, name):
|
__getattr__ = new_method_proxy(getattr)
|
||||||
if self._wrapped is None:
|
|
||||||
self._setup()
|
|
||||||
return getattr(self._wrapped, name)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if name == "_wrapped":
|
if name == "_wrapped":
|
||||||
# Assign to __dict__ to avoid infinite __setattr__ loops.
|
# Assign to __dict__ to avoid infinite __setattr__ loops.
|
||||||
self.__dict__["_wrapped"] = value
|
self.__dict__["_wrapped"] = value
|
||||||
else:
|
else:
|
||||||
if self._wrapped is None:
|
if self._wrapped is empty:
|
||||||
self._setup()
|
self._setup()
|
||||||
setattr(self._wrapped, name, value)
|
setattr(self._wrapped, name, value)
|
||||||
|
|
||||||
def __delattr__(self, name):
|
def __delattr__(self, name):
|
||||||
if name == "_wrapped":
|
if name == "_wrapped":
|
||||||
raise TypeError("can't delete _wrapped.")
|
raise TypeError("can't delete _wrapped.")
|
||||||
if self._wrapped is None:
|
if self._wrapped is empty:
|
||||||
self._setup()
|
self._setup()
|
||||||
delattr(self._wrapped, name)
|
delattr(self._wrapped, name)
|
||||||
|
|
||||||
|
@ -204,11 +210,8 @@ class LazyObject(object):
|
||||||
|
|
||||||
# introspection support:
|
# introspection support:
|
||||||
__members__ = property(lambda self: self.__dir__())
|
__members__ = property(lambda self: self.__dir__())
|
||||||
|
__dir__ = new_method_proxy(dir)
|
||||||
|
|
||||||
def __dir__(self):
|
|
||||||
if self._wrapped is None:
|
|
||||||
self._setup()
|
|
||||||
return dir(self._wrapped)
|
|
||||||
|
|
||||||
class SimpleLazyObject(LazyObject):
|
class SimpleLazyObject(LazyObject):
|
||||||
"""
|
"""
|
||||||
|
@ -229,16 +232,14 @@ class SimpleLazyObject(LazyObject):
|
||||||
self.__dict__['_setupfunc'] = func
|
self.__dict__['_setupfunc'] = func
|
||||||
super(SimpleLazyObject, self).__init__()
|
super(SimpleLazyObject, self).__init__()
|
||||||
|
|
||||||
def __str__(self):
|
def _setup(self):
|
||||||
if self._wrapped is None: self._setup()
|
self._wrapped = self._setupfunc()
|
||||||
return str(self._wrapped)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
__str__ = new_method_proxy(str)
|
||||||
if self._wrapped is None: self._setup()
|
__unicode__ = new_method_proxy(unicode)
|
||||||
return unicode(self._wrapped)
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
if self._wrapped is None:
|
if self._wrapped is empty:
|
||||||
# We have to use SimpleLazyObject, not self.__class__, because the
|
# We have to use SimpleLazyObject, not self.__class__, because the
|
||||||
# latter is proxied.
|
# latter is proxied.
|
||||||
result = SimpleLazyObject(self._setupfunc)
|
result = SimpleLazyObject(self._setupfunc)
|
||||||
|
@ -250,21 +251,10 @@ class SimpleLazyObject(LazyObject):
|
||||||
|
|
||||||
# Need to pretend to be the wrapped class, for the sake of objects that care
|
# Need to pretend to be the wrapped class, for the sake of objects that care
|
||||||
# about this (especially in equality tests)
|
# about this (especially in equality tests)
|
||||||
def __get_class(self):
|
__class__ = property(new_method_proxy(operator.attrgetter("__class__")))
|
||||||
if self._wrapped is None: self._setup()
|
__eq__ = new_method_proxy(operator.eq)
|
||||||
return self._wrapped.__class__
|
__hash__ = new_method_proxy(hash)
|
||||||
__class__ = property(__get_class)
|
__nonzero__ = new_method_proxy(bool)
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if self._wrapped is None: self._setup()
|
|
||||||
return self._wrapped == other
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
if self._wrapped is None: self._setup()
|
|
||||||
return hash(self._wrapped)
|
|
||||||
|
|
||||||
def _setup(self):
|
|
||||||
self._wrapped = self._setupfunc()
|
|
||||||
|
|
||||||
|
|
||||||
class lazy_property(property):
|
class lazy_property(property):
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import copy
|
import copy
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject, empty
|
||||||
|
|
||||||
|
|
||||||
class _ComplexObject(object):
|
class _ComplexObject(object):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
|
@ -65,13 +66,33 @@ class TestUtilsSimpleLazyObject(unittest.TestCase):
|
||||||
|
|
||||||
# First, for an unevaluated SimpleLazyObject
|
# First, for an unevaluated SimpleLazyObject
|
||||||
s = SimpleLazyObject(complex_object)
|
s = SimpleLazyObject(complex_object)
|
||||||
assert s._wrapped is None
|
self.assertIs(s._wrapped, empty)
|
||||||
s2 = copy.deepcopy(s)
|
s2 = copy.deepcopy(s)
|
||||||
assert s._wrapped is None # something has gone wrong is s is evaluated
|
# something has gone wrong is s is evaluated
|
||||||
|
self.assertIs(s._wrapped, empty)
|
||||||
self.assertEqual(s2, complex_object())
|
self.assertEqual(s2, complex_object())
|
||||||
|
|
||||||
# Second, for an evaluated SimpleLazyObject
|
# Second, for an evaluated SimpleLazyObject
|
||||||
name = s.name # evaluate
|
name = s.name # evaluate
|
||||||
assert s._wrapped is not None
|
self.assertIsNot(s._wrapped, empty)
|
||||||
s3 = copy.deepcopy(s)
|
s3 = copy.deepcopy(s)
|
||||||
self.assertEqual(s3, complex_object())
|
self.assertEqual(s3, complex_object())
|
||||||
|
|
||||||
|
|
||||||
|
def test_none(self):
|
||||||
|
i = [0]
|
||||||
|
def f():
|
||||||
|
i[0] += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
x = SimpleLazyObject(f)
|
||||||
|
self.assertEqual(str(x), "None")
|
||||||
|
self.assertEqual(i, [1])
|
||||||
|
self.assertEqual(str(x), "None")
|
||||||
|
self.assertEqual(i, [1])
|
||||||
|
|
||||||
|
def test_bool(self):
|
||||||
|
x = SimpleLazyObject(lambda: 3)
|
||||||
|
self.assertTrue(x)
|
||||||
|
x = SimpleLazyObject(lambda: 0)
|
||||||
|
self.assertFalse(x)
|
Loading…
Reference in New Issue