mirror of https://github.com/django/django.git
Made proxy class in lazy() prepare eagerly.
Previously, the proxy class was prepared lazily: lazy_identity = lazy(identity, int) lazy_identity(10) # prepared here lazy_identity(10) This has a slight advantage that if the lazy doesn't end up getting used, the preparation work is skipped, however that's not very likely. Besides this laziness, it is also inconsistent in that the methods which are wrapped directly (__str__ etc.) are prepared already when __proxy__ is defined, and there is a weird half-initialized state. This change it so that everything is prepared already on the first line of the example above.
This commit is contained in:
parent
b214845f0f
commit
ae94077e7d
|
@ -89,19 +89,14 @@ def lazy(func, *resultclasses):
|
||||||
until one of the methods on the result is called.
|
until one of the methods on the result is called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__prepared = False
|
|
||||||
|
|
||||||
def __init__(self, args, kw):
|
def __init__(self, args, kw):
|
||||||
self.__args = args
|
self._args = args
|
||||||
self.__kw = kw
|
self._kw = kw
|
||||||
if not self.__prepared:
|
|
||||||
self.__prepare_class__()
|
|
||||||
self.__class__.__prepared = True
|
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (
|
return (
|
||||||
_lazy_proxy_unpickle,
|
_lazy_proxy_unpickle,
|
||||||
(func, self.__args, self.__kw) + resultclasses,
|
(func, self._args, self._kw) + resultclasses,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
|
@ -111,31 +106,8 @@ def lazy(func, *resultclasses):
|
||||||
memo[id(self)] = self
|
memo[id(self)] = self
|
||||||
return 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):
|
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
|
# Explicitly wrap methods which are defined on object and hence would
|
||||||
# not have been overloaded by the loop over resultclasses below.
|
# not have been overloaded by the loop over resultclasses below.
|
||||||
|
@ -144,8 +116,6 @@ def lazy(func, *resultclasses):
|
||||||
return repr(self.__cast())
|
return repr(self.__cast())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# object defines __str__(), so __prepare_class__() won't overload
|
|
||||||
# a __str__() method from the proxied class.
|
|
||||||
return str(self.__cast())
|
return str(self.__cast())
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -193,6 +163,28 @@ def lazy(func, *resultclasses):
|
||||||
def __mod__(self, other):
|
def __mod__(self, other):
|
||||||
return self.__cast() % 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)
|
@wraps(func)
|
||||||
def __wrapper__(*args, **kw):
|
def __wrapper__(*args, **kw):
|
||||||
# Creates the proxy object, instead of the actual value.
|
# Creates the proxy object, instead of the actual value.
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.utils.functional import cached_property, classproperty, lazy
|
from django.utils.functional import cached_property, classproperty, lazy
|
||||||
from django.utils.version import PY312
|
from django.utils.version import PY312
|
||||||
|
@ -273,14 +271,10 @@ class FunctionalTests(SimpleTestCase):
|
||||||
lazy_obj = lazy(lambda: original_object, bytes)
|
lazy_obj = lazy(lambda: original_object, bytes)
|
||||||
self.assertEqual(repr(original_object), repr(lazy_obj()))
|
self.assertEqual(repr(original_object), repr(lazy_obj()))
|
||||||
|
|
||||||
def test_lazy_class_preparation_caching(self):
|
def test_lazy_regular_method(self):
|
||||||
# lazy() should prepare the proxy class only once i.e. the first time
|
original_object = 15
|
||||||
# it's used.
|
lazy_obj = lazy(lambda: original_object, int)
|
||||||
lazified = lazy(lambda: 0, int)
|
self.assertEqual(original_object.bit_length(), lazy_obj().bit_length())
|
||||||
__proxy__ = lazified().__class__
|
|
||||||
with mock.patch.object(__proxy__, "__prepare_class__") as mocked:
|
|
||||||
lazified()
|
|
||||||
mocked.assert_not_called()
|
|
||||||
|
|
||||||
def test_lazy_bytes_and_str_result_classes(self):
|
def test_lazy_bytes_and_str_result_classes(self):
|
||||||
lazy_obj = lazy(lambda: "test", str, bytes)
|
lazy_obj = lazy(lambda: "test", str, bytes)
|
||||||
|
|
Loading…
Reference in New Issue