8000 gh-127750: Improve caching in singledispatchmethod by vodik · Pull Request #127751 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-127750: Improve caching in singledispatchmethod #127751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 34 additions & 18 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,9 +1027,7 @@ def __init__(self, func):

self.dispatcher = singledispatch(func)
self.func = func

import weakref # see comment in singledispatch function
self._method_cache = weakref.WeakKeyDictionary()
self._method_cache = {}

def register(self, cls, method=None):
"""generic_method.register(cls, func) -> func
Expand All @@ -1039,30 +1037,48 @@ def register(self, cls, method=None):
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:
cache_key = id(obj)

try:
_obj_ref, _method = self._method_cache[id(obj)]
except KeyError:
pass
else:
if _obj_ref() is obj:
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)

def _remove(_):
self._method_cache.pop(cache_key, None)

import weakref # see comment in singledispatch function
try:
obj_ref = weakref.ref(obj, _remove)
except TypeError:
cache_key = None

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)
else:
def _method(*args, **kwargs):
if not args:
raise TypeError(f'{funcname} requires at least '
'1 positional argument')
# Make sure to use the weakref here to prevent storing
# a strong reference to obj in the cache
return dispatch(args[0].__class__).__get__(obj_ref(), cls)(*args, **kwargs)
Copy link
Author
@vodik vodik Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this introduce a data race though? If I assigned the method and then deleted the underlying obj? I'll have to try it.

Is a weakref even appropriate for this cache then? Or should I throw a ReferenceError?

-                return dispatch(args[0].__class__).__get__(obj_ref(), cls)(*args, **kwargs)
+                obj = obj_ref()
+                if obj is None:
+                    raise ReferenceError
+
+                return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)

Or is this whole approach maybe not correct enough?


_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
if cache_key:
self._method_cache[cache_key] = (obj_ref, _method)

return _method

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve caching of :class:`functools.singledispatchmethod` to fix issues
where sometimes different object instances could collide with each other.
Loading
0