@@ -403,6 +403,7 @@ def clear_overloads():
403403 "_is_runtime_protocol" , "__dict__" , "__slots__" , "__parameters__" ,
404404 "__orig_bases__" , "__module__" , "_MutableMapping__marker" , "__doc__" ,
405405 "__subclasshook__" , "__orig_class__" , "__init__" , "__new__" ,
406+ "__protocol_attrs__" , "__callable_proto_members_only__" ,
406407}
407408
408409if sys .version_info < (3 , 8 ):
@@ -420,19 +421,15 @@ def clear_overloads():
420421def _get_protocol_attrs (cls ):
421422 attrs = set ()
422423 for base in cls .__mro__ [:- 1 ]: # without object
423- if base .__name__ in ( 'Protocol' , 'Generic' ) :
424+ if base .__name__ in { 'Protocol' , 'Generic' } :
424425 continue
425426 annotations = getattr (base , '__annotations__' , {})
426- for attr in list ( base .__dict__ . keys ()) + list ( annotations . keys () ):
427+ for attr in ( * base .__dict__ , * annotations ):
427428 if (not attr .startswith ('_abc_' ) and attr not in _EXCLUDED_ATTRS ):
428429 attrs .add (attr )
429430 return attrs
430431
431432
432- def _is_callable_members_only (cls ):
433- return all (callable (getattr (cls , attr , None )) for attr in _get_protocol_attrs (cls ))
434-
435-
436433def _maybe_adjust_parameters (cls ):
437434 """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__.
438435
@@ -442,7 +439,7 @@ def _maybe_adjust_parameters(cls):
442439 """
443440 tvars = []
444441 if '__orig_bases__' in cls .__dict__ :
445- tvars = typing . _collect_type_vars (cls .__orig_bases__ )
442+ tvars = _collect_type_vars (cls .__orig_bases__ )
446443 # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
447444 # If found, tvars must be a subset of it.
448445 # If not found, tvars is it.
@@ -480,9 +477,9 @@ def _caller(depth=2):
480477 return None
481478
482479
483- # A bug in runtime-checkable protocols was fixed in 3.10+ ,
484- # but we backport it to all versions
485- if sys .version_info >= (3 , 10 ):
480+ # The performance of runtime-checkable protocols is significantly improved on Python 3.12 ,
481+ # so we backport the 3.12 version of Protocol to Python <=3.11
482+ if sys .version_info >= (3 , 12 ):
486483 Protocol = typing .Protocol
487484 runtime_checkable = typing .runtime_checkable
488485else :
@@ -500,6 +497,15 @@ def _no_init(self, *args, **kwargs):
500497 class _ProtocolMeta (abc .ABCMeta ):
501498 # This metaclass is a bit unfortunate and exists only because of the lack
502499 # of __instancehook__.
500+ def __init__ (cls , * args , ** kwargs ):
501+ super ().__init__ (* args , ** kwargs )
502+ cls .__protocol_attrs__ = _get_protocol_attrs (cls )
503+ # PEP 544 prohibits using issubclass()
504+ # with protocols that have non-method members.
505+ cls .__callable_proto_members_only__ = all (
506+ callable (getattr (cls , attr , None )) for attr in cls .__protocol_attrs__
507+ )
508+
503509 def __instancecheck__ (cls , instance ):
504510 # We need this method for situations where attributes are
505511 # assigned in __init__.
@@ -511,17 +517,22 @@ def __instancecheck__(cls, instance):
511517 ):
512518 raise TypeError ("Instance and class checks can only be used with"
513519 " @runtime_checkable protocols" )
514- if ((not is_protocol_cls or
515- _is_callable_members_only (cls )) and
516- issubclass (instance .__class__ , cls )):
520+
521+ if super ().__instancecheck__ (instance ):
517522 return True
523+
518524 if is_protocol_cls :
519- if all (hasattr (instance , attr ) and
520- (not callable (getattr (cls , attr , None )) or
521- getattr (instance , attr ) is not None )
522- for attr in _get_protocol_attrs (cls )):
525+ for attr in cls .__protocol_attrs__ :
526+ try :
527+ val = getattr (instance , attr )
528+ except AttributeError :
529+ break
530+ if val is None and callable (getattr (cls , attr , None )):
531+ break
532+ else :
523533 return True
524- return super ().__instancecheck__ (instance )
534+
535+ return False
525536
526537 class Protocol (metaclass = _ProtocolMeta ):
527538 # There is quite a lot of overlapping code with typing.Generic.
@@ -613,15 +624,15 @@ def _proto_hook(other):
613624 return NotImplemented
614625 raise TypeError ("Instance and class checks can only be used with"
615626 " @runtime protocols" )
616- if not _is_callable_members_only ( cls ) :
627+ if not cls . __callable_proto_members_only__ :
617628 if _allow_reckless_class_checks ():
618629 return NotImplemented
619630 raise TypeError ("Protocols with non-method members"
620631 " don't support issubclass()" )
621632 if not isinstance (other , type ):
622633 # Same error as for issubclass(1, int)
623634 raise TypeError ('issubclass() arg 1 must be a class' )
624- for attr in _get_protocol_attrs ( cls ) :
635+ for attr in cls . __protocol_attrs__ :
625636 for base in other .__mro__ :
626637 if attr in base .__dict__ :
627638 if base .__dict__ [attr ] is None :
@@ -1819,6 +1830,10 @@ class Movie(TypedDict):
18191830
18201831if hasattr (typing , "Unpack" ): # 3.11+
18211832 Unpack = typing .Unpack
1833+
1834+ def _is_unpack (obj ):
1835+ return get_origin (obj ) is Unpack
1836+
18221837elif sys .version_info [:2 ] >= (3 , 9 ):
18231838 class _UnpackSpecialForm (typing ._SpecialForm , _root = True ):
18241839 def __repr__ (self ):
0 commit comments