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
1818from collections import namedtuple
1919# import weakref # Deferred to single_dispatch()
2020from operator import itemgetter
@@ -435,10 +435,9 @@ def __setstate__(self, state):
435435except 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