Skip to content

Commit

Permalink
More updates from upstream typing.py (3.5->3.6)
Browse files Browse the repository at this point in the history
  • Loading branch information
gvanrossum committed Oct 3, 2016
2 parents 0a6ef79 + b47c9d2 commit 7fe091d
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 64 deletions.
67 changes: 38 additions & 29 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,13 @@ def test_subclass_error(self):
def test_union_any(self):
u = Union[Any]
self.assertEqual(u, Any)
u = Union[int, Any]
self.assertEqual(u, Any)
u = Union[Any, int]
self.assertEqual(u, Any)
u1 = Union[int, Any]
u2 = Union[Any, int]
u3 = Union[Any, object]
self.assertEqual(u1, u2)
self.assertNotEqual(u1, Any)
self.assertNotEqual(u2, Any)
self.assertNotEqual(u3, Any)

def test_union_object(self):
u = Union[object]
Expand All @@ -215,12 +218,6 @@ def test_union_object(self):
u = Union[object, int]
self.assertEqual(u, object)

def test_union_any_object(self):
u = Union[object, Any]
self.assertEqual(u, Any)
u = Union[Any, object]
self.assertEqual(u, Any)

def test_unordered(self):
u1 = Union[int, float]
u2 = Union[float, int]
Expand Down Expand Up @@ -600,8 +597,8 @@ class MyMapping(MutableMapping[str, str]): pass
self.assertNotIsInstance({}, MyMapping)
self.assertNotIsSubclass(dict, MyMapping)

def test_multiple_abc_bases(self):
class MM1(MutableMapping[str, str], collections_abc.MutableMapping):
def test_abc_bases(self):
class MM(MutableMapping[str, str]):
def __getitem__(self, k):
return None
def __setitem__(self, k, v):
Expand All @@ -612,24 +609,20 @@ def __iter__(self):
return iter(())
def __len__(self):
return 0
class MM2(collections_abc.MutableMapping, MutableMapping[str, str]):
def __getitem__(self, k):
return None
def __setitem__(self, k, v):
pass
def __delitem__(self, k):
# this should just work
MM().update()
self.assertIsInstance(MM(), collections_abc.MutableMapping)
self.assertIsInstance(MM(), MutableMapping)
self.assertNotIsInstance(MM(), List)
self.assertNotIsInstance({}, MM)

def test_multiple_bases(self):
class MM1(MutableMapping[str, str], collections_abc.MutableMapping):
pass
with self.assertRaises(TypeError):
# consistent MRO not possible
class MM2(collections_abc.MutableMapping, MutableMapping[str, str]):
pass
def __iter__(self):
return iter(())
def __len__(self):
return 0
# these two should just work
MM1().update()
MM2().update()
self.assertIsInstance(MM1(), collections_abc.MutableMapping)
self.assertIsInstance(MM1(), MutableMapping)
self.assertIsInstance(MM2(), collections_abc.MutableMapping)
self.assertIsInstance(MM2(), MutableMapping)

def test_pickle(self):
global C # pickle wants to reference the class by name
Expand Down Expand Up @@ -1380,12 +1373,28 @@ class MMA(typing.MutableMapping):
MMA()

class MMC(MMA):
def __getitem__(self, k):
return None
def __setitem__(self, k, v):
pass
def __delitem__(self, k):
pass
def __iter__(self):
return iter(())
def __len__(self):
return 0

self.assertEqual(len(MMC()), 0)

class MMB(typing.MutableMapping[KT, VT]):
def __getitem__(self, k):
return None
def __setitem__(self, k, v):
pass
def __delitem__(self, k):
pass
def __iter__(self):
return iter(())
def __len__(self):
return 0

Expand Down
105 changes: 70 additions & 35 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ class _TypingBase(metaclass=TypingMeta, _root=True):

__slots__ = ()


def __init__(self, *args, **kwds):
pass

Expand All @@ -158,7 +157,7 @@ def __new__(cls, *args, **kwds):
isinstance(args[1], tuple)):
# Close enough.
raise TypeError("Cannot subclass %r" % cls)
return object.__new__(cls)
return super().__new__(cls)

# Things that are not classes also need these.
def _eval_type(self, globalns, localns):
Expand All @@ -177,7 +176,11 @@ def __call__(self, *args, **kwds):


class _FinalTypingBase(_TypingBase, _root=True):
"""Mix-in class to prevent instantiation."""
"""Mix-in class to prevent instantiation.
Prevents instantiation unless _root=True is given in class call.
It is used to create pseudo-singleton instances Any, Union, Tuple, etc.
"""

__slots__ = ()

Expand Down Expand Up @@ -273,7 +276,7 @@ def __init__(self, name, type_var, impl_type, type_checker):
assert isinstance(name, str), repr(name)
assert isinstance(impl_type, type), repr(impl_type)
assert not isinstance(impl_type, TypingMeta), repr(impl_type)
assert isinstance(type_var, (type, _TypingBase))
assert isinstance(type_var, (type, _TypingBase)), repr(type_var)
self.name = name
self.type_var = type_var
self.impl_type = impl_type
Expand Down Expand Up @@ -375,9 +378,13 @@ def _type_repr(obj):
class _Any(_FinalTypingBase, _root=True):
"""Special type indicating an unconstrained type.
- Any object is an instance of Any.
- Any class is a subclass of Any.
- As a special case, Any and object are subclasses of each other.
- Any is compatible with every type.
- Any assumed to have all methods.
- All values assumed to be instances of Any.
Note that all the above statements are true from the point of view of
static type checkers. At runtime, Any should not be used with instance
or class checks.
"""

__slots__ = ()
Expand Down Expand Up @@ -502,7 +509,7 @@ def inner(*args, **kwds):
try:
return cached(*args, **kwds)
except TypeError:
pass # Do not duplicate real errors.
pass # All real errors (not unhashable args) are raised below.
return func(*args, **kwds)
return inner

Expand Down Expand Up @@ -542,16 +549,10 @@ class Manager(Employee): pass
Union[Manager, int, Employee] == Union[int, Employee]
Union[Employee, Manager] == Employee
- Corollary: if Any is present it is the sole survivor, e.g.::
Union[int, Any] == Any
- Similar for object::
Union[int, object] == object
- To cut a tie: Union[object, Any] == Union[Any, object] == Any.
- You cannot subclass or instantiate a union.
- You cannot write Union[X][Y] (what would it mean?).
Expand Down Expand Up @@ -589,14 +590,11 @@ def __new__(cls, parameters=None, *args, _root=False):
assert not all_params, all_params
# Weed out subclasses.
# E.g. Union[int, Employee, Manager] == Union[int, Employee].
# If Any or object is present it will be the sole survivor.
# If both Any and object are present, Any wins.
# Never discard type variables, except against Any.
# If object is present it will be sole survivor among proper classes.
# Never discard type variables.
# (In particular, Union[str, AnyStr] != AnyStr.)
all_params = set(params)
for t1 in params:
if t1 is Any:
return Any
if not isinstance(t1, type):
continue
if any(isinstance(t2, type) and issubclass(t1, t2)
Expand Down Expand Up @@ -662,7 +660,7 @@ def __subclasscheck__(self, cls):
class _Optional(_FinalTypingBase, _root=True):
"""Optional type.
Optional[X] is equivalent to Union[X, type(None)].
Optional[X] is equivalent to Union[X, None].
"""

__slots__ = ()
Expand Down Expand Up @@ -894,11 +892,55 @@ def _next_in_mro(cls):
return next_in_mro


def _valid_for_check(cls):
if cls is Generic:
raise TypeError("Class %r cannot be used with class "
"or instance checks" % cls)
if (cls.__origin__ is not None and
sys._getframe(3).f_globals['__name__'] not in ['abc', 'functools']):
raise TypeError("Parameterized generics cannot be used with class "
"or instance checks")


def _make_subclasshook(cls):
"""Construct a __subclasshook__ callable that incorporates
the associated __extra__ class in subclass checks performed
against cls.
"""
if isinstance(cls.__extra__, abc.ABCMeta):
# The logic mirrors that of ABCMeta.__subclasscheck__.
# Registered classes need not be checked here because
# cls and its extra share the same _abc_registry.
def __extrahook__(subclass):
_valid_for_check(cls)
res = cls.__extra__.__subclasshook__(subclass)
if res is not NotImplemented:
return res
if cls.__extra__ in subclass.__mro__:
return True
for scls in cls.__extra__.__subclasses__():
if isinstance(scls, GenericMeta):
continue
if issubclass(subclass, scls):
return True
return NotImplemented
else:
# For non-ABC extras we'll just call issubclass().
def __extrahook__(subclass):
_valid_for_check(cls)
if cls.__extra__ and issubclass(subclass, cls.__extra__):
return True
return NotImplemented
return __extrahook__


class GenericMeta(TypingMeta, abc.ABCMeta):
"""Metaclass for generic types."""

def __new__(cls, name, bases, namespace,
tvars=None, args=None, origin=None, extra=None):
if extra is not None and type(extra) is abc.ABCMeta and extra not in bases:
bases = (extra,) + bases
self = super().__new__(cls, name, bases, namespace, _root=True)

if tvars is not None:
Expand Down Expand Up @@ -947,6 +989,13 @@ def __new__(cls, name, bases, namespace,
self.__extra__ = extra
# Speed hack (https://github.com/python/typing/issues/196).
self.__next_in_mro__ = _next_in_mro(self)

# This allows unparameterized generic collections to be used
# with issubclass() and isinstance() in the same way as their
# collections.abc counterparts (e.g., isinstance([], Iterable)).
self.__subclasshook__ = _make_subclasshook(self)
if isinstance(extra, abc.ABCMeta):
self._abc_registry = extra._abc_registry
return self

def _get_type_vars(self, tvars):
Expand Down Expand Up @@ -1032,21 +1081,7 @@ def __instancecheck__(self, instance):
# latter, we must extend __instancecheck__ too. For simplicity
# we just skip the cache check -- instance checks for generic
# classes are supposed to be rare anyways.
return self.__subclasscheck__(instance.__class__)

def __subclasscheck__(self, cls):
if self is Generic:
raise TypeError("Class %r cannot be used with class "
"or instance checks" % self)
if (self.__origin__ is not None and
sys._getframe(1).f_globals['__name__'] != 'abc'):
raise TypeError("Parameterized generics cannot be used with class "
"or instance checks")
if super().__subclasscheck__(cls):
return True
if self.__extra__ is not None:
return issubclass(cls, self.__extra__)
return False
return issubclass(instance.__class__, self)


# Prevent checks for Generic to crash when defining Generic.
Expand Down

0 comments on commit 7fe091d

Please sign in to comment.