8000 Kill __subclasscheck__ by ilevkivskyi · Pull Request #283 · python/typing · GitHub
[go: up one dir, main page]

Skip to content

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

Merged
merged 28 commits into from
Sep 27, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7d73596
Started #136
ilevkivskyi Sep 18, 2016
20de824
Started #136
ilevkivskyi Sep 18, 2016
8e6f0a6
More work on #136
ilevkivskyi Sep 18, 2016
bcde4c6
Finish first round of #136
ilevkivskyi Sep 18, 2016
9987bb2
Shorter error messages
ilevkivskyi Sep 18, 2016
90d72da
Added lru_cache for Generic[]
ilevkivskyi Sep 18, 2016
603ff6d
Flexible cache for Generic[]
ilevkivskyi Sep 18, 2016
fa4a681
Skip GenericMeta in FrozenSetMeta MRO
ilevkivskyi Sep 18, 2016
cc99972
Revert "Skip GenericMeta in FrozenSetMeta MRO"
ilevkivskyi Sep 18, 2016
b587dc0
Remove redundant metaclass for frozenset
ilevkivskyi Sep 18, 2016
e25b303
Some formatting
ilevkivskyi Sep 18, 2016
3ccb7a9
Fix async/await tests
ilevkivskyi Sep 18, 2016
0606059
Fixed ContextManger test
ilevkivskyi Sep 18, 2016
8b909d6
Be consistent about _TypeAlias
ilevkivskyi Sep 18, 2016
554f2ed
Removed debugging print()
ilevkivskyi Sep 18, 2016
11189b0
Simplified code
ilevkivskyi Sep 19, 2016
4c91c3b
Increased cache size to 50; start backporting changes to Python 2
ilevkivskyi Sep 22, 2016
1920009
Backport Union and Optional to Python 2
ilevkivskyi Sep 23, 2016
f003eca
Finished backport to Python 2; some polishing is needed
ilevkivskyi Sep 23, 2016
e130c77
Added many unit tests for bugs fixed by PR
ilevkivskyi Sep 24, 2016
fe2d3b7
Updated docstrings and other minor things
ilevkivskyi Sep 24, 2016
e4cebff
Optimized caching + first part of response to comments
ilevkivskyi Sep 25, 2016
9b443cb
Second part of response to comments. Need to port all comments etc. t…
ilevkivskyi Sep 26, 2016
bd160a1
Start backporting comments to Python2; improve caching
ilevkivskyi Sep 26, 2016
80ce513
Get rid of frozensetmeta, it causes random failures
ilevkivskyi Sep 26, 2016
c4f11e8
Backported everything to Python 2
Sep 26, 2016
230a500
Do not duplicate real errors while caching
ilevkivskyi Sep 26, 2016
e8c85b2
Merge remote-tracking branch 'origin/master'
ilevkivskyi Sep 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Optimized caching + first part of response to comments
  • Loading branch information
ilevkivskyi committed Sep 25, 2016
commit e4cebff3dfb7c2cd8c70396ca24a033a464183a5
2 changes: 1 addition & 1 deletion src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ class A(typing.Match):
pass

self.assertEqual(str(ex.exception),
"A type alias cannot be subclassed")
"Cannot subclass typing._TypeAlias")


class AllTests(BaseTestCase):
Expand Down
99 changes: 64 additions & 35 deletions src/typing.py
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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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):
Copy link
Member
@gvanrossum gvanrossum Sep 25, 2016

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 for Final below (and probably also TypingMeta above).

"""Indicator of special typing constructs."""

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a __slots__ here too, otherwise the __slots__ in the subclass is pointless. Also all the final classes (and probably also those inheriting from TypingBase) should have their own __slots__.

(I guess it's a little-known fact that __slots__ only suppresses the __dict__ when used for every class in the MRO...)

def __new__(cls, *args, **kwds):
"""Constructor.

This only exists to give a better error message in case
Copy link
Member

Choose a reason for hiding this comment

The 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)
Copy link
Member
@gvanrossum gvanrossum Sep 26, 2016

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand All @@ -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):
Copy link
Member
@gvanrossum gvanrossum Sep 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to _FinalTypingBase?

"""Mix-in class to prevent instantiation."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd update the docstring to hint that it prevents instantiation unless _root=True is given.


__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):
Expand Down Expand Up @@ -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().")

Expand All @@ -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.

Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -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,)
Expand Down
0