8000 full backwards compatibility · python/cpython@f323dbd · GitHub
[go: up one dir, main page]

Skip to content

Commit f323dbd

Browse files
committed
full backwards compatibility
1 parent 8fa0ec5 commit f323dbd

File tree

1 file changed

+68
-49
lines changed

1 file changed

+68
-49
lines changed

Lib/functools.py

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,9 @@ def __setstate__(self, state):
432432
except ImportError:
433433
pass
434434

435+
_NULL = object()
436+
_UNKNOWN_DESCRIPTOR = object()
437+
_STD_METHOD_TYPES = (staticmethod, classmethod, FunctionType, partial)
435438

436439
# Descriptor version
437440
class partialmethod:
@@ -443,86 +446,102 @@ class partialmethod:
443446
"""
444447

445448
__slots__ = ("func", "args", "keywords", "wrapper",
446-
"__isabstractmethod__", "__dict__", "__weakref__")
449+
"__dict__", "__weakref__")
447450

448451
__repr__ = _partial_repr
449-
__class_getitem__ = classmethod(GenericAlias)
450452

451453
def __init__(self, func, /, *args, **keywords):
454+
if not callable(func) and getattr(func, '__get__', None) is None:
455+
raise TypeError(f'the first argument {func!r} must be a callable '
456+
'or a descriptor')
457+
452458
if isinstance(func, partialmethod):
453459
# Subclass optimization
454460
temp = partial(lambda: None, *func.args, **func.keywords)
455461
temp = partial(temp, *args, **keywords)
456-
isabstract = func.__isabstractmethod__
457462
func = func.func
458463
args = temp.args
459464
keywords = temp.keywords
460-
else:
461-
isabstract = getattr(func, '__isabstractmethod__', False)
465+
462466
self.func = func
463467
self.args = args
464468
self.keywords = keywords
465-
self.__isabstractmethod__ = isabstract
466469

467-
# 5 cases
470+
if (isinstance(func, _STD_METHOD_TYPES) or
471+
getattr(func, '__get__', None) is None):
472+
self.method = None
473+
else:
474+
# Unknown descriptor
475+
self.method = _UNKNOWN_DESCRIPTOR
476+
477+
def _set_func_attrs(self, func):
478+
func.__partialmethod__ = self
479+
if self.__isabstractmethod__:
480+
func = abstractmethod(func)
481+
return func
482+
483+
def _make_method(self):
484+
args = self.args
485+
func = self.func
486+
487+
# 4 cases
468488
if isinstance(func, staticmethod):
469-
wrapper = partial(func.__wrapped__, *args, **keywords)
470-
self.wrapper = _rewrap_func(wrapper, isabstract, staticmethod)
489+
func = partial(func.__wrapped__, *args, **self.keywords)
490+
self._set_func_attrs(func)
491+
return staticmethod(func)
471492
elif isinstance(func, classmethod):
472-
wrapper = _partial_unbound(func.__wrapped__, args, keywords)
473-
self.wrapper = _rewrap_func(wrapper, isabstract, classmethod)
474-
elif isinstance(func, (FunctionType, partial)):
475-
# instance method
476-
wrapper = _partial_unbound(func, args, keywords)
477-
self.wrapper = _rewrap_func(wrapper, isabstract)
478-
elif getattr(func, '__get__', None) is None:
479-
# callable object without __get__
480-
# treat this like an instance method
481-
if not callable(func):
482-
raise TypeError(f'the first argument {func!r} must be a callable '
483-
'or a descriptor')
484-
wrapper = _partial_unbound(func, args, keywords)
485-
self.wrapper = _rewrap_func(wrapper, isabstract)
493+
ph_args = (Placeholder,) if args else ()
494+
func = partial(func.__wrapped__, *ph_args, *args, **self.keywords)
495+
self._set_func_attrs(func)
496+
return classmethod(func)
486497
else:
487-
# Unknown descriptor
488-
self.wrapper = None
498+
# instance method. 2 cases:
499+
# a) FunctionType | partial
500+
# b) callable object without __get__
501+
ph_args = (Placeholder,) if args else ()
502+
func = partial(func, *ph_args, *args, **self.keywords)
503+
self._set_func_attrs(func)
504+
return func
489505

490506
def __get__(self, obj, cls=None):
491-
if self.wrapper is not None:
492-
return self.wrapper.__get__(obj, cls)
493-
else:
494-
# Unknown descriptor
495-
new_func = getattr(self.func, '__get__')(obj, cls)
507+
method = self.method
508+
if method is _UNKNOWN_DESCRIPTOR:
509+
# Unknown descriptor == unknown binding
510+
# Need to get callable at runtime and apply partial on top
511+
new_func = self.func.__get__(obj, cls)
496512
result = partial(new_func, *self.args, **self.keywords)
497-
try:
498-
result.__self__ = new_func.__self__
499-
except AttributeError:
500-
pass
513+
self._set_func_attrs(func)
514+
__self__ = getattr(new_func, '__self__', _NULL)
515+
if __self__ is not _NULL:
516+
result.__self__ = __self__
501517
return result
518+
if method is None:
519+
# Cache method
520+
self.method = method = self._make_method()
521+
return method.__get__(obj, cls)
502522

523+
@property
524+
def __isabstractmethod__(self):
525+
return getattr(self.func, '__isabstractmethod__', False)
503526

504-
# Helper functions
527+
__class_getitem__ = classmethod(GenericAlias)
505528

506-
def _partial_unbound(func, args, keywords):
507-
if not args:
508-
return partial(func, **keywords)
509-
return partial(func, Placeholder, *args, **keywords)
510-
511-
def _rewrap_func(func, isabstract, decorator=None):
512-
if isabstract:
513-
func = abstractmethod(func)
514-
if decorator is not None:
515-
func = decorator(func)
516-
return func
529+
# Helper functions
517530

518531
def _unwrap_partial(func):
519-
while isinstance(func, partial):
532+
if isinstance(func, partial):
520533
func = func.func
521534
return func
522535

523536
def _unwrap_partialmethod(func):
524-
while isinstance(func, (partial, partialmethod)):
525-
func = func.func
537+
prev = None
538+
while func is not prev:
539+
prev = func
540+
__partialmethod__ = getattr(func, "__partialmethod__", None)
541+
if isinstance(__partialmethod__, partialmethod):
542+
func = __partialmethod__.func
543+
if isinstance(func, (partial, partialmethod)):
544+
func = func.func
526545
return func
527546

528547

0 commit comments

Comments
 (0)
0