@@ -432,6 +432,9 @@ def __setstate__(self, state):
432
432
except ImportError :
433
433
pass
434
434
435
+ _NULL = object ()
436
+ _UNKNOWN_DESCRIPTOR = object ()
437
+ _STD_METHOD_TYPES = (staticmethod ,classmethod ,FunctionType ,partial )
435
438
436
439
# Descriptor version
437
440
class partialmethod :
@@ -443,86 +446,102 @@ class partialmethod:
443
446
"""
444
447
445
448
__slots__ = ("func" ,"args" ,"keywords" ,"wrapper" ,
446
- "__isabstractmethod__" , " __dict__" ,"__weakref__" )
449
+ "__dict__" ,"__weakref__" )
447
450
448
451
__repr__ = _partial_repr
449
- __class_getitem__ = classmethod (GenericAlias )
450
452
451
453
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
+
452
458
if isinstance (func ,partialmethod ):
453
459
# Subclass optimization
454
460
temp = partial (lambda :None ,* func .args ,** func .keywords )
455
461
temp = partial (temp ,* args ,** keywords )
456
- isabstract = func .__isabstractmethod__
457
462
func = func .func
458
463
args = temp .args
459
464
keywords = temp .keywords
460
- else :
461
- isabstract = getattr (func ,'__isabstractmethod__' ,False )
465
+
462
466
self .func = func
463
467
self .args = args
464
468
self .keywords = keywords
465
- self .__isabstractmethod__ = isabstract
466
469
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
468
488
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 )
471
492
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 )
486
497
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
489
505
490
506
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 )
496
512
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__
501
517
return result
518
+ if method is None :
519
+ # Cache method
520
+ self .method = method = self ._make_method ()
521
+ return method .__get__ (obj ,cls )
502
522
523
+ @property
524
+ def __isabstractmethod__ (self ):
525
+ return getattr (self .func ,'__isabstractmethod__' ,False )
503
526
504
- # Helper functions
527
+ __class_getitem__ = classmethod ( GenericAlias )
505
528
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
517
530
518
531
def _unwrap_partial (func ):
519
- while isinstance (func ,partial ):
532
+ if isinstance (func ,partial ):
520
533
func = func .func
521
534
return func
522
535
523
536
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
526
545
return func
527
546
528
547