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:
Alex Gaynor 2011-06-01 15:30:06 +00:00
parent 632dfa2338
commit 60cf3f2f84
2 changed files with 49 additions and 38 deletions

View File

@ -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):

View File

@ -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)