Skip to content

Commit 8cb87b2

Browse files
committed
backwards compatibility for freezing functionality
1 parent e8bff4c commit 8cb87b2

1 file changed

Lines changed: 79 additions & 48 deletions

File tree

‎Lib/functools.py‎

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
1515
'cached_property', 'Placeholder']
1616

17-
from abc import abstractmethod, get_cache_token
17+
from abc import get_cache_token
1818
from collections import namedtuple
1919
# import weakref # Deferred to single_dispatch()
2020
from operator import itemgetter
@@ -435,10 +435,9 @@ def __setstate__(self, state):
435435
except ImportError:
436436
pass
437437

438-
_UNKNOWN_DESCRIPTOR = object()
439-
_STD_METHOD_TYPES = (staticmethod, classmethod, FunctionType, partial)
440-
_ONE_PLACEHOLDER_TUPLE = (Placeholder,)
441-
_VOID_LAMBDA = lambda *_, **__: None
438+
439+
_PM_WRAPPED_METHOD_TYPES = (staticmethod, classmethod)
440+
_PM_FAST_METHOD_TYPES = _PM_WRAPPED_METHOD_TYPES + (FunctionType, partial)
442441

443442

444443
# Descriptor version
@@ -449,88 +448,120 @@ class partialmethod:
449448
Supports wrapping existing descriptors and handles non-descriptor
450449
callables as instance methods.
451450
"""
452-
453-
__slots__ = ("func", "args", "keywords", "__dict__", "__weakref__")
451+
__slots__ = (
452+
"_func", "_args", "_keywords", "__dict__", "__weakref__",
453+
"_cachedmethod", "_iscachable"
454+
)
454455

455456
__repr__ = _partial_repr
456457

457458
def __init__(self, func, /, *args, **keywords):
458459
if isinstance(func, partialmethod):
459460
# Subclass optimization
460-
temp = partial(_VOID_LAMBDA, *func.args, **func.keywords)
461+
temp = partial(lambda *_, **__: None, *func._args, **func.keywords)
461462
temp = partial(temp, *args, **keywords)
462-
func = func.func
463+
func = func._func
463464
args = temp.args
464465
keywords = temp.keywords
465466

466467
if args and args[-1] is Placeholder:
467468
raise TypeError("trailing Placeholders are not allowed")
468469

469-
self.func = func
470-
self.args = args
471-
self.keywords = keywords
470+
self.func = func # setting via attribute setter
471+
self._args = args
472+
self._keywords = keywords
472473

473-
if isinstance(func, _STD_METHOD_TYPES):
474-
self.method = None
474+
@property
475+
def func(self):
476+
return self._func
477+
478+
@func.setter
479+
def func(self, func):
480+
if isinstance(func, _PM_FAST_METHOD_TYPES):
481+
self._iscachable = True
475482
elif getattr(func, '__get__', None) is None:
476483
if not callable(func):
477484
raise TypeError(f'the first argument {func!r} must be a callable '
478485
'or a descriptor')
479-
self.method = None
486+
self._iscachable = True
480487
else:
481-
# Unknown descriptor
482-
self.method = _UNKNOWN_DESCRIPTOR
488+
self._iscachable = False
489+
self._func = func
490+
self._cachedmethod = None
483491

484-
def __make_method(self):
485-
args = self.args
486-
func = self.func
492+
@property
493+
def args(self):
494+
return self._args
495+
496+
@args.setter
497+
def args(self, args):
498+
self._args = args
499+
self._cachedmethod = None
487500

501+
@property
502+
def keywords(self):
503+
method = self._cachedmethod
504+
if method is None:
505+
return self._keywords
506+
if isinstance(method, _PM_WRAPPED_METHOD_TYPES):
507+
method = method.__wrapped__
508+
return method.keywords
509+
510+
@keywords.setter
511+
def keywords(self, keywords):
512+
self._keywords = keywords
513+
self._cachedmethod = None
514+
515+
def __make_method(self):
516+
func = self._func
517+
args = self._args
518+
keywords = self._keywords
488519
if isinstance(func, staticmethod):
489520
deco = staticmethod
490-
method = partial(func.__wrapped__, *args, **self.keywords)
521+
method = partial(func.__wrapped__, *args, **keywords)
491522
elif isinstance(func, classmethod):
492523
deco = classmethod
493-
ph_args = _ONE_PLACEHOLDER_TUPLE if args else ()
494-
method = partial(func.__wrapped__, *ph_args, *args, **self.keywords)
524+
ph_args = (Placeholder,) if args else ()
525+
method = partial(func.__wrapped__, *ph_args, *args, **keywords)
495526
else:
496527
# instance method. 2 cases:
497528
# a) FunctionType | partial
498529
# b) callable object without __get__
499530
deco = None
500-
ph_args = _ONE_PLACEHOLDER_TUPLE if args else ()
501-
method = partial(func, *ph_args, *args, **self.keywords)
531+
ph_args = (Placeholder,) if args else ()
532+
method = partial(func, *ph_args, *args, **keywords)
502533

503534
method.__partialmethod__ = self
504535
if self.__isabstractmethod__:
505-
method = abstractmethod(method)
536+
method.__isabstractmethod__ = True
506537
if deco is not None:
507538
method = deco(method)
508539
return method
509540

510541
def __get__(self, obj, cls=None):
511-
method = self.method
512-
if method is _UNKNOWN_DESCRIPTOR:
513-
# Unknown descriptor == unknown binding
514-
# Need to get callable at runtime and apply partial on top
515-
new_func = self.func.__get__(obj, cls)
516-
if new_func is self.func:
517-
method = None
518-
else:
519-
result = partial(new_func, *self.args, **self.keywords)
520-
result.__partialmethod__ = self
521-
if self.__isabstractmethod__:
522-
result = abstractmethod(result)
523-
try:
524-
obj = new_func.__self__
525-
except AttributeError:
526-
pass
527-
else:
528-
result.__self__ = obj
529-
return result
530-
542+
method = self._cachedmethod
531543
if method is None:
532-
# Cache method
533-
self.method = method = self.__make_method()
544+
if not self._iscachable:
545+
# Unknown descriptor == unknown binding
546+
# Need to get callable at runtime and apply partial on top
547+
new_func = self._func.__get__(obj, cls)
548+
if new_func is not self.func:
549+
result = partial(new_func, *self._args, **self._keywords)
550+
result.__partialmethod__ = self
551+
if self.__isabstractmethod__:
552+
result.__isabstractmethod__ = True
553+
try:
554+
obj = new_func.__self__
555+
except AttributeError:
556+
pass
557+
else:
558+
result.__self__ = obj
559+
return result
560+
561+
if method is None:
562+
# Cache method
563+
self._cachedmethod = method = self.__make_method()
564+
534565
return method.__get__(obj, cls)
535566

536567
@property

0 commit comments

Comments
 (0)