From 60cf3f2f842b6e56132b8880c70acc87bd753c2e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 1 Jun 2011 15:30:06 +0000 Subject: [PATCH] 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 --- django/utils/functional.py | 58 ++++++++----------- .../regressiontests/utils/simplelazyobject.py | 29 ++++++++-- 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