@@ -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
0 commit comments