summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/utils/functional.py60
-rw-r--r--tests/utils_tests/test_functional.py14
2 files changed, 30 insertions, 44 deletions
diff --git a/django/utils/functional.py b/django/utils/functional.py
index e3be672282..ed344166de 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -89,19 +89,14 @@ def lazy(func, *resultclasses):
until one of the methods on the result is called.
"""
- __prepared = False
-
def __init__(self, args, kw):
- self.__args = args
- self.__kw = kw
- if not self.__prepared:
- self.__prepare_class__()
- self.__class__.__prepared = True
+ self._args = args
+ self._kw = kw
def __reduce__(self):
return (
_lazy_proxy_unpickle,
- (func, self.__args, self.__kw) + resultclasses,
+ (func, self._args, self._kw) + resultclasses,
)
def __deepcopy__(self, memo):
@@ -111,31 +106,8 @@ def lazy(func, *resultclasses):
memo[id(self)] = self
return self
- @classmethod
- def __prepare_class__(cls):
- for resultclass in resultclasses:
- for type_ in resultclass.mro():
- for method_name in type_.__dict__:
- # All __promise__ return the same wrapper method, they
- # look up the correct implementation when called.
- if hasattr(cls, method_name):
- continue
- meth = cls.__promise__(method_name)
- setattr(cls, method_name, meth)
-
- @classmethod
- def __promise__(cls, method_name):
- # Builds a wrapper around some magic method
- def __wrapper__(self, *args, **kw):
- # Automatically triggers the evaluation of a lazy value and
- # applies the given magic method of the result type.
- res = func(*self.__args, **self.__kw)
- return getattr(res, method_name)(*args, **kw)
-
- return __wrapper__
-
def __cast(self):
- return func(*self.__args, **self.__kw)
+ return func(*self._args, **self._kw)
# Explicitly wrap methods which are defined on object and hence would
# not have been overloaded by the loop over resultclasses below.
@@ -144,8 +116,6 @@ def lazy(func, *resultclasses):
return repr(self.__cast())
def __str__(self):
- # object defines __str__(), so __prepare_class__() won't overload
- # a __str__() method from the proxied class.
return str(self.__cast())
def __eq__(self, other):
@@ -193,6 +163,28 @@ def lazy(func, *resultclasses):
def __mod__(self, other):
return self.__cast() % other
+ def __promise__(method_name):
+ # Builds a wrapper around some method.
+ def __wrapper__(self, *args, **kw):
+ # Automatically triggers the evaluation of a lazy value and
+ # applies the given method of the result type.
+ res = func(*self._args, **self._kw)
+ return getattr(res, method_name)(*args, **kw)
+
+ return __wrapper__
+
+ # Add wrappers for all methods from resultclasses which haven't been
+ # wrapped explicitly above.
+ for resultclass in resultclasses:
+ for type_ in resultclass.mro():
+ for method_name in type_.__dict__:
+ # All __promise__ return the same wrapper method, they look up
+ # the correct implementation when called.
+ if hasattr(__proxy__, method_name):
+ continue
+ meth = __promise__(method_name)
+ setattr(__proxy__, method_name, meth)
+
@wraps(func)
def __wrapper__(*args, **kw):
# Creates the proxy object, instead of the actual value.
diff --git a/tests/utils_tests/test_functional.py b/tests/utils_tests/test_functional.py
index d49c2a5b49..0fc6ee97da 100644
--- a/tests/utils_tests/test_functional.py
+++ b/tests/utils_tests/test_functional.py
@@ -1,5 +1,3 @@
-from unittest import mock
-
from django.test import SimpleTestCase
from django.utils.functional import cached_property, classproperty, lazy
from django.utils.version import PY312
@@ -273,14 +271,10 @@ class FunctionalTests(SimpleTestCase):
lazy_obj = lazy(lambda: original_object, bytes)
self.assertEqual(repr(original_object), repr(lazy_obj()))
- def test_lazy_class_preparation_caching(self):
- # lazy() should prepare the proxy class only once i.e. the first time
- # it's used.
- lazified = lazy(lambda: 0, int)
- __proxy__ = lazified().__class__
- with mock.patch.object(__proxy__, "__prepare_class__") as mocked:
- lazified()
- mocked.assert_not_called()
+ def test_lazy_regular_method(self):
+ original_object = 15
+ lazy_obj = lazy(lambda: original_object, int)
+ self.assertEqual(original_object.bit_length(), lazy_obj().bit_length())
def test_lazy_bytes_and_str_result_classes(self):
lazy_obj = lazy(lambda: "test", str, bytes)