Skip to content

Commit

Permalink
gh-89828: Do not relay the __class__ attribute in GenericAlias (#93754)
Browse files Browse the repository at this point in the history
list[int].__class__ returned type, and isinstance(list[int], type)
returned True. It caused numerous problems in code that checks
isinstance(x, type).
  • Loading branch information
serhiy-storchaka authored Jun 18, 2022
1 parent 084023c commit f9433ff
Show file tree
Hide file tree
Showing 7 changed files with 18 additions and 20 deletions.
4 changes: 2 additions & 2 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def __init__(self, type):
self.type = type

def __repr__(self):
if isinstance(self.type, type) and not isinstance(self.type, GenericAlias):
if isinstance(self.type, type):
type_name = self.type.__name__
else:
# typing objects, e.g. List[int]
Expand Down Expand Up @@ -1248,7 +1248,7 @@ def _is_dataclass_instance(obj):
def is_dataclass(obj):
"""Returns True if obj is a dataclass or an instance of a
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)


Expand Down
5 changes: 2 additions & 3 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,12 +843,11 @@ def _is_union_type(cls):
return get_origin(cls) in {Union, types.UnionType}

def _is_valid_dispatch_type(cls):
if isinstance(cls, type) and not isinstance(cls, GenericAlias):
if isinstance(cls, type):
return True
from typing import get_args
return (_is_union_type(cls) and
all(isinstance(arg, type) and not isinstance(arg, GenericAlias)
for arg in get_args(cls)))
all(isinstance(arg, type) for arg in get_args(cls)))

def register(cls, func=None):
"""generic_func.register(cls, func) -> func
Expand Down
22 changes: 9 additions & 13 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ class or function within a module or module in a package. If the
import sysconfig
import time
import tokenize
import types
import urllib.parse
import warnings
from collections import deque
Expand All @@ -92,24 +91,21 @@ def pathdirs():
normdirs.append(normdir)
return dirs

def _isclass(object):
return inspect.isclass(object) and not isinstance(object, types.GenericAlias)

def _findclass(func):
cls = sys.modules.get(func.__module__)
if cls is None:
return None
for name in func.__qualname__.split('.')[:-1]:
cls = getattr(cls, name)
if not _isclass(cls):
if not inspect.isclass(cls):
return None
return cls

def _finddoc(obj):
if inspect.ismethod(obj):
name = obj.__func__.__name__
self = obj.__self__
if (_isclass(self) and
if (inspect.isclass(self) and
getattr(getattr(self, name, None), '__func__') is obj.__func__):
# classmethod
cls = self
Expand All @@ -123,7 +119,7 @@ def _finddoc(obj):
elif inspect.isbuiltin(obj):
name = obj.__name__
self = obj.__self__
if (_isclass(self) and
if (inspect.isclass(self) and
self.__qualname__ + '.' + name == obj.__qualname__):
# classmethod
cls = self
Expand Down Expand Up @@ -210,7 +206,7 @@ def classname(object, modname):

def isdata(object):
"""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.istraceback(object) or inspect.iscode(object))

Expand Down Expand Up @@ -481,7 +477,7 @@ def document(self, object, name=None, *args):
# by lacking a __name__ attribute) and an instance.
try:
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)
except AttributeError:
pass
Expand Down Expand Up @@ -783,7 +779,7 @@ def docmodule(self, object, name=None, mod=None, *ignored):
modules = inspect.getmembers(object, inspect.ismodule)

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 is not None or
(inspect.getmodule(value) or object) is object):
Expand Down Expand Up @@ -1223,7 +1219,7 @@ def docmodule(self, object, name=None, mod=None):
result = result + self.section('DESCRIPTION', desc)

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 is not None
or (inspect.getmodule(value) or object) is object):
Expand Down Expand Up @@ -1707,7 +1703,7 @@ def describe(thing):
return 'member descriptor %s.%s.%s' % (
thing.__objclass__.__module__, thing.__objclass__.__name__,
thing.__name__)
if _isclass(thing):
if inspect.isclass(thing):
return 'class ' + thing.__name__
if inspect.isfunction(thing):
return 'function ' + thing.__name__
Expand Down Expand Up @@ -1768,7 +1764,7 @@ def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
desc += ' in module ' + module.__name__

if not (inspect.ismodule(object) or
_isclass(object) or
inspect.isclass(object) or
inspect.isroutine(object) or
inspect.isdatadescriptor(object) or
_getdoc(object)):
Expand Down
2 changes: 1 addition & 1 deletion Lib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def resolve_bases(bases):
updated = False
shift = 0
for i, base in enumerate(bases):
if isinstance(base, type) and not isinstance(base, GenericAlias):
if isinstance(base, type):
continue
if not hasattr(base, "__mro_entries__"):
continue
Expand Down
2 changes: 1 addition & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ def __typing_prepare_subst__(self, alias, args):
var_tuple_index = None
fillarg = None
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)
if subargs and len(subargs) == 2 and subargs[-1] is ...:
if var_tuple_index is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`types.GenericAlias` no longer relays the ``__class__`` attribute.
For example, ``isinstance(list[int], type)`` no longer returns ``True``.
1 change: 1 addition & 0 deletions Objects/genericaliasobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@ ga_vectorcall(PyObject *self, PyObject *const *args,
}

static const char* const attr_exceptions[] = {
"__class__",
"__origin__",
"__args__",
"__unpacked__",
Expand Down

0 comments on commit f9433ff

Please sign in to comment.