mirror of
https://github.com/python/cpython.git
synced 2025-12-04 16:43:27 +00:00
gh-127750: Fix and optimize functools.singledispatchmethod() (GH-130008)
Remove broken singledispatchmethod caching introduced in gh-85160. Achieve the same performance using different optimization. * Add more tests. * Fix issues with __module__ and __doc__ descriptors.
This commit is contained in:
parent
fb2d325725
commit
395335d0ff
3 changed files with 104 additions and 29 deletions
|
|
@ -1026,9 +1026,6 @@ class singledispatchmethod:
|
|||
self.dispatcher = singledispatch(func)
|
||||
self.func = func
|
||||
|
||||
import weakref # see comment in singledispatch function
|
||||
self._method_cache = weakref.WeakKeyDictionary()
|
||||
|
||||
def register(self, cls, method=None):
|
||||
"""generic_method.register(cls, func) -> func
|
||||
|
||||
|
|
@ -1037,38 +1034,52 @@ class singledispatchmethod:
|
|||
return self.dispatcher.register(cls, func=method)
|
||||
|
||||
def __get__(self, obj, cls=None):
|
||||
if self._method_cache is not None:
|
||||
try:
|
||||
_method = self._method_cache[obj]
|
||||
except TypeError:
|
||||
self._method_cache = None
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return _method
|
||||
|
||||
dispatch = self.dispatcher.dispatch
|
||||
funcname = getattr(self.func, '__name__', 'singledispatchmethod method')
|
||||
def _method(*args, **kwargs):
|
||||
if not args:
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'1 positional argument')
|
||||
return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)
|
||||
|
||||
_method.__isabstractmethod__ = self.__isabstractmethod__
|
||||
_method.register = self.register
|
||||
update_wrapper(_method, self.func)
|
||||
|
||||
if self._method_cache is not None:
|
||||
self._method_cache[obj] = _method
|
||||
|
||||
return _method
|
||||
return _singledispatchmethod_get(self, obj, cls)
|
||||
|
||||
@property
|
||||
def __isabstractmethod__(self):
|
||||
return getattr(self.func, '__isabstractmethod__', False)
|
||||
|
||||
|
||||
class _singledispatchmethod_get:
|
||||
def __init__(self, unbound, obj, cls):
|
||||
self._unbound = unbound
|
||||
self._dispatch = unbound.dispatcher.dispatch
|
||||
self._obj = obj
|
||||
self._cls = cls
|
||||
# Set instance attributes which cannot be handled in __getattr__()
|
||||
# because they conflict with type descriptors.
|
||||
func = unbound.func
|
||||
try:
|
||||
self.__module__ = func.__module__
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
self.__doc__ = func.__doc__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def __call__(self, /, *args, **kwargs):
|
||||
if not args:
|
||||
funcname = getattr(self._unbound.func, '__name__',
|
||||
'singledispatchmethod method')
|
||||
raise TypeError(f'{funcname} requires at least '
|
||||
'1 positional argument')
|
||||
return self._dispatch(args[0].__class__).__get__(self._obj, self._cls)(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Resolve these attributes lazily to speed up creation of
|
||||
# the _singledispatchmethod_get instance.
|
||||
if name not in {'__name__', '__qualname__', '__isabstractmethod__',
|
||||
'__annotations__', '__type_params__'}:
|
||||
raise AttributeError
|
||||
return getattr(self._unbound.func, name)
|
||||
|
||||
@property
|
||||
def register(self):
|
||||
return self._unbound.register
|
||||
|
||||
|
||||
################################################################################
|
||||
### cached_property() - property result cached as instance attribute
|
||||
################################################################################
|
||||
|
|
|
|||
|
|
@ -2910,6 +2910,7 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
"""My function docstring"""
|
||||
return str(arg)
|
||||
|
||||
prefix = A.__qualname__ + '.'
|
||||
for meth in (
|
||||
A.func,
|
||||
A().func,
|
||||
|
|
@ -2919,6 +2920,9 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
A().static_func
|
||||
):
|
||||
with self.subTest(meth=meth):
|
||||
self.assertEqual(meth.__module__, __name__)
|
||||
self.assertEqual(type(meth).__module__, 'functools')
|
||||
self.assertEqual(meth.__qualname__, prefix + meth.__name__)
|
||||
self.assertEqual(meth.__doc__,
|
||||
('My function docstring'
|
||||
if support.HAVE_DOCSTRINGS
|
||||
|
|
@ -3251,6 +3255,64 @@ class TestSingleDispatch(unittest.TestCase):
|
|||
def _(arg: undefined):
|
||||
return "forward reference"
|
||||
|
||||
def test_method_equal_instances(self):
|
||||
# gh-127750: Reference to self was cached
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
def __hash__(self):
|
||||
return 1
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
return self
|
||||
|
||||
a = A()
|
||||
b = A()
|
||||
self.assertIs(a.t(1), a)
|
||||
self.assertIs(b.t(2), b)
|
||||
|
||||
def test_method_bad_hash(self):
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
raise AssertionError
|
||||
def __hash__(self):
|
||||
raise AssertionError
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
pass
|
||||
|
||||
# Should not raise
|
||||
A().t(1)
|
||||
hash(A().t)
|
||||
A().t == A().t
|
||||
|
||||
def test_method_no_reference_loops(self):
|
||||
# gh-127750: Created a strong reference to self
|
||||
class A:
|
||||
@functools.singledispatchmethod
|
||||
def t(self, arg):
|
||||
return weakref.ref(self)
|
||||
|
||||
a = A()
|
||||
r = a.t(1)
|
||||
self.assertIsNotNone(r())
|
||||
del a # delete a after a.t
|
||||
if not support.check_impl_detail(cpython=True):
|
||||
support.gc_collect()
|
||||
self.assertIsNone(r())
|
||||
|
||||
a = A()
|
||||
t = a.t
|
||||
del a # delete a before a.t
|
||||
support.gc_collect()
|
||||
r = t(1)
|
||||
self.assertIsNotNone(r())
|
||||
del t
|
||||
if not support.check_impl_detail(cpython=True):
|
||||
support.gc_collect()
|
||||
self.assertIsNone(r())
|
||||
|
||||
|
||||
class CachedCostItem:
|
||||
_cost = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Remove broken :func:`functools.singledispatchmethod` caching introduced in
|
||||
:gh:`85160`. Achieve the same performance using different optimization.
|
||||
Loading…
Add table
Add a link
Reference in a new issue