From ac7494fa900f76c7b3342bb6e0389e1543de0071 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 3 Jun 2015 18:44:48 -0700 Subject: [PATCH] Stop supporting isinstance(). The big exception is isinstance(f, Callable), which is now equivalent to isinstance(f, collections.abc.Callable). Also other enhanced ABCs that shadow ABCs from collections.abc still support isinstance() -- but not the concrete classes (List, Dict, Set, FrozenSet). I'm sad to see the original Callable.__instancecheck__ go, but Mark Shannon's right that isinstance() with a type argument is just poorly defined. Fixes #135. --- prototyping/test_typing.py | 245 +++++++------------------------------ prototyping/typing.py | 156 ++++------------------- 2 files changed, 71 insertions(+), 330 deletions(-) diff --git a/prototyping/test_typing.py b/prototyping/test_typing.py index c37e1130a..97404258a 100644 --- a/prototyping/test_typing.py +++ b/prototyping/test_typing.py @@ -41,11 +41,9 @@ class ManagingFounder(Manager, Founder): class AnyTests(TestCase): - def test_any_instance(self): - self.assertIsInstance(Employee(), Any) - self.assertIsInstance(42, Any) - self.assertIsInstance(None, Any) - self.assertIsInstance(object(), Any) + def test_any_instance_type_error(self): + with self.assertRaises(TypeError): + isinstance(42, Any) def test_any_subclass(self): self.assertTrue(issubclass(Employee, Any)) @@ -109,9 +107,6 @@ class TypeVarTests(TestCase): def test_basic_plain(self): T = TypeVar('T') - # Nothing is an instance if T. - with self.assertRaises(TypeError): - isinstance('', T) # Every class is a subclass of T. assert issubclass(int, T) assert issubclass(str, T) @@ -119,12 +114,16 @@ def test_basic_plain(self): assert T == T # T is a subclass of itself. assert issubclass(T, T) + # T is an instance of TypeVar + assert isinstance(T, TypeVar) + + def test_typevar_instance_type_error(self): + T = TypeVar('T') + with self.assertRaises(TypeError): + isinstance(42, T) def test_basic_constrained(self): A = TypeVar('A', str, bytes) - # Nothing is an instance of A. - with self.assertRaises(TypeError): - isinstance('', A) # Only str and bytes are subclasses of A. assert issubclass(str, A) assert issubclass(bytes, A) @@ -213,8 +212,6 @@ class UnionTests(TestCase): def test_basics(self): u = Union[int, float] self.assertNotEqual(u, Union) - self.assertIsInstance(42, u) - self.assertIsInstance(3.14, u) self.assertTrue(issubclass(int, u)) self.assertTrue(issubclass(float, u)) @@ -247,7 +244,6 @@ def test_unordered(self): def test_subclass(self): u = Union[int, Employee] - self.assertIsInstance(Manager(), u) self.assertTrue(issubclass(Manager, u)) def test_self_subclass(self): @@ -256,7 +252,6 @@ def test_self_subclass(self): def test_multiple_inheritance(self): u = Union[int, Employee] - self.assertIsInstance(ManagingFounder(), u) self.assertTrue(issubclass(ManagingFounder, u)) def test_single_class_disappears(self): @@ -309,9 +304,6 @@ def test_optional(self): o = Optional[int] u = Union[int, None] self.assertEqual(o, u) - self.assertIsInstance(42, o) - self.assertIsInstance(None, o) - self.assertNotIsInstance(3.14, o) def test_empty(self): with self.assertRaises(TypeError): @@ -321,11 +313,9 @@ def test_issubclass_union(self): assert issubclass(Union[int, str], Union) assert not issubclass(int, Union) - def test_isinstance_union(self): - # Nothing is an instance of bare Union. - assert not isinstance(42, Union) - assert not isinstance(int, Union) - assert not isinstance(Union[int, str], Union) + def test_union_instance_type_error(self): + with self.assertRaises(TypeError): + isinstance(42, Union[int, str]) class TypeVarUnionTests(TestCase): @@ -352,22 +342,11 @@ def test_var_union(self): TU = TypeVar('TU', Union[int, float], None) assert issubclass(int, TU) assert issubclass(float, TU) - with self.assertRaises(TypeError): - isinstance(42, TU) - with self.assertRaises(TypeError): - isinstance('', TU) class TupleTests(TestCase): def test_basics(self): - self.assertIsInstance((42, 3.14, ''), Tuple) - self.assertIsInstance((42, 3.14, ''), Tuple[int, float, str]) - self.assertIsInstance((42,), Tuple[int]) - self.assertNotIsInstance((3.14,), Tuple[int]) - self.assertNotIsInstance((42, 3.14), Tuple[int, float, str]) - self.assertNotIsInstance((42, 3.14, 100), Tuple[int, float, str]) - self.assertNotIsInstance((42, 3.14, 100), Tuple[int, float]) self.assertTrue(issubclass(Tuple[int, str], Tuple)) self.assertTrue(issubclass(Tuple[int, str], Tuple[int, str])) self.assertFalse(issubclass(int, Tuple)) @@ -382,14 +361,11 @@ class MyTuple(tuple): pass self.assertTrue(issubclass(MyTuple, Tuple)) - def test_tuple_ellipsis(self): - t = Tuple[int, ...] - assert isinstance((), t) - assert isinstance((1,), t) - assert isinstance((1, 2), t) - assert isinstance((1, 2, 3), t) - assert not isinstance((3.14,), t) - assert not isinstance((1, 2, 3.14,), t) + def test_tuple_instance_type_error(self): + with self.assertRaises(TypeError): + isinstance((0, 0), Tuple[int, int]) + with self.assertRaises(TypeError): + isinstance((0, 0), Tuple) def test_tuple_ellipsis_subclass(self): @@ -419,18 +395,6 @@ def test_errors(self): class CallableTests(TestCase): - def test_basics(self): - c = Callable[[int, float], str] - - def flub(a: int, b: float) -> str: - return str(a * b) - - def flob(a: int, b: int) -> str: - return str(a * b) - - self.assertIsInstance(flub, c) - self.assertNotIsInstance(flob, c) - def test_self_subclass(self): self.assertTrue(issubclass(Callable[[int], int], Callable)) self.assertFalse(issubclass(Callable, Callable[[int], int])) @@ -453,91 +417,6 @@ def test_eq_hash(self): self.assertNotEqual(Callable[[int], int], Callable[[], int]) self.assertNotEqual(Callable[[int], int], Callable) - def test_with_none(self): - c = Callable[[None], None] - - def flub(self: None) -> None: - pass - - def flab(self: Any) -> None: - pass - - def flob(self: None) -> Any: - pass - - self.assertIsInstance(flub, c) - self.assertIsInstance(flab, c) - self.assertNotIsInstance(flob, c) # Test contravariance. - - def test_with_subclasses(self): - c = Callable[[Employee, Manager], Employee] - - def flub(a: Employee, b: Employee) -> Manager: - return Manager() - - def flob(a: Manager, b: Manager) -> Employee: - return Employee() - - self.assertIsInstance(flub, c) - self.assertNotIsInstance(flob, c) - - def test_with_default_args(self): - c = Callable[[int], int] - - def flub(a: int, b: float = 3.14) -> int: - return a - - def flab(a: int, *, b: float = 3.14) -> int: - return a - - def flob(a: int = 42) -> int: - return a - - self.assertIsInstance(flub, c) - self.assertIsInstance(flab, c) - self.assertIsInstance(flob, c) - - def test_with_varargs(self): - c = Callable[[int], int] - - def flub(*args) -> int: - return 42 - - def flab(*args: int) -> int: - return 42 - - def flob(*args: float) -> int: - return 42 - - self.assertIsInstance(flub, c) - self.assertIsInstance(flab, c) - self.assertNotIsInstance(flob, c) - - def test_with_method(self): - - class C: - - def imethod(self, arg: int) -> int: - self.last_arg = arg - return arg + 1 - - @classmethod - def cmethod(cls, arg: int) -> int: - cls.last_cls_arg = arg - return arg + 1 - - @staticmethod - def smethod(arg: int) -> int: - return arg + 1 - - ct = Callable[[int], int] - self.assertIsInstance(C().imethod, ct) - self.assertIsInstance(C().cmethod, ct) - self.assertIsInstance(C.cmethod, ct) - self.assertIsInstance(C().smethod, ct) - self.assertIsInstance(C.smethod, ct) - self.assertIsInstance(C.imethod, Callable[[Any, int], int]) - def test_cannot_subclass(self): with self.assertRaises(TypeError): @@ -556,21 +435,21 @@ def test_cannot_instantiate(self): with self.assertRaises(TypeError): c() - def test_varargs(self): - ct = Callable[..., int] + def test_callable_instance_works(self): + f = lambda: None + assert isinstance(f, Callable) + assert not isinstance(None, Callable) - def foo(a, b) -> int: - return 42 - - def bar(a=42) -> int: - return a - - def baz(*, x, y, z) -> int: - return 100 - - self.assertIsInstance(foo, ct) - self.assertIsInstance(bar, ct) - self.assertIsInstance(baz, ct) + def test_callable_instance_type_error(self): + f = lambda: None + with self.assertRaises(TypeError): + assert isinstance(f, Callable[[], None]) + with self.assertRaises(TypeError): + assert isinstance(f, Callable[[], Any]) + with self.assertRaises(TypeError): + assert not isinstance(None, Callable[[], None]) + with self.assertRaises(TypeError): + assert not isinstance(None, Callable[[], Any]) def test_repr(self): ct0 = Callable[[], bool] @@ -659,6 +538,10 @@ def test_reversible(self): assert issubclass(list, typing.Reversible) assert not issubclass(int, typing.Reversible) + def test_protocol_instance_type_error(self): + with self.assertRaises(TypeError): + isinstance([], typing.Reversible) + class GenericTests(TestCase): @@ -889,6 +772,11 @@ def add_right(self, node: 'Node[T]' = None): right_hints = get_type_hints(t.add_right, globals(), locals()) assert right_hints['node'] == Optional[Node[T]] + def test_forwardref_instance_type_error(self): + fr = typing._ForwardRef('int') + with self.assertRaises(TypeError): + isinstance(42, fr) + def test_union_forward(self): def foo(a: Union['T']): @@ -1069,50 +957,17 @@ def test_bytestring(self): def test_list(self): assert issubclass(list, typing.List) - assert isinstance([], typing.List) - assert not isinstance((), typing.List) - t = typing.List[int] - assert isinstance([], t) - assert isinstance([42], t) - assert not isinstance([''], t) def test_set(self): assert issubclass(set, typing.Set) assert not issubclass(frozenset, typing.Set) - assert isinstance(set(), typing.Set) - assert not isinstance({}, typing.Set) - t = typing.Set[int] - assert isinstance(set(), t) - assert isinstance({42}, t) - assert not isinstance({''}, t) def test_frozenset(self): assert issubclass(frozenset, typing.FrozenSet) assert not issubclass(set, typing.FrozenSet) - assert isinstance(frozenset(), typing.FrozenSet) - assert not isinstance({}, typing.FrozenSet) - t = typing.FrozenSet[int] - assert isinstance(frozenset(), t) - assert isinstance(frozenset({42}), t) - assert not isinstance(frozenset({''}), t) - assert not isinstance({42}, t) - - def test_mapping_views(self): - # TODO: These tests are kind of lame. - assert isinstance({}.keys(), typing.KeysView) - assert isinstance({}.items(), typing.ItemsView) - assert isinstance({}.values(), typing.ValuesView) def test_dict(self): assert issubclass(dict, typing.Dict) - assert isinstance({}, typing.Dict) - assert not isinstance([], typing.Dict) - t = typing.Dict[int, str] - assert isinstance({}, t) - assert isinstance({42: ''}, t) - assert not isinstance({42: 42}, t) - assert not isinstance({'': 42}, t) - assert not isinstance({'': ''}, t) def test_no_list_instantiation(self): with self.assertRaises(TypeError): @@ -1191,8 +1046,6 @@ def foo(): yield 42 g = foo() assert issubclass(type(g), typing.Generator) - assert isinstance(g, typing.Generator) - assert not isinstance(foo, typing.Generator) assert issubclass(typing.Generator[Manager, Employee, Manager], typing.Generator[Employee, Manager, Employee]) assert not issubclass(typing.Generator[Manager, Manager, Manager], @@ -1228,12 +1081,6 @@ def __len__(self): assert len(MMB[str, str]()) == 0 assert len(MMB[KT, VT]()) == 0 - def test_recursive_dict(self): - D = typing.Dict[int, 'D'] # Uses a _ForwardRef - assert isinstance({}, D) # Easy - assert isinstance({0: {}}, D) # Touches _ForwardRef - assert isinstance({0: {0: {}}}, D) # Etc... - class NamedTupleTests(TestCase): @@ -1294,8 +1141,6 @@ class RETests(TestCase): def test_basics(self): pat = re.compile('[a-z]+', re.I) assert issubclass(pat.__class__, Pattern) - assert isinstance(pat, Pattern[str]) - assert not isinstance(pat, Pattern[bytes]) assert issubclass(type(pat), Pattern) assert issubclass(type(pat), Pattern[str]) @@ -1307,12 +1152,10 @@ def test_basics(self): assert issubclass(type(mat), Match[str]) p = Pattern[Union[str, bytes]] - assert isinstance(pat, p) assert issubclass(Pattern[str], Pattern) assert issubclass(Pattern[str], p) m = Match[Union[bytes, str]] - assert isinstance(mat, m) assert issubclass(Match[bytes], Match) assert issubclass(Match[bytes], m) @@ -1327,6 +1170,12 @@ def test_errors(self): with self.assertRaises(TypeError): # Too complicated? m[str] + with self.assertRaises(TypeError): + # We don't support isinstance(). + isinstance(42, Pattern) + with self.assertRaises(TypeError): + # We don't support isinstance(). + isinstance(42, Pattern[str]) def test_repr(self): assert repr(Pattern) == 'Pattern[~AnyStr]' diff --git a/prototyping/typing.py b/prototyping/typing.py index 38e07ad50..66bee917d 100644 --- a/prototyping/typing.py +++ b/prototyping/typing.py @@ -176,6 +176,9 @@ def _eval_type(self, globalns, localns): self.__forward_evaluated__ = True return self.__forward_value__ + def __instancecheck__(self, obj): + raise TypeError("Forward references cannot be used with isinstance().") + def __subclasscheck__(self, cls): if not self.__forward_evaluated__: globalns = self.__forward_frame__.f_globals @@ -186,16 +189,6 @@ def __subclasscheck__(self, cls): return False # Too early. return issubclass(cls, self.__forward_value__) - def __instancecheck__(self, obj): - if not self.__forward_evaluated__: - globalns = self.__forward_frame__.f_globals - localns = self.__forward_frame__.f_locals - try: - self._eval_type(globalns, localns) - except NameError: - return False # Too early. - return isinstance(obj, self.__forward_value__) - def __repr__(self): return '_ForwardRef(%r)' % (self.__forward_arg__,) @@ -259,8 +252,7 @@ def __getitem__(self, parameter): self.impl_type, self.type_checker) def __instancecheck__(self, obj): - return (isinstance(obj, self.impl_type) and - isinstance(self.type_checker(obj), self.type_var)) + raise TypeError("Type aliases cannot be used with isinstance().") def __subclasscheck__(self, cls): if cls is Any: @@ -332,8 +324,8 @@ def __new__(cls, name, bases, namespace, _root=False): self = super().__new__(cls, name, bases, namespace, _root=_root) return self - def __instancecheck__(self, instance): - return True + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") def __subclasscheck__(self, cls): if not isinstance(cls, type): @@ -548,9 +540,8 @@ def __eq__(self, other): def __hash__(self): return hash(self.__union_set_params__) - def __instancecheck__(self, instance): - return (self.__union_set_params__ is not None and - any(isinstance(instance, t) for t in self.__union_params__)) + def __instancecheck__(self, obj): + raise TypeError("Unions cannot be used with isinstance().") def __subclasscheck__(self, cls): if cls is Any: @@ -709,18 +700,8 @@ def __eq__(self, other): def __hash__(self): return hash(self.__tuple_params__) - def __instancecheck__(self, t): - if not isinstance(t, tuple): - return False - if self.__tuple_params__ is None: - return True - if self.__tuple_use_ellipsis__: - p = self.__tuple_params__[0] - return all(isinstance(x, p) for x in t) - else: - return (len(t) == len(self.__tuple_params__) and - all(isinstance(x, p) - for x, p in zip(t, self.__tuple_params__))) + def __instancecheck__(self, obj): + raise TypeError("Tuples cannot be used with isinstance().") def __subclasscheck__(self, cls): if cls is Any: @@ -826,57 +807,14 @@ def __eq__(self, other): def __hash__(self): return hash(self.__args__) ^ hash(self.__result__) - def __instancecheck__(self, instance): - if not callable(instance): - return False + def __instancecheck__(self, obj): + # For unparametrized Callable we allow this, because + # typing.Callable should be equivalent to + # collections.abc.Callable. if self.__args__ is None and self.__result__ is None: - return True - assert self.__args__ is not None - assert self.__result__ is not None - my_args, my_result = self.__args__, self.__result__ - import inspect # TODO: Avoid this import. - # Would it be better to use Signature objects? - try: - (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, - annotations) = inspect.getfullargspec(instance) - except TypeError: - return False # We can't find the signature. Give up. - msg = ("When testing isinstance(, Callable[...], " - "'s annotations must be types.") - if my_args is not Ellipsis: - if kwonlyargs and (not kwonlydefaults or - len(kwonlydefaults) < len(kwonlyargs)): - return False - if isinstance(instance, types.MethodType): - # For methods, getfullargspec() includes self/cls, - # but it's not part of the call signature, so drop it. - del args[0] - min_call_args = len(args) - if defaults: - min_call_args -= len(defaults) - if varargs: - max_call_args = 999999999 - if len(args) < len(my_args): - args += [varargs] * (len(my_args) - len(args)) - else: - max_call_args = len(args) - if not min_call_args <= len(my_args) <= max_call_args: - return False - for my_arg_type, name in zip(my_args, args): - if name in annotations: - annot_type = _type_check(annotations[name], msg) - else: - annot_type = Any - if not issubclass(my_arg_type, annot_type): - return False - # TODO: If mutable type, check invariance? - if 'return' in annotations: - annot_return_type = _type_check(annotations['return'], msg) - # Note contravariance here! - if not issubclass(annot_return_type, my_result): - return False - # Can't find anything wrong... - return True + return isinstance(obj, collections_abc.Callable) + else: + raise TypeError("Callable[] cannot be used with isinstance().") def __subclasscheck__(self, cls): if cls is Any: @@ -1073,13 +1011,6 @@ def __subclasscheck__(self, cls): return False return issubclass(cls, self.__extra__) - def __instancecheck__(self, obj): - if super().__instancecheck__(obj): - return True - if self.__extra__ is None: - return False - return isinstance(obj, self.__extra__) - class Generic(metaclass=GenericMeta): """Abstract base class for generic types. @@ -1234,6 +1165,9 @@ class _ProtocolMeta(GenericMeta): from Generic. """ + def __instancecheck__(self, obj): + raise TypeError("Protocols cannot be used with isinstance().") + def __subclasscheck__(self, cls): if not self._is_protocol: # No structural checks since this isn't a protocol. @@ -1399,19 +1333,7 @@ class ByteString(Sequence[int], extra=collections_abc.ByteString): ByteString.register(type(memoryview(b''))) -class _ListMeta(GenericMeta): - - def __instancecheck__(self, obj): - if not super().__instancecheck__(obj): - return False - itemtype = self.__parameters__[0] - for x in obj: - if not isinstance(x, itemtype): - return False - return True - - -class List(list, MutableSequence[T], metaclass=_ListMeta): +class List(list, MutableSequence[T]): def __new__(cls, *args, **kwds): if _geqv(cls, List): @@ -1420,19 +1342,7 @@ def __new__(cls, *args, **kwds): return list.__new__(cls, *args, **kwds) -class _SetMeta(GenericMeta): - - def __instancecheck__(self, obj): - if not super().__instancecheck__(obj): - return False - itemtype = self.__parameters__[0] - for x in obj: - if not isinstance(x, itemtype): - return False - return True - - -class Set(set, MutableSet[T], metaclass=_SetMeta): +class Set(set, MutableSet[T]): def __new__(cls, *args, **kwds): if _geqv(cls, Set): @@ -1441,7 +1351,7 @@ def __new__(cls, *args, **kwds): return set.__new__(cls, *args, **kwds) -class _FrozenSetMeta(_SetMeta): +class _FrozenSetMeta(GenericMeta): """This metaclass ensures set is not a subclass of FrozenSet. Without this metaclass, set would be considered a subclass of @@ -1454,11 +1364,6 @@ def __subclasscheck__(self, cls): return False return super().__subclasscheck__(cls) - def __instancecheck__(self, obj): - if issubclass(obj.__class__, Set): - return False - return super().__instancecheck__(obj) - class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta): @@ -1488,20 +1393,7 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): pass -class _DictMeta(GenericMeta): - - def __instancecheck__(self, obj): - if not super().__instancecheck__(obj): - return False - keytype, valuetype = self.__parameters__ - for key, value in obj.items(): - if not (isinstance(key, keytype) and - isinstance(value, valuetype)): - return False - return True - - -class Dict(dict, MutableMapping[KT, VT], metaclass=_DictMeta): +class Dict(dict, MutableMapping[KT, VT]): def __new__(cls, *args, **kwds): if _geqv(cls, Dict):