summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2011-06-01 15:30:06 +0000
committerAlex Gaynor <alex.gaynor@gmail.com>2011-06-01 15:30:06 +0000
commit60cf3f2f842b6e56132b8880c70acc87bd753c2e (patch)
treee432d89353511cded892080cd1906c01b8167cf2
parent632dfa233839f8c76bd6b065b8420fe0ab96175a (diff)
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
-rw-r--r--django/utils/functional.py58
-rw-r--r--tests/regressiontests/utils/simplelazyobject.py29
2 files changed, 49 insertions, 38 deletions
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 76f3639b58..c8f8ee33c7 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -1,3 +1,4 @@
+import operator
from functools import wraps, update_wrapper
@@ -164,6 +165,14 @@ def allow_lazy(func, *resultclasses):
return lazy(func, *resultclasses)(*args, **kwargs)
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):
"""
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.
"""
def __init__(self):
- self._wrapped = None
+ self._wrapped = empty
- def __getattr__(self, name):
- if self._wrapped is None:
- self._setup()
- return getattr(self._wrapped, name)
+ __getattr__ = new_method_proxy(getattr)
def __setattr__(self, name, value):
if name == "_wrapped":
# Assign to __dict__ to avoid infinite __setattr__ loops.
self.__dict__["_wrapped"] = value
else:
- if self._wrapped is None:
+ if self._wrapped is empty:
self._setup()
setattr(self._wrapped, name, value)
def __delattr__(self, name):
if name == "_wrapped":
raise TypeError("can't delete _wrapped.")
- if self._wrapped is None:
+ if self._wrapped is empty:
self._setup()
delattr(self._wrapped, name)
@@ -204,11 +210,8 @@ class LazyObject(object):
# introspection support:
__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):
"""
@@ -229,16 +232,14 @@ class SimpleLazyObject(LazyObject):
self.__dict__['_setupfunc'] = func
super(SimpleLazyObject, self).__init__()
- def __str__(self):
- if self._wrapped is None: self._setup()
- return str(self._wrapped)
+ def _setup(self):
+ self._wrapped = self._setupfunc()
- def __unicode__(self):
- if self._wrapped is None: self._setup()
- return unicode(self._wrapped)
+ __str__ = new_method_proxy(str)
+ __unicode__ = new_method_proxy(unicode)
def __deepcopy__(self, memo):
- if self._wrapped is None:
+ if self._wrapped is empty:
# We have to use SimpleLazyObject, not self.__class__, because the
# latter is proxied.
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
# about this (especially in equality tests)
- def __get_class(self):
- if self._wrapped is None: self._setup()
- return self._wrapped.__class__
- __class__ = property(__get_class)
-
- 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__ = property(new_method_proxy(operator.attrgetter("__class__")))
+ __eq__ = new_method_proxy(operator.eq)
+ __hash__ = new_method_proxy(hash)
+ __nonzero__ = new_method_proxy(bool)
class lazy_property(property):
@@ -285,4 +275,4 @@ class lazy_property(property):
@wraps(fdel)
def fdel(instance, name=fdel.__name__):
return getattr(instance, name)()
- return property(fget, fset, fdel, doc)
+ return property(fget, fset, fdel, doc) \ No newline at end of file
diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py
index e29729df60..e84752a5cc 100644
--- a/tests/regressiontests/utils/simplelazyobject.py
+++ b/tests/regressiontests/utils/simplelazyobject.py
@@ -1,7 +1,8 @@
import copy
import unittest
-from django.utils.functional import SimpleLazyObject
+from django.utils.functional import SimpleLazyObject, empty
+
class _ComplexObject(object):
def __init__(self, name):
@@ -65,13 +66,33 @@ class TestUtilsSimpleLazyObject(unittest.TestCase):
# First, for an unevaluated SimpleLazyObject
s = SimpleLazyObject(complex_object)
- assert s._wrapped is None
+ self.assertIs(s._wrapped, empty)
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())
# Second, for an evaluated SimpleLazyObject
name = s.name # evaluate
- assert s._wrapped is not None
+ self.assertIsNot(s._wrapped, empty)
s3 = copy.deepcopy(s)
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) \ No newline at end of file