mirror of
https://github.com/python/cpython.git
synced 2025-10-04 22:20:46 +00:00
various updates from upstream python/typing repo:
- Added typing.Counter and typing.ChainMap generics
- More flexible typing.NamedTuple
- Improved generic ABC caching
- More tests
- Bugfixes
- Other updates
* Add Misc/NEWS entry
* Add issue number
Contributed by Ivan Levkivskyi @ilevkivskyi
(cherry picked from commit b692dc8475
)
This commit is contained in:
parent
af456b241b
commit
9c5684e0d3
4 changed files with 338 additions and 58 deletions
138
Lib/typing.py
138
Lib/typing.py
|
@ -10,6 +10,12 @@ try:
|
|||
import collections.abc as collections_abc
|
||||
except ImportError:
|
||||
import collections as collections_abc # Fallback for PY3.2.
|
||||
try:
|
||||
from types import SlotWrapperType, MethodWrapperType, MethodDescriptorType
|
||||
except ImportError:
|
||||
SlotWrapperType = type(object.__init__)
|
||||
MethodWrapperType = type(object().__str__)
|
||||
MethodDescriptorType = type(str.join)
|
||||
|
||||
|
||||
# Please keep __all__ alphabetized within each category.
|
||||
|
@ -62,6 +68,7 @@ __all__ = [
|
|||
'SupportsRound',
|
||||
|
||||
# Concrete collection types.
|
||||
'Counter',
|
||||
'Deque',
|
||||
'Dict',
|
||||
'DefaultDict',
|
||||
|
@ -849,19 +856,6 @@ def _next_in_mro(cls):
|
|||
return next_in_mro
|
||||
|
||||
|
||||
def _valid_for_check(cls):
|
||||
"""An internal helper to prohibit isinstance([1], List[str]) etc."""
|
||||
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
|
||||
|
@ -872,7 +866,6 @@ def _make_subclasshook(cls):
|
|||
# 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
|
||||
|
@ -887,7 +880,6 @@ def _make_subclasshook(cls):
|
|||
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
|
||||
|
@ -974,6 +966,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
|||
# remove bare Generic from bases if there are other generic bases
|
||||
if any(isinstance(b, GenericMeta) and b is not Generic for b in bases):
|
||||
bases = tuple(b for b in bases if b is not Generic)
|
||||
namespace.update({'__origin__': origin, '__extra__': extra})
|
||||
self = super().__new__(cls, name, bases, namespace, _root=True)
|
||||
|
||||
self.__parameters__ = tvars
|
||||
|
@ -982,8 +975,6 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
|||
self.__args__ = tuple(... if a is _TypingEllipsis else
|
||||
() if a is _TypingEmpty else
|
||||
a for a in args) if args else None
|
||||
self.__origin__ = origin
|
||||
self.__extra__ = extra
|
||||
# Speed hack (https://github.com/python/typing/issues/196).
|
||||
self.__next_in_mro__ = _next_in_mro(self)
|
||||
# Preserve base classes on subclassing (__bases__ are type erased now).
|
||||
|
@ -994,20 +985,56 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
|||
# with issubclass() and isinstance() in the same way as their
|
||||
# collections.abc counterparts (e.g., isinstance([], Iterable)).
|
||||
if (
|
||||
# allow overriding
|
||||
'__subclasshook__' not in namespace and extra or
|
||||
hasattr(self.__subclasshook__, '__name__') and
|
||||
self.__subclasshook__.__name__ == '__extrahook__'
|
||||
# allow overriding
|
||||
getattr(self.__subclasshook__, '__name__', '') == '__extrahook__'
|
||||
):
|
||||
self.__subclasshook__ = _make_subclasshook(self)
|
||||
if isinstance(extra, abc.ABCMeta):
|
||||
self._abc_registry = extra._abc_registry
|
||||
self._abc_cache = extra._abc_cache
|
||||
elif origin is not None:
|
||||
self._abc_registry = origin._abc_registry
|
||||
self._abc_cache = origin._abc_cache
|
||||
|
||||
if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2.
|
||||
self.__qualname__ = origin.__qualname__
|
||||
self.__tree_hash__ = hash(self._subs_tree()) if origin else hash((self.__name__,))
|
||||
self.__tree_hash__ = (hash(self._subs_tree()) if origin else
|
||||
super(GenericMeta, self).__hash__())
|
||||
return self
|
||||
|
||||
# _abc_negative_cache and _abc_negative_cache_version
|
||||
# realised as descriptors, since GenClass[t1, t2, ...] always
|
||||
# share subclass info with GenClass.
|
||||
# This is an important memory optimization.
|
||||
@property
|
||||
def _abc_negative_cache(self):
|
||||
if isinstance(self.__extra__, abc.ABCMeta):
|
||||
return self.__extra__._abc_negative_cache
|
||||
return _gorg(self)._abc_generic_negative_cache
|
||||
|
||||
@_abc_negative_cache.setter
|
||||
def _abc_negative_cache(self, value):
|
||||
if self.__origin__ is None:
|
||||
if isinstance(self.__extra__, abc.ABCMeta):
|
||||
self.__extra__._abc_negative_cache = value
|
||||
else:
|
||||
self._abc_generic_negative_cache = value
|
||||
|
||||
@property
|
||||
def _abc_negative_cache_version(self):
|
||||
if isinstance(self.__extra__, abc.ABCMeta):
|
||||
return self.__extra__._abc_negative_cache_version
|
||||
return _gorg(self)._abc_generic_negative_cache_version
|
||||
|
||||
@_abc_negative_cache_version.setter
|
||||
def _abc_negative_cache_version(self, value):
|
||||
if self.__origin__ is None:
|
||||
if isinstance(self.__extra__, abc.ABCMeta):
|
||||
self.__extra__._abc_negative_cache_version = value
|
||||
else:
|
||||
self._abc_generic_negative_cache_version = value
|
||||
|
||||
def _get_type_vars(self, tvars):
|
||||
if self.__origin__ and self.__parameters__:
|
||||
_get_type_vars(self.__parameters__, tvars)
|
||||
|
@ -1095,8 +1122,10 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
|||
_check_generic(self, params)
|
||||
tvars = _type_vars(params)
|
||||
args = params
|
||||
|
||||
prepend = (self,) if self.__origin__ is None else ()
|
||||
return self.__class__(self.__name__,
|
||||
self.__bases__,
|
||||
prepend + self.__bases__,
|
||||
_no_slots_copy(self.__dict__),
|
||||
tvars=tvars,
|
||||
args=args,
|
||||
|
@ -1104,6 +1133,17 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
|
|||
extra=self.__extra__,
|
||||
orig_bases=self.__orig_bases__)
|
||||
|
||||
def __subclasscheck__(self, cls):
|
||||
if self.__origin__ is not None:
|
||||
if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']:
|
||||
raise TypeError("Parameterized generics cannot be used with class "
|
||||
"or instance checks")
|
||||
return False
|
||||
if self is Generic:
|
||||
raise TypeError("Class %r cannot be used with class "
|
||||
"or instance checks" % self)
|
||||
return super().__subclasscheck__(cls)
|
||||
|
||||
def __instancecheck__(self, instance):
|
||||
# Since we extend ABC.__subclasscheck__ and
|
||||
# ABC.__instancecheck__ inlines the cache checking done by the
|
||||
|
@ -1398,6 +1438,11 @@ def _get_defaults(func):
|
|||
return res
|
||||
|
||||
|
||||
_allowed_types = (types.FunctionType, types.BuiltinFunctionType,
|
||||
types.MethodType, types.ModuleType,
|
||||
SlotWrapperType, MethodWrapperType, MethodDescriptorType)
|
||||
|
||||
|
||||
def get_type_hints(obj, globalns=None, localns=None):
|
||||
"""Return type hints for an object.
|
||||
|
||||
|
@ -1452,12 +1497,7 @@ def get_type_hints(obj, globalns=None, localns=None):
|
|||
hints = getattr(obj, '__annotations__', None)
|
||||
if hints is None:
|
||||
# Return empty annotations for something that _could_ have them.
|
||||
if (
|
||||
isinstance(obj, types.FunctionType) or
|
||||
isinstance(obj, types.BuiltinFunctionType) or
|
||||
isinstance(obj, types.MethodType) or
|
||||
isinstance(obj, types.ModuleType)
|
||||
):
|
||||
if isinstance(obj, _allowed_types):
|
||||
return {}
|
||||
else:
|
||||
raise TypeError('{!r} is not a module, class, method, '
|
||||
|
@ -1824,8 +1864,7 @@ class Deque(collections.deque, MutableSequence[T], extra=collections.deque):
|
|||
|
||||
def __new__(cls, *args, **kwds):
|
||||
if _geqv(cls, Deque):
|
||||
raise TypeError("Type Deque cannot be instantiated; "
|
||||
"use deque() instead")
|
||||
return collections.deque(*args, **kwds)
|
||||
return _generic_new(collections.deque, cls, *args, **kwds)
|
||||
|
||||
|
||||
|
@ -1894,11 +1933,35 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT],
|
|||
|
||||
def __new__(cls, *args, **kwds):
|
||||
if _geqv(cls, DefaultDict):
|
||||
raise TypeError("Type DefaultDict cannot be instantiated; "
|
||||
"use collections.defaultdict() instead")
|
||||
return collections.defaultdict(*args, **kwds)
|
||||
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
||||
|
||||
|
||||
class Counter(collections.Counter, Dict[T, int], extra=collections.Counter):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, *args, **kwds):
|
||||
if _geqv(cls, Counter):
|
||||
return collections.Counter(*args, **kwds)
|
||||
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||
|
||||
|
||||
if hasattr(collections, 'ChainMap'):
|
||||
# ChainMap only exists in 3.3+
|
||||
__all__.append('ChainMap')
|
||||
|
||||
class ChainMap(collections.ChainMap, MutableMapping[KT, VT],
|
||||
extra=collections.ChainMap):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, *args, **kwds):
|
||||
if _geqv(cls, ChainMap):
|
||||
return collections.ChainMap(*args, **kwds)
|
||||
return _generic_new(collections.ChainMap, cls, *args, **kwds)
|
||||
|
||||
|
||||
# Determine what base class to use for Generator.
|
||||
if hasattr(collections_abc, 'Generator'):
|
||||
# Sufficiently recent versions of 3.5 have a Generator ABC.
|
||||
|
@ -1975,6 +2038,13 @@ def _make_nmtuple(name, types):
|
|||
|
||||
_PY36 = sys.version_info[:2] >= (3, 6)
|
||||
|
||||
# attributes prohibited to set in NamedTuple class syntax
|
||||
_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__',
|
||||
'_fields', '_field_defaults', '_field_types',
|
||||
'_make', '_replace', '_asdict')
|
||||
|
||||
_special = ('__module__', '__name__', '__qualname__', '__annotations__')
|
||||
|
||||
|
||||
class NamedTupleMeta(type):
|
||||
|
||||
|
@ -2002,7 +2072,9 @@ class NamedTupleMeta(type):
|
|||
nm_tpl._field_defaults = defaults_dict
|
||||
# update from user namespace without overriding special namedtuple attributes
|
||||
for key in ns:
|
||||
if not hasattr(nm_tpl, key):
|
||||
if key in _prohibited:
|
||||
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
|
||||
elif key not in _special and key not in nm_tpl._fields:
|
||||
setattr(nm_tpl, key, ns[key])
|
||||
return nm_tpl
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue