5
5
import typing
6
6
import collections .abc as collections_abc
7
7
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
+
8
13
# These are used by Protocol implementation
9
14
# We use internal typing helpers here, but this significantly reduces
10
15
# 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
12
21
OLD_GENERICS = False
13
22
try :
14
23
from typing import _type_vars , _next_in_mro , _type_check
@@ -729,7 +738,31 @@ def _next_in_mro(cls):
729
738
next_in_mro = cls .__mro__ [i + 1 ]
730
739
return next_in_mro
731
740
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 :
733
766
class _ProtocolMeta (GenericMeta ):
734
767
"""Internal metaclass for Protocol.
735
768
@@ -817,8 +850,6 @@ def __init__(cls, *args, **kwargs):
817
850
base .__origin__ is Generic ):
818
851
raise TypeError ('Protocols can only inherit from other'
819
852
' protocols, got %r' % base )
820
- cls ._callable_members_only = all (callable (getattr (cls , attr , None ))
821
- for attr in cls ._get_protocol_attrs ())
822
853
823
854
def _no_init (self , * args , ** kwargs ):
824
855
if type (self )._is_protocol :
@@ -831,7 +862,7 @@ def _proto_hook(other):
831
862
if not isinstance (other , type ):
832
863
# Same error as for issubclass(1, int)
833
864
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 ):
835
866
for base in other .__mro__ :
836
867
if attr in base .__dict__ :
837
868
if base .__dict__ [attr ] is None :
@@ -850,14 +881,14 @@ def __instancecheck__(self, instance):
850
881
# We need this method for situations where attributes are
851
882
# assigned in __init__.
852
883
if ((not getattr (self , '_is_protocol' , False ) or
853
- self . _callable_members_only ) and
884
+ _is_callable_members_only ( self ) ) and
854
885
issubclass (instance .__class__ , self )):
855
886
return True
856
887
if self ._is_protocol :
857
888
if all (hasattr (instance , attr ) and
858
889
(not callable (getattr (self , attr , None )) or
859
890
getattr (instance , attr ) is not None )
860
- for attr in self . _get_protocol_attrs ()):
891
+ for attr in _get_protocol_attrs (self )):
861
892
return True
862
893
return super (GenericMeta , self ).__instancecheck__ (instance )
863
894
@@ -874,32 +905,13 @@ def __subclasscheck__(self, cls):
874
905
raise TypeError ("Instance and class checks can only be used with"
875
906
" @runtime protocols" )
876
907
if (self .__dict__ .get ('_is_runtime_protocol' , None ) and
877
- not self . _callable_members_only ):
908
+ not _is_callable_members_only ( self ) ):
878
909
if sys ._getframe (1 ).f_globals ['__name__' ] in ['abc' , 'functools' , 'typing' ]:
879
910
return super (GenericMeta , self ).__subclasscheck__ (cls )
880
911
raise TypeError ("Protocols with non-method members"
881
912
" don't support issubclass()" )
882
913
return super (GenericMeta , self ).__subclasscheck__ (cls )
883
914
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
-
903
915
if not OLD_GENERICS :
904
916
@_tp_cache
905
917
def __getitem__ (self , params ):
@@ -985,6 +997,189 @@ def __new__(cls, *args, **kwds):
985
997
Protocol .__doc__ = Protocol .__doc__ .format (bases = "Protocol, Generic[T]" if
986
998
OLD_GENERICS else "Protocol[T]" )
987
999
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 :
988
1183
def runtime (cls ):
989
1184
"""Mark a protocol class as a runtime protocol, so that it
990
1185
can be used with isinstance() and issubclass(). Raise TypeError
0 commit comments