gh-89828: Do not relay the __class__ attribute in GenericAlias (#93754)

list[int].__class__ returned type, and isinstance(list[int], type)
returned True. It caused numerous problems in code that checks
isinstance(x, type).
This commit is contained in:
Serhiy Storchaka 2022-06-18 11:34:57 +03:00 committed by GitHub
parent 084023ccbe
commit f9433fff47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 18 additions and 20 deletions

View File

@ -230,7 +230,7 @@ class InitVar:
self.type = type self.type = type
def __repr__(self): def __repr__(self):
if isinstance(self.type, type) and not isinstance(self.type, GenericAlias): if isinstance(self.type, type):
type_name = self.type.__name__ type_name = self.type.__name__
else: else:
# typing objects, e.g. List[int] # typing objects, e.g. List[int]
@ -1248,7 +1248,7 @@ def _is_dataclass_instance(obj):
def is_dataclass(obj): def is_dataclass(obj):
"""Returns True if obj is a dataclass or an instance of a """Returns True if obj is a dataclass or an instance of a
dataclass.""" dataclass."""
cls = obj if isinstance(obj, type) and not isinstance(obj, GenericAlias) else type(obj) cls = obj if isinstance(obj, type) else type(obj)
return hasattr(cls, _FIELDS) return hasattr(cls, _FIELDS)

View File

@ -843,12 +843,11 @@ def singledispatch(func):
return get_origin(cls) in {Union, types.UnionType} return get_origin(cls) in {Union, types.UnionType}
def _is_valid_dispatch_type(cls): def _is_valid_dispatch_type(cls):
if isinstance(cls, type) and not isinstance(cls, GenericAlias): if isinstance(cls, type):
return True return True
from typing import get_args from typing import get_args
return (_is_union_type(cls) and return (_is_union_type(cls) and
all(isinstance(arg, type) and not isinstance(arg, GenericAlias) all(isinstance(arg, type) for arg in get_args(cls)))
for arg in get_args(cls)))
def register(cls, func=None): def register(cls, func=None):
"""generic_func.register(cls, func) -> func """generic_func.register(cls, func) -> func

View File

@ -70,7 +70,6 @@ import sys
import sysconfig import sysconfig
import time import time
import tokenize import tokenize
import types
import urllib.parse import urllib.parse
import warnings import warnings
from collections import deque from collections import deque
@ -92,16 +91,13 @@ def pathdirs():
normdirs.append(normdir) normdirs.append(normdir)
return dirs return dirs
def _isclass(object):
return inspect.isclass(object) and not isinstance(object, types.GenericAlias)
def _findclass(func): def _findclass(func):
cls = sys.modules.get(func.__module__) cls = sys.modules.get(func.__module__)
if cls is None: if cls is None:
return None return None
for name in func.__qualname__.split('.')[:-1]: for name in func.__qualname__.split('.')[:-1]:
cls = getattr(cls, name) cls = getattr(cls, name)
if not _isclass(cls): if not inspect.isclass(cls):
return None return None
return cls return cls
@ -109,7 +105,7 @@ def _finddoc(obj):
if inspect.ismethod(obj): if inspect.ismethod(obj):
name = obj.__func__.__name__ name = obj.__func__.__name__
self = obj.__self__ self = obj.__self__
if (_isclass(self) and if (inspect.isclass(self) and
getattr(getattr(self, name, None), '__func__') is obj.__func__): getattr(getattr(self, name, None), '__func__') is obj.__func__):
# classmethod # classmethod
cls = self cls = self
@ -123,7 +119,7 @@ def _finddoc(obj):
elif inspect.isbuiltin(obj): elif inspect.isbuiltin(obj):
name = obj.__name__ name = obj.__name__
self = obj.__self__ self = obj.__self__
if (_isclass(self) and if (inspect.isclass(self) and
self.__qualname__ + '.' + name == obj.__qualname__): self.__qualname__ + '.' + name == obj.__qualname__):
# classmethod # classmethod
cls = self cls = self
@ -210,7 +206,7 @@ def classname(object, modname):
def isdata(object): def isdata(object):
"""Check if an object is of a type that probably means it's data.""" """Check if an object is of a type that probably means it's data."""
return not (inspect.ismodule(object) or _isclass(object) or return not (inspect.ismodule(object) or inspect.isclass(object) or
inspect.isroutine(object) or inspect.isframe(object) or inspect.isroutine(object) or inspect.isframe(object) or
inspect.istraceback(object) or inspect.iscode(object)) inspect.istraceback(object) or inspect.iscode(object))
@ -481,7 +477,7 @@ class Doc:
# by lacking a __name__ attribute) and an instance. # by lacking a __name__ attribute) and an instance.
try: try:
if inspect.ismodule(object): return self.docmodule(*args) if inspect.ismodule(object): return self.docmodule(*args)
if _isclass(object): return self.docclass(*args) if inspect.isclass(object): return self.docclass(*args)
if inspect.isroutine(object): return self.docroutine(*args) if inspect.isroutine(object): return self.docroutine(*args)
except AttributeError: except AttributeError:
pass pass
@ -783,7 +779,7 @@ class HTMLDoc(Doc):
modules = inspect.getmembers(object, inspect.ismodule) modules = inspect.getmembers(object, inspect.ismodule)
classes, cdict = [], {} classes, cdict = [], {}
for key, value in inspect.getmembers(object, _isclass): for key, value in inspect.getmembers(object, inspect.isclass):
# if __all__ exists, believe it. Otherwise use old heuristic. # if __all__ exists, believe it. Otherwise use old heuristic.
if (all is not None or if (all is not None or
(inspect.getmodule(value) or object) is object): (inspect.getmodule(value) or object) is object):
@ -1223,7 +1219,7 @@ location listed above.
result = result + self.section('DESCRIPTION', desc) result = result + self.section('DESCRIPTION', desc)
classes = [] classes = []
for key, value in inspect.getmembers(object, _isclass): for key, value in inspect.getmembers(object, inspect.isclass):
# if __all__ exists, believe it. Otherwise use old heuristic. # if __all__ exists, believe it. Otherwise use old heuristic.
if (all is not None if (all is not None
or (inspect.getmodule(value) or object) is object): or (inspect.getmodule(value) or object) is object):
@ -1707,7 +1703,7 @@ def describe(thing):
return 'member descriptor %s.%s.%s' % ( return 'member descriptor %s.%s.%s' % (
thing.__objclass__.__module__, thing.__objclass__.__name__, thing.__objclass__.__module__, thing.__objclass__.__name__,
thing.__name__) thing.__name__)
if _isclass(thing): if inspect.isclass(thing):
return 'class ' + thing.__name__ return 'class ' + thing.__name__
if inspect.isfunction(thing): if inspect.isfunction(thing):
return 'function ' + thing.__name__ return 'function ' + thing.__name__
@ -1768,7 +1764,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
desc += ' in module ' + module.__name__ desc += ' in module ' + module.__name__
if not (inspect.ismodule(object) or if not (inspect.ismodule(object) or
_isclass(object) or inspect.isclass(object) or
inspect.isroutine(object) or inspect.isroutine(object) or
inspect.isdatadescriptor(object) or inspect.isdatadescriptor(object) or
_getdoc(object)): _getdoc(object)):

View File

@ -80,7 +80,7 @@ def resolve_bases(bases):
updated = False updated = False
shift = 0 shift = 0
for i, base in enumerate(bases): for i, base in enumerate(bases):
if isinstance(base, type) and not isinstance(base, GenericAlias): if isinstance(base, type):
continue continue
if not hasattr(base, "__mro_entries__"): if not hasattr(base, "__mro_entries__"):
continue continue

View File

@ -1079,7 +1079,7 @@ class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _root=True):
var_tuple_index = None var_tuple_index = None
fillarg = None fillarg = None
for k, arg in enumerate(args): for k, arg in enumerate(args):
if not (isinstance(arg, type) and not isinstance(arg, GenericAlias)): if not isinstance(arg, type):
subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
if subargs and len(subargs) == 2 and subargs[-1] is ...: if subargs and len(subargs) == 2 and subargs[-1] is ...:
if var_tuple_index is not None: if var_tuple_index is not None:

View File

@ -0,0 +1,2 @@
:class:`types.GenericAlias` no longer relays the ``__class__`` attribute.
For example, ``isinstance(list[int], type)`` no longer returns ``True``.

View File

@ -583,6 +583,7 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
} }
static const char* const attr_exceptions[] = { static const char* const attr_exceptions[] = {
"__class__",
"__origin__", "__origin__",
"__args__", "__args__",
"__unpacked__", "__unpacked__",