@@ -473,7 +473,7 @@ def clear_overloads():
473
473
"_is_runtime_protocol" , "__dict__" , "__slots__" , "__parameters__" ,
474
474
"__orig_bases__" , "__module__" , "_MutableMapping__marker" , "__doc__" ,
475
475
"__subclasshook__" , "__orig_class__" , "__init__" , "__new__" ,
476
- "__protocol_attrs__" , "__callable_proto_members_only__ " ,
476
+ "__protocol_attrs__" , "__non_callable_proto_members__ " ,
477
477
"__match_args__" ,
478
478
}
479
479
@@ -521,6 +521,22 @@ def _no_init(self, *args, **kwargs):
521
521
if type (self )._is_protocol :
522
522
raise TypeError ('Protocols cannot be instantiated' )
523
523
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
+
524
540
# Inheriting from typing._ProtocolMeta isn't actually desirable,
525
541
# but is necessary to allow typing.Protocol and typing_extensions.Protocol
526
542
# to mix without getting TypeErrors about "metaclass conflict"
@@ -551,11 +567,6 @@ def __init__(cls, *args, **kwargs):
551
567
abc .ABCMeta .__init__ (cls , * args , ** kwargs )
552
568
if getattr (cls , "_is_protocol" , False ):
553
569
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
- )
559
570
560
571
def __subclasscheck__ (cls , other ):
561
572
if cls is Protocol :
@@ -564,26 +575,23 @@ def __subclasscheck__(cls, other):
564
575
getattr (cls , '_is_protocol' , False )
565
576
and not _allow_reckless_class_checks ()
566
577
):
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
+ )
570
584
if (
571
- not cls .__callable_proto_members_only__
585
+ # this attribute is set by @runtime_checkable:
586
+ cls .__non_callable_proto_members__
572
587
and cls .__dict__ .get ("__subclasshook__" ) is _proto_hook
573
588
):
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__ )
578
591
raise TypeError (
579
592
"Protocols with non-method members don't support issubclass()."
580
593
f" Non-method members: { str (non_method_attrs )[1 :- 1 ]} ."
581
594
)
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
- )
587
595
return abc .ABCMeta .__subclasscheck__ (cls , other )
588
596
589
597
def __instancecheck__ (cls , instance ):
@@ -610,7 +618,8 @@ def __instancecheck__(cls, instance):
610
618
val = inspect .getattr_static (instance , attr )
611
619
except AttributeError :
612
620
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__ :
614
623
break
615
624
else :
616
625
return True
@@ -678,8 +687,58 @@ def __init_subclass__(cls, *args, **kwargs):
678
687
cls .__init__ = _no_init
679
688
680
689
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
+
681
740
# The "runtime" alias exists for backwards compatibility.
682
- runtime = runtime_checkable = typing . runtime_checkable
741
+ runtime = runtime_checkable
683
742
684
743
685
744
# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
0 commit comments