1414 'partial' , 'partialmethod' , 'singledispatch' , 'singledispatchmethod' ,
1515 'cached_property' , 'Placeholder' ]
1616
17- from abc import get_cache_token
17+ from abc import abstractmethod , get_cache_token
1818from collections import namedtuple
1919# import types, weakref # Deferred to single_dispatch()
2020from operator import itemgetter
2121from reprlib import recursive_repr
22- from types import MethodType
22+ from types import FunctionType , MethodType
2323from _thread import RLock
2424
2525# Avoid importing types, so we can speedup import time
@@ -316,49 +316,6 @@ def _partial_prepare_merger(args):
316316 merger = itemgetter (* order ) if phcount else None
317317 return phcount , merger
318318
319- def _partial_new (cls , func , / , * args , ** keywords ):
320- if issubclass (cls , partial ):
321- base_cls = partial
322- if not callable (func ):
323- raise TypeError ("the first argument must be callable" )
324- else :
325- base_cls = partialmethod
326- # func could be a descriptor like classmethod which isn't callable
327- if not callable (func ) and not hasattr (func , "__get__" ):
328- raise TypeError (f"the first argument { func !r} must be a callable "
329- "or a descriptor" )
330- if args and args [- 1 ] is Placeholder :
331- raise TypeError ("trailing Placeholders are not allowed" )
332- if isinstance (func , base_cls ):
333- pto_phcount = func ._phcount
334- tot_args = func .args
335- if args :
336- tot_args += args
337- if pto_phcount :
338- # merge args with args of `func` which is `partial`
339- nargs = len (args )
340- if nargs < pto_phcount :
341- tot_args += (Placeholder ,) * (pto_phcount - nargs )
342- tot_args = func ._merger (tot_args )
343- if nargs > pto_phcount :
344- tot_args += args [pto_phcount :]
345- phcount , merger = _partial_prepare_merger (tot_args )
346- else : # works for both pto_phcount == 0 and != 0
347- phcount , merger = pto_phcount , func ._merger
348- keywords = {** func .keywords , ** keywords }
349- func = func .func
350- else :
351- tot_args = args
352- phcount , merger = _partial_prepare_merger (tot_args )
353-
354- self = object .__new__ (cls )
355- self .func = func
356- self .args = tot_args
357- self .keywords = keywords
358- self ._phcount = phcount
359- self ._merger = merger
360- return self
361-
362319def _partial_repr (self ):
363320 cls = type (self )
364321 module = cls .__module__
@@ -377,7 +334,49 @@ class partial:
377334 __slots__ = ("func" , "args" , "keywords" , "_phcount" , "_merger" ,
378335 "__dict__" , "__weakref__" )
379336
380- __new__ = _partial_new
337+ def __new__ (cls , func , / , * args , ** keywords ):
338+ if not callable (func ):
339+ raise TypeError ("the first argument must be callable" )
340+ if args and args [- 1 ] is Placeholder :
341+ # Trim trailing placeholders
342+ j = len (args ) - 1
343+ if not j :
344+ args = ()
345+ else :
346+ while (j := j - 1 ) >= 0 :
347+ if args [j ] is not Placeholder :
348+ break
349+ args = args [:j + 1 ]
350+ if isinstance (func , partial ):
351+ pto_phcount = func ._phcount
352+ tot_args = func .args
353+ if args :
354+ tot_args += args
355+ if pto_phcount :
356+ # merge args with args of `func` which is `partial`
357+ nargs = len (args )
358+ if nargs < pto_phcount :
359+ tot_args += (Placeholder ,) * (pto_phcount - nargs )
360+ tot_args = func ._merger (tot_args )
361+ if nargs > pto_phcount :
362+ tot_args += args [pto_phcount :]
363+ phcount , merger = _partial_prepare_merger (tot_args )
364+ else : # works for both pto_phcount == 0 and != 0
365+ phcount , merger = pto_phcount , func ._merger
366+ keywords = {** func .keywords , ** keywords }
367+ func = func .func
368+ else :
369+ tot_args = args
370+ phcount , merger = _partial_prepare_merger (tot_args )
371+
372+ self = object .__new__ (cls )
373+ self .func = func
374+ self .args = tot_args
375+ self .keywords = keywords
376+ self ._phcount = phcount
377+ self ._merger = merger
378+ return self
379+
381380 __repr__ = recursive_repr ()(_partial_repr )
382381
383382 def __call__ (self , / , * args , ** keywords ):
@@ -416,7 +415,7 @@ def __setstate__(self, state):
416415 raise TypeError ("invalid partial state" )
417416
418417 if args and args [- 1 ] is Placeholder :
419- raise TypeError ("trailing Placeholders are not allowed " )
418+ raise TypeError ("unexpected trailing Placeholders" )
420419 phcount , merger = _partial_prepare_merger (args )
421420
422421 args = tuple (args ) # just in case it's a subclass
@@ -439,6 +438,7 @@ def __setstate__(self, state):
439438except ImportError :
440439 pass
441440
441+
442442# Descriptor version
443443class partialmethod :
444444 """Method descriptor with partial application of the given arguments
@@ -447,50 +447,65 @@ class partialmethod:
447447 Supports wrapping existing descriptors and handles non-descriptor
448448 callables as instance methods.
449449 """
450- __new__ = _partial_new
450+
451+ __slots__ = ("func" , "args" , "keywords" , "wrapper" ,
452+ "__isabstractmethod__" , "__dict__" , "__weakref__" )
451453 __repr__ = _partial_repr
452454
453- def _make_unbound_method (self ):
454- def _method (cls_or_self , / , * args , ** keywords ):
455- phcount = self ._phcount
456- if phcount :
457- try :
458- pto_args = self ._merger (self .args + args )
459- args = args [phcount :]
460- except IndexError :
461- raise TypeError ("missing positional arguments "
462- "in 'partialmethod' call; expected "
463- f"at least { phcount } , got { len (args )} " )
464- else :
465- pto_args = self .args
466- keywords = {** self .keywords , ** keywords }
467- return self .func (cls_or_self , * pto_args , * args , ** keywords )
468- _method .__isabstractmethod__ = self .__isabstractmethod__
469- _method .__partialmethod__ = self
470- return _method
455+ def __init__ (self , func , / , * args , ** keywords ):
456+ if isinstance (func , partialmethod ):
457+ # Subclass optimization
458+ temp = partial (lambda : None , * func .args , ** func .keywords )
459+ temp = partial (temp , * args , ** keywords )
460+ func = func .func
461+ args = temp .args
462+ keywords = temp .keywords
463+ self .func = func
464+ self .args = args
465+ self .keywords = keywords
466+ self .__isabstractmethod__ = getattr (func , "__isabstractmethod__" , False )
467+
468+ # 5 cases
469+ rewrap = None
470+ if isinstance (func , staticmethod ):
471+ self .wrapper = partial (func .__wrapped__ , * args , ** keywords )
472+ rewrap = staticmethod
473+ elif isinstance (func , classmethod ):
474+ self .wrapper = partial (func .__wrapped__ , Placeholder , * args , ** keywords )
475+ rewrap = classmethod
476+ elif isinstance (func , (FunctionType , partial )):
477+ # instance method
478+ self .wrapper = partial (func , Placeholder , * args , ** keywords )
479+ elif getattr (func , '__get__' , None ) is None :
480+ if not callable (func ):
481+ raise TypeError (f"the first argument { func !r} must be a callable "
482+ "or a descriptor" )
483+ # callable object without __get__
484+ # treat this like an instance method
485+ self .wrapper = partial (func , Placeholder , * args , ** keywords )
486+ else :
487+ # Unknown descriptor
488+ self .wrapper = None
471489
472- def __get__ (self , obj , cls = None ):
473- get = getattr (self .func , "__get__" , None )
474- result = None
475- if get is not None :
476- new_func = get (obj , cls )
477- if new_func is not self .func :
478- # Assume __get__ returning something new indicates the
479- # creation of an appropriate callable
480- result = partial (new_func , * self .args , ** self .keywords )
481- try :
482- result .__self__ = new_func .__self__
483- except AttributeError :
484- pass
485- if result is None :
486- # If the underlying descriptor didn't do anything, treat this
487- # like an instance method
488- result = self ._make_unbound_method ().__get__ (obj , cls )
489- return result
490+ # Adjust for abstract and rewrap if needed
491+ if self .wrapper is not None :
492+ if self .__isabstractmethod__ :
493+ self .wrapper = abstractmethod (self .wrapper )
494+ if rewrap is not None :
495+ self .wrapper = rewrap (self .wrapper )
490496
491- @property
492- def __isabstractmethod__ (self ):
493- return getattr (self .func , "__isabstractmethod__" , False )
497+ def __get__ (self , obj , cls = None ):
498+ if self .wrapper is not None :
499+ return self .wrapper .__get__ (obj , cls )
500+ else :
501+ # Unknown descriptor
502+ new_func = getattr (self .func , '__get__' )(obj , cls )
503+ result = partial (new_func , * self .args , ** self .keywords )
504+ try :
505+ result .__self__ = new_func .__self__
506+ except AttributeError :
507+ pass
508+ return result
494509
495510 __class_getitem__ = classmethod (GenericAlias )
496511
@@ -506,8 +521,6 @@ def _unwrap_partialmethod(func):
506521 prev = None
507522 while func is not prev :
508523 prev = func
509- while isinstance (getattr (func , "__partialmethod__" , None ), partialmethod ):
510- func = func .__partialmethod__
511524 while isinstance (func , partialmethod ):
512525 func = getattr (func , 'func' )
513526 func = _unwrap_partial (func )
0 commit comments