-
Notifications
You must be signed in to change notification settings - Fork 262
Kill __subclasscheck__ #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
7d73596
20de824
8e6f0a6
bcde4c6
9987bb2
90d72da
603ff6d
fa4a681
cc99972
b587dc0
e25b303
3ccb7a9
0606059
8b909d6
554f2ed
11189b0
4c91c3b
1920009
f003eca
e130c77
fe2d3b7
e4cebff
9b443cb
bd160a1
80ce513
c4f11e8
230a500
e8c85b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import abc | ||
from abc import abstractmethod, abstractproperty | ||
from functools import lru_cache, wraps | ||
import collections | ||
import contextlib | ||
import functools | ||
|
@@ -90,6 +89,13 @@ def _qualname(x): | |
return x.__name__ | ||
|
||
|
||
def _trim_name(nm): | ||
if nm.startswith('_') and nm not in ('_TypeAlias', | ||
'_ForwardRef', '_TypingBase', '_FinalTypingBase'): | ||
nm = nm[1:] | ||
return nm | ||
|
||
|
||
class TypingMeta(type): | ||
"""Metaclass for every type defined below. | ||
|
||
|
@@ -128,12 +134,26 @@ def _get_type_vars(self, tvars): | |
pass | ||
|
||
def __repr__(self): | ||
return '%s.%s' % (self.__module__, _qualname(self)) | ||
qname = _trim_name(_qualname(self)) | ||
return '%s.%s' % (self.__module__, qname) | ||
|
||
|
||
class TypingBase(metaclass=TypingMeta, _root=True): | ||
"""Indicator of special typing constructs.""" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a (I guess it's a little-known fact that |
||
def __new__(cls, *args, **kwds): | ||
"""Constructor. | ||
|
||
This only exists to give a better error message in case | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe getting a better error isn't enough reason to insert an extra function call? Or does the cache make this moot? |
||
someone tries to subclass a special typing object (not a good idea). | ||
""" | ||
if (len(args) == 3 and | ||
isinstance(args[0], str) and | ||
isinstance(args[1], tuple)): | ||
# Close enough. | ||
raise TypeError("Cannot subclass %r" % cls) | ||
return object.__new__(cls) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't this use super()? |
||
|
||
# things that are not classes also need these | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you capitalize sentences and add a period at the end? |
||
def _eval_type(self, globalns, localns): | ||
return self | ||
|
@@ -143,18 +163,23 @@ def _get_type_vars(self, tvars): | |
|
||
def __repr__(self): | ||
cls = type(self) | ||
return '{}.{}'.format(cls.__module__, cls.__name__[1:]) | ||
qname = _trim_name(_qualname(cls)) | ||
return '%s.%s' % (cls.__module__, qname) | ||
|
||
def __call__(self, *args, **kwds): | ||
raise TypeError("Cannot instantiate %r" % type(self)) | ||
|
||
|
||
class Final(TypingBase, _root=True): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe rename to |
||
"""Mix-in class to prevent instantiation.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd update the docstring to hint that it prevents instantiation unless |
||
|
||
__slots__ = () | ||
|
||
def __new__(self, *args, _root=False, **kwds): | ||
if _root: | ||
return object.__new__(self) | ||
raise TypeError("Cannot instantiate or subclass %r" % self) | ||
def __new__(cls, *args, _root=False, **kwds): | ||
self = super().__new__(cls, *args, **kwds) | ||
if _root is True: | ||
return self | ||
raise TypeError("Cannot instantiate %r" % cls) | ||
|
||
|
||
class _ForwardRef(TypingBase, _root=True): | ||
|
@@ -193,6 +218,14 @@ def _eval_type(self, globalns, localns): | |
self.__forward_evaluated__ = True | ||
return self.__forward_value__ | ||
|
||
def __eq__(self, other): | ||
if not isinstance(other, _ForwardRef): | ||
return NotImplemented | ||
return self.__forward_arg__ == other.__forward_arg__ | ||
|
||
def __hash__(self): | ||
return hash(self.__forward_arg__) | ||
|
||
def __instancecheck__(self, obj): | ||
raise TypeError("Forward references cannot be used with isinstance().") | ||
|
||
|
@@ -214,19 +247,6 @@ class _TypeAlias(TypingBase, _root=True): | |
|
||
__slots__ = ('name', 'type_var', 'impl_type', 'type_checker') | ||
|
||
def __new__(cls, *args, **kwds): | ||
"""Constructor. | ||
|
||
This only exists to give a better error message in case | ||
someone tries to subclass a type alias (not a good idea). | ||
""" | ||
if (len(args) == 3 and | ||
isinstance(args[0], str) and | ||
isinstance(args[1], tuple)): | ||
# Close enough. | ||
raise TypeError("A type alias cannot be subclassed") | ||
return object.__new__(cls) | ||
|
||
def __init__(self, name, type_var, impl_type, type_checker): | ||
"""Initializer. | ||
|
||
|
@@ -261,6 +281,14 @@ def __getitem__(self, parameter): | |
return self.__class__(self.name, parameter, | ||
self.impl_type, self.type_checker) | ||
|
||
def __eq__(self, other): | ||
if not isinstance(other, _TypeAlias): | ||
return NotImplemented | ||
return self.name == other.name and self.type_var == other.type_var | ||
|
||
def __hash__(self): | ||
return hash((self.name, self.type_var)) | ||
|
||
def __instancecheck__(self, obj): | ||
if not isinstance(self.type_var, TypeVar): | ||
raise TypeError("Parameterized type aliases cannot be used " | ||
|
@@ -447,6 +475,17 @@ def __subclasscheck__(self, cls): | |
AnyStr = TypeVar('AnyStr', bytes, str) | ||
|
||
|
||
def _tp_cache(func): | ||
cached = functools.lru_cache()(func) | ||
@functools.wraps(func) | ||
def inner(*args, **kwargs): | ||
if any(not isinstance(arg, collections_abc.Hashable) for arg in args): | ||
return func(*args, **kwargs) | ||
else: | ||
return cached(*args, **kwargs) | ||
return inner | ||
|
||
|
||
class _Union(Final, _root=True): | ||
"""Union type; Union[X, Y] means either X or Y. | ||
|
||
|
@@ -499,8 +538,8 @@ class Manager(Employee): pass | |
- You can use Optional[X] as a shorthand for Union[X, None]. | ||
""" | ||
|
||
def __new__(cls, parameters=None, _root=False): | ||
self = super().__new__(cls, _root=_root) | ||
def __new__(cls, parameters=None, *args, _root=False): | ||
self = super().__new__(cls, parameters, *args, _root=_root) | ||
if parameters is None: | ||
self.__union_params__ = None | ||
self.__union_set_params__ = None | ||
|
@@ -568,6 +607,7 @@ def __repr__(self): | |
for t in self.__union_params__)) | ||
return r | ||
|
||
@_tp_cache | ||
def __getitem__(self, parameters): | ||
if self.__union_params__ is not None: | ||
raise TypeError( | ||
|
@@ -674,7 +714,7 @@ def __eq__(self, other): | |
self.__tuple_use_ellipsis__ == other.__tuple_use_ellipsis__) | ||
|
||
def __hash__(self): | ||
return hash(self.__tuple_params__) | ||
return hash((self.__tuple_params__, self.__tuple_use_ellipsis__)) | ||
|
||
def __instancecheck__(self, obj): | ||
if self.__tuple_params__ == None: | ||
|
@@ -823,17 +863,6 @@ def _next_in_mro(cls): | |
return next_in_mro | ||
|
||
|
||
def tp_cache(func): | ||
cached = lru_cache(maxsize=50)(func) | ||
wraps(func) | ||
def inner(*args, **kwargs): | ||
if any(not isinstance(arg, collections_abc.Hashable) for arg in args): | ||
return func(*args, **kwargs) | ||
else: | ||
return cached(*args, **kwargs) | ||
return inner | ||
|
||
|
||
class GenericMeta(TypingMeta, abc.ABCMeta): | ||
"""Metaclass for generic types.""" | ||
|
||
|
@@ -919,7 +948,7 @@ def __eq__(self, other): | |
def __hash__(self): | ||
return hash((self.__name__, self.__parameters__)) | ||
|
||
@tp_cache | ||
@_tp_cache | ||
def __getitem__(self, params): | ||
if not isinstance(params, tuple): | ||
params = (params,) | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this class name should start with
_
, and ditto forFinal
below (and probably alsoTypingMeta
above).