From 579e985e75fd682e314c7b0cce6b1751abe8a7d0 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 18 Jun 2022 07:41:25 -0700 Subject: [PATCH] gh-89828: Do not relay the __class__ attribute in GenericAlias (GH-93754) list[int].__class__ returned type, and isinstance(list[int], type) returned True. It caused numerous problems in code that checks isinstance(x, type). (cherry picked from commit f9433fff476aa13af9cb314fcc6962055faa4085) Co-authored-by: Serhiy Storchaka --- Lib/dataclasses.py | 4 ++-- Lib/functools.py | 5 ++--- Lib/pydoc.py | 22 ++++++++----------- Lib/types.py | 2 +- Lib/typing.py | 2 +- ...2-06-12-19-31-56.gh-issue-89828.bq02M7.rst | 2 ++ Objects/genericaliasobject.c | 1 + 7 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-06-12-19-31-56.gh-issue-89828.bq02M7.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 18ab69053fd520..69cab8c563ea98 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -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] @@ -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) diff --git a/Lib/functools.py b/Lib/functools.py index cd5666dfa71fd0..43ead512e1ea4e 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -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 diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 297ff967a2ce9c..434316635d489a 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -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 @@ -92,16 +91,13 @@ 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 @@ -109,7 +105,7 @@ 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 @@ -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 @@ -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)) @@ -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 @@ -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): @@ -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): @@ -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__ @@ -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)): diff --git a/Lib/types.py b/Lib/types.py index 9490da7b9ee3b9..2e73fbc4501337 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -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 diff --git a/Lib/typing.py b/Lib/typing.py index 25cae7ffb8d788..1ebc3ce1f64bd6 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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: diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-06-12-19-31-56.gh-issue-89828.bq02M7.rst b/Misc/NEWS.d/next/Core and Builtins/2022-06-12-19-31-56.gh-issue-89828.bq02M7.rst new file mode 100644 index 00000000000000..14ca99e1458ce3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-06-12-19-31-56.gh-issue-89828.bq02M7.rst @@ -0,0 +1,2 @@ +:class:`types.GenericAlias` no longer relays the ``__class__`` attribute. +For example, ``isinstance(list[int], type)`` no longer returns ``True``. diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 0a0d0cc4c15b68..b2636d5475dbb9 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -583,6 +583,7 @@ ga_vectorcall(PyObject *self, PyObject *const *args, } static const char* const attr_exceptions[] = { + "__class__", "__origin__", "__args__", "__unpacked__",