@@ -473,7 +473,7 @@ def clear_overloads():
473473 "_is_runtime_protocol" , "__dict__" , "__slots__" , "__parameters__" ,
474474 "__orig_bases__" , "__module__" , "_MutableMapping__marker" , "__doc__" ,
475475 "__subclasshook__" , "__orig_class__" , "__init__" , "__new__" ,
476- "__protocol_attrs__" , "__callable_proto_members_only__ " ,
476+ "__protocol_attrs__" , "__non_callable_proto_members__ " ,
477477 "__match_args__" ,
478478}
479479
@@ -521,6 +521,22 @@ def _no_init(self, *args, **kwargs):
521521 if type (self )._is_protocol :
522522 raise TypeError ('Protocols cannot be instantiated' )
523523
524+ def _type_check_issubclass_arg_1 (arg ):
525+ """Raise TypeError if `arg` is not an instance of `type`
526+ in `issubclass(arg, <protocol>)`.
527+
528+ In most cases, this is verified by type.__subclasscheck__.
529+ Checking it again unnecessarily would slow down issubclass() checks,
530+ so, we don't perform this check unless we absolutely have to.
531+
532+ For various error paths, however,
533+ we want to ensure that *this* error message is shown to the user
534+ where relevant, rather than a typing.py-specific error message.
535+ """
536+ if not isinstance (arg , type ):
537+ # Same error message as for issubclass(1, int).
538+ raise TypeError ('issubclass() arg 1 must be a class' )
539+
524540 # Inheriting from typing._ProtocolMeta isn't actually desirable,
525541 # but is necessary to allow typing.Protocol and typing_extensions.Protocol
526542 # to mix without getting TypeErrors about "metaclass conflict"
@@ -551,11 +567,6 @@ def __init__(cls, *args, **kwargs):
551567 abc .ABCMeta .__init__ (cls , * args , ** kwargs )
552568 if getattr (cls , "_is_protocol" , False ):
553569 cls .__protocol_attrs__ = _get_protocol_attrs (cls )
554- # PEP 544 prohibits using issubclass()
555- # with protocols that have non-method members.
556- cls .__callable_proto_members_only__ = all (
557- callable (getattr (cls , attr , None )) for attr in cls .__protocol_attrs__
558- )
559570
560571 def __subclasscheck__ (cls , other ):
561572 if cls is Protocol :
@@ -564,26 +575,23 @@ def __subclasscheck__(cls, other):
564575 getattr (cls , '_is_protocol' , False )
565576 and not _allow_reckless_class_checks ()
566577 ):
567- if not isinstance (other , type ):
568- # Same error message as for issubclass(1, int).
569- raise TypeError ('issubclass() arg 1 must be a class' )
578+ if not getattr (cls , '_is_runtime_protocol' , False ):
579+ _type_check_issubclass_arg_1 (other )
580+ raise TypeError (
581+ "Instance and class checks can only be used with "
582+ "@runtime_checkable protocols"
583+ )
570584 if (
571- not cls .__callable_proto_members_only__
585+ # this attribute is set by @runtime_checkable:
586+ cls .__non_callable_proto_members__
572587 and cls .__dict__ .get ("__subclasshook__" ) is _proto_hook
573588 ):
574- non_method_attrs = sorted (
575- attr for attr in cls .__protocol_attrs__
576- if not callable (getattr (cls , attr , None ))
577- )
589+ _type_check_issubclass_arg_1 (other )
590+ non_method_attrs = sorted (cls .__non_callable_proto_members__ )
578591 raise TypeError (
579592 "Protocols with non-method members don't support issubclass()."
580593 f" Non-method members: { str (non_method_attrs )[1 :- 1 ]} ."
581594 )
582- if not getattr (cls , '_is_runtime_protocol' , False ):
583- raise TypeError (
584- "Instance and class checks can only be used with "
585- "@runtime_checkable protocols"
586- )
587595 return abc .ABCMeta .__subclasscheck__ (cls , other )
588596
589597 def __instancecheck__ (cls , instance ):
@@ -610,7 +618,8 @@ def __instancecheck__(cls, instance):
610618 val = inspect .getattr_static (instance , attr )
611619 except AttributeError :
612620 break
613- if val is None and callable (getattr (cls , attr , None )):
621+ # this attribute is set by @runtime_checkable:
622+ if val is None and attr not in cls .__non_callable_proto_members__ :
614623 break
615624 else :
616625 return True
@@ -678,8 +687,58 @@ def __init_subclass__(cls, *args, **kwargs):
678687 cls .__init__ = _no_init
679688
680689
690+ if sys .version_info >= (3 , 13 ):
691+ runtime_checkable = typing .runtime_checkable
692+ else :
693+ def runtime_checkable (cls ):
694+ """Mark a protocol class as a runtime protocol.
695+
696+ Such protocol can be used with isinstance() and issubclass().
697+ Raise TypeError if applied to a non-protocol class.
698+ This allows a simple-minded structural check very similar to
699+ one trick ponies in collections.abc such as Iterable.
700+
701+ For example::
702+
703+ @runtime_checkable
704+ class Closable(Protocol):
705+ def close(self): ...
706+
707+ assert isinstance(open('/some/file'), Closable)
708+
709+ Warning: this will check only the presence of the required methods,
710+ not their type signatures!
711+ """
712+ if not issubclass (cls , typing .Generic ) or not getattr (cls , '_is_protocol' , False ):
713+ raise TypeError ('@runtime_checkable can be only applied to protocol classes,'
714+ ' got %r' % cls )
715+ cls ._is_runtime_protocol = True
716+
717+ # Only execute the following block if it's a typing_extensions.Protocol class.
718+ # typing.Protocol classes don't need it.
719+ if isinstance (cls , _ProtocolMeta ):
720+ # PEP 544 prohibits using issubclass()
721+ # with protocols that have non-method members.
722+ # See gh-113320 for why we compute this attribute here,
723+ # rather than in `_ProtocolMeta.__init__`
724+ cls .__non_callable_proto_members__ = set ()
725+ for attr in cls .__protocol_attrs__ :
726+ try :
727+ is_callable = callable (getattr (cls , attr , None ))
728+ except Exception as e :
729+ raise TypeError (
730+ f"Failed to determine whether protocol member { attr !r} "
731+ "is a method member"
732+ ) from e
733+ else :
734+ if not is_callable :
735+ cls .__non_callable_proto_members__ .add (attr )
736+
737+ return cls
738+
739+
681740# The "runtime" alias exists for backwards compatibility.
682- runtime = runtime_checkable = typing . runtime_checkable
741+ runtime = runtime_checkable
683742
684743
685744# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
0 commit comments