55import typing
66import collections .abc as collections_abc
77
8+ # After PEP 560, internal typing API was substantially reworked.
9+ # This is especially important for Protocol class which uses internal APIs
10+ # quite extensivelly.
11+ PEP_560 = sys .version_info [:3 ] >= (3 , 7 , 0 )
12+
813# These are used by Protocol implementation
914# We use internal typing helpers here, but this significantly reduces
1015# code duplication. (Also this is only until Protocol is in typing.)
11- from typing import GenericMeta , TypingMeta , Generic , Callable , TypeVar , Tuple
16+ from typing import Generic , Callable , TypeVar , Tuple
17+ if PEP_560 :
18+ GenericMeta = TypingMeta = type
19+ else :
20+ from typing import GenericMeta , TypingMeta
1221OLD_GENERICS = False
1322try :
1423 from typing import _type_vars , _next_in_mro , _type_check
@@ -729,7 +738,31 @@ def _next_in_mro(cls):
729738 next_in_mro = cls .__mro__ [i + 1 ]
730739 return next_in_mro
731740
732- if HAVE_PROTOCOLS :
741+
742+ def _get_protocol_attrs (cls ):
743+ attrs = set ()
744+ for base in cls .__mro__ [:- 1 ]: # without object
745+ if base .__name__ in ('Protocol' , 'Generic' ):
746+ continue
747+ annotations = getattr (base , '__annotations__' , {})
748+ for attr in list (base .__dict__ .keys ()) + list (annotations .keys ()):
749+ if (not attr .startswith ('_abc_' ) and attr not in (
750+ '__abstractmethods__' , '__annotations__' , '__weakref__' ,
751+ '_is_protocol' , '_is_runtime_protocol' , '__dict__' ,
752+ '__args__' , '__slots__' ,
753+ '__next_in_mro__' , '__parameters__' , '__origin__' ,
754+ '__orig_bases__' , '__extra__' , '__tree_hash__' ,
755+ '__doc__' , '__subclasshook__' , '__init__' , '__new__' ,
756+ '__module__' , '_MutableMapping__marker' , '_gorg' )):
757+ attrs .add (attr )
758+ return attrs
759+
760+
761+ def _is_callable_members_only (cls ):
762+ return all (callable (getattr (cls , attr , None )) for attr in _get_protocol_attrs (cls ))
763+
764+
765+ if HAVE_PROTOCOLS and not PEP_560 :
733766 class _ProtocolMeta (GenericMeta ):
734767 """Internal metaclass for Protocol.
735768
@@ -817,8 +850,6 @@ def __init__(cls, *args, **kwargs):
817850 base .__origin__ is Generic ):
818851 raise TypeError ('Protocols can only inherit from other'
819852 ' protocols, got %r' % base )
820- cls ._callable_members_only = all (callable (getattr (cls , attr , None ))
821- for attr in cls ._get_protocol_attrs ())
822853
823854 def _no_init (self , * args , ** kwargs ):
824855 if type (self )._is_protocol :
@@ -831,7 +862,7 @@ def _proto_hook(other):
831862 if not isinstance (other , type ):
832863 # Same error as for issubclass(1, int)
833864 raise TypeError ('issubclass() arg 1 must be a class' )
834- for attr in cls . _get_protocol_attrs ():
865+ for attr in _get_protocol_attrs (cls ):
835866 for base in other .__mro__ :
836867 if attr in base .__dict__ :
837868 if base .__dict__ [attr ] is None :
@@ -850,14 +881,14 @@ def __instancecheck__(self, instance):
850881 # We need this method for situations where attributes are
851882 # assigned in __init__.
852883 if ((not getattr (self , '_is_protocol' , False ) or
853- self . _callable_members_only ) and
884+ _is_callable_members_only ( self ) ) and
854885 issubclass (instance .__class__ , self )):
855886 return True
856887 if self ._is_protocol :
857888 if all (hasattr (instance , attr ) and
858889 (not callable (getattr (self , attr , None )) or
859890 getattr (instance , attr ) is not None )
860- for attr in self . _get_protocol_attrs ()):
891+ for attr in _get_protocol_attrs (self )):
861892 return True
862893 return super (GenericMeta , self ).__instancecheck__ (instance )
863894
@@ -874,32 +905,13 @@ def __subclasscheck__(self, cls):
874905 raise TypeError ("Instance and class checks can only be used with"
875906 " @runtime protocols" )
876907 if (self .__dict__ .get ('_is_runtime_protocol' , None ) and
877- not self . _callable_members_only ):
908+ not _is_callable_members_only ( self ) ):
878909 if sys ._getframe (1 ).f_globals ['__name__' ] in ['abc' , 'functools' , 'typing' ]:
879910 return super (GenericMeta , self ).__subclasscheck__ (cls )
880911 raise TypeError ("Protocols with non-method members"
881912 " don't support issubclass()" )
882913 return super (GenericMeta , self ).__subclasscheck__ (cls )
883914
884- def _get_protocol_attrs (self ):
885- attrs = set ()
886- for base in self .__mro__ [:- 1 ]: # without object
887- if base .__name__ in ('Protocol' , 'Generic' ):
888- continue
889- annotations = getattr (base , '__annotations__' , {})
890- for attr in list (base .__dict__ .keys ()) + list (annotations .keys ()):
891- if (not attr .startswith ('_abc_' ) and attr not in (
892- '__abstractmethods__' , '__annotations__' , '__weakref__' ,
893- '_is_protocol' , '_is_runtime_protocol' , '__dict__' ,
894- '__args__' , '__slots__' , '_get_protocol_attrs' ,
895- '__next_in_mro__' , '__parameters__' , '__origin__' ,
896- '__orig_bases__' , '__extra__' , '__tree_hash__' ,
897- '__doc__' , '__subclasshook__' , '__init__' , '__new__' ,
898- '__module__' , '_MutableMapping__marker' , '_gorg' ,
899- '_callable_members_only' )):
900- attrs .add (attr )
901- return attrs
902-
903915 if not OLD_GENERICS :
904916 @_tp_cache
905917 def __getitem__ (self , params ):
@@ -985,6 +997,189 @@ def __new__(cls, *args, **kwds):
985997 Protocol .__doc__ = Protocol .__doc__ .format (bases = "Protocol, Generic[T]" if
986998 OLD_GENERICS else "Protocol[T]" )
987999
1000+
1001+ elif PEP_560 :
1002+ from typing import _type_check , _GenericAlias , _collect_type_vars
1003+
1004+ class _ProtocolMeta (abc .ABCMeta ):
1005+ # This metaclass is a bit unfortunate and exists only because of the lack
1006+ # of __instancehook__.
1007+ def __instancecheck__ (cls , instance ):
1008+ # We need this method for situations where attributes are
1009+ # assigned in __init__.
1010+ if ((not getattr (cls , '_is_protocol' , False ) or
1011+ _is_callable_members_only (cls )) and
1012+ issubclass (instance .__class__ , cls )):
1013+ return True
1014+ if cls ._is_protocol :
1015+ if all (hasattr (instance , attr ) and
1016+ (not callable (getattr (cls , attr , None )) or
1017+ getattr (instance , attr ) is not None )
1018+ for attr in _get_protocol_attrs (cls )):
1019+ return True
1020+ return super ().__instancecheck__ (instance )
1021+
1022+
1023+ class Protocol (metaclass = _ProtocolMeta ):
1024+ # There is quite a lot of overlapping code with typing.Generic.
1025+ # Unfortunately it is hard to avoid this while these live in two different modules.
1026+ # The duplicated code will be removed when Protocol is moved to typing.
1027+ """Base class for protocol classes. Protocol classes are defined as::
1028+
1029+ class Proto(Protocol):
1030+ def meth(self) -> int:
1031+ ...
1032+
1033+ Such classes are primarily used with static type checkers that recognize
1034+ structural subtyping (static duck-typing), for example::
1035+
1036+ class C:
1037+ def meth(self) -> int:
1038+ return 0
1039+
1040+ def func(x: Proto) -> int:
1041+ return x.meth()
1042+
1043+ func(C()) # Passes static type check
1044+
1045+ See PEP 544 for details. Protocol classes decorated with
1046+ @typing_extensions.runtime act as simple-minded runtime protocol that checks
1047+ only the presence of given attributes, ignoring their type signatures.
1048+
1049+ Protocol classes can be generic, they are defined as::
1050+
1051+ class GenProto(Protocol[T]):
1052+ def meth(self) -> T:
1053+ ...
1054+ """
1055+ __slots__ = ()
1056+ _is_protocol = True
1057+
1058+ def __new__ (cls , * args , ** kwds ):
1059+ if cls is Protocol :
1060+ raise TypeError ("Type Protocol cannot be instantiated; "
1061+ "it can only be used as a base class" )
1062+ return super ().__new__ (cls )
1063+
1064+ @_tp_cache
1065+ def __class_getitem__ (cls , params ):
1066+ if not isinstance (params , tuple ):
1067+ params = (params ,)
1068+ if not params and cls is not Tuple :
1069+ raise TypeError (
1070+ "Parameter list to {}[...] cannot be empty" .format (cls .__qualname__ ))
1071+ msg = "Parameters to generic types must be types."
1072+ params = tuple (_type_check (p , msg ) for p in params )
1073+ if cls is Protocol :
1074+ # Generic can only be subscripted with unique type variables.
1075+ if not all (isinstance (p , TypeVar ) for p in params ):
1076+ i = 0
1077+ while isinstance (params [i ], TypeVar ):
1078+ i += 1
1079+ raise TypeError (
1080+ "Parameters to Protocol[...] must all be type variables."
1081+ " Parameter {} is {}" .format (i + 1 , params [i ]))
1082+ if len (set (params )) != len (params ):
1083+ raise TypeError (
1084+ "Parameters to Protocol[...] must all be unique" )
1085+ else :
1086+ # Subscripting a regular Generic subclass.
1087+ _check_generic (cls , params )
1088+ return _GenericAlias (cls , params )
1089+
1090+ def __init_subclass__ (cls , * args , ** kwargs ):
1091+ tvars = []
1092+ if '__orig_bases__' in cls .__dict__ :
1093+ error = Generic in cls .__orig_bases__
1094+ else :
1095+ error = Generic in cls .__bases__
1096+ if error :
1097+ raise TypeError ("Cannot inherit from plain Generic" )
1098+ if '__orig_bases__' in cls .__dict__ :
1099+ tvars = _collect_type_vars (cls .__orig_bases__ )
1100+ # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
1101+ # If found, tvars must be a subset of it.
1102+ # If not found, tvars is it.
1103+ # Also check for and reject plain Generic,
1104+ # and reject multiple Generic[...] and/or Protocol[...].
1105+ gvars = None
1106+ for base in cls .__orig_bases__ :
1107+ if (isinstance (base , _GenericAlias ) and
1108+ base .__origin__ in (Generic , Protocol )):
1109+ # for error messages
1110+ the_base = 'Generic' if base .__origin__ is Generic else 'Protocol'
1111+ if gvars is not None :
1112+ raise TypeError (
1113+ "Cannot inherit from Generic[...]"
1114+ " and/or Protocol[...] multiple types." )
1115+ gvars = base .__parameters__
1116+ if gvars is None :
1117+ gvars = tvars
1118+ else :
1119+ tvarset = set (tvars )
1120+ gvarset = set (gvars )
1121+ if not tvarset <= gvarset :
1122+ s_vars = ', ' .join (str (t ) for t in tvars if t not in gvarset )
1123+ s_args = ', ' .join (str (g ) for g in gvars )
1124+ raise TypeError ("Some type variables ({}) are"
1125+ " not listed in {}[{}]" .format (s_vars , the_base , s_args ))
1126+ tvars = gvars
1127+ cls .__parameters__ = tuple (tvars )
1128+
1129+ # Determine if this is a protocol or a concrete subclass.
1130+ if not cls .__dict__ .get ('_is_protocol' , None ):
1131+ cls ._is_protocol = any (b is Protocol for b in cls .__bases__ )
1132+
1133+ # Set (or override) the protocol subclass hook.
1134+ def _proto_hook (other ):
1135+ if not cls .__dict__ .get ('_is_protocol' , None ):
1136+ return NotImplemented
1137+ if not getattr (cls , '_is_runtime_protocol' , False ):
1138+ if sys ._getframe (2 ).f_globals ['__name__' ] in ['abc' , 'functools' ]:
1139+ return NotImplemented
1140+ raise TypeError ("Instance and class checks can only be used with"
1141+ " @runtime protocols" )
1142+ if not _is_callable_members_only (cls ):
1143+ if sys ._getframe (2 ).f_globals ['__name__' ] in ['abc' , 'functools' ]:
1144+ return NotImplemented
1145+ raise TypeError ("Protocols with non-method members"
1146+ " don't support issubclass()" )
1147+ if not isinstance (other , type ):
1148+ # Same error as for issubclass(1, int)
1149+ raise TypeError ('issubclass() arg 1 must be a class' )
1150+ for attr in _get_protocol_attrs (cls ):
1151+ for base in other .__mro__ :
1152+ if attr in base .__dict__ :
1153+ if base .__dict__ [attr ] is None :
1154+ return NotImplemented
1155+ break
1156+ if (attr in getattr (base , '__annotations__' , {}) and
1157+ isinstance (other , _ProtocolMeta ) and other ._is_protocol ):
1158+ break
1159+ else :
1160+ return NotImplemented
1161+ return True
1162+ if '__subclasshook__' not in cls .__dict__ :
1163+ cls .__subclasshook__ = _proto_hook
1164+
1165+ # We have nothing more to do for non-protocols.
1166+ if not cls ._is_protocol :
1167+ return
1168+
1169+ # Check consistency of bases.
1170+ for base in cls .__bases__ :
1171+ if not (base in (object , Generic , Callable ) or
1172+ isinstance (base , _ProtocolMeta ) and base ._is_protocol ):
1173+ raise TypeError ('Protocols can only inherit from other'
1174+ ' protocols, got %r' % base )
1175+
1176+ def _no_init (self , * args , ** kwargs ):
1177+ if type (self )._is_protocol :
1178+ raise TypeError ('Protocols cannot be instantiated' )
1179+ cls .__init__ = _no_init
1180+
1181+
1182+ if HAVE_PROTOCOLS :
9881183 def runtime (cls ):
9891184 """Mark a protocol class as a runtime protocol, so that it
9901185 can be used with isinstance() and issubclass(). Raise TypeError
0 commit comments