mirror of https://github.com/python/cpython
gh-101688: Implement types.get_original_bases (#101827)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
05b3ce7339
commit
730bbddfdf
|
@ -82,6 +82,46 @@ Dynamic Type Creation
|
|||
|
||||
.. versionadded:: 3.7
|
||||
|
||||
.. function:: get_original_bases(cls, /)
|
||||
|
||||
Return the tuple of objects originally given as the bases of *cls* before
|
||||
the :meth:`~object.__mro_entries__` method has been called on any bases
|
||||
(following the mechanisms laid out in :pep:`560`). This is useful for
|
||||
introspecting :ref:`Generics <user-defined-generics>`.
|
||||
|
||||
For classes that have an ``__orig_bases__`` attribute, this
|
||||
function returns the value of ``cls.__orig_bases__``.
|
||||
For classes without the ``__orig_bases__`` attribute, ``cls.__bases__`` is
|
||||
returned.
|
||||
|
||||
Examples::
|
||||
|
||||
from typing import TypeVar, Generic, NamedTuple, TypedDict
|
||||
|
||||
T = TypeVar("T")
|
||||
class Foo(Generic[T]): ...
|
||||
class Bar(Foo[int], float): ...
|
||||
class Baz(list[str]): ...
|
||||
Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
|
||||
Spam = TypedDict("Spam", {"a": int, "b": str})
|
||||
|
||||
assert Bar.__bases__ == (Foo, float)
|
||||
assert get_original_bases(Bar) == (Foo[int], float)
|
||||
|
||||
assert Baz.__bases__ == (list,)
|
||||
assert get_original_bases(Baz) == (list[str],)
|
||||
|
||||
assert Eggs.__bases__ == (tuple,)
|
||||
assert get_original_bases(Eggs) == (NamedTuple,)
|
||||
|
||||
assert Spam.__bases__ == (dict,)
|
||||
assert get_original_bases(Spam) == (TypedDict,)
|
||||
|
||||
assert int.__bases__ == (object,)
|
||||
assert get_original_bases(int) == (object,)
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. seealso::
|
||||
|
||||
:pep:`560` - Core support for typing module and generic types
|
||||
|
|
|
@ -2102,6 +2102,10 @@ Resolving MRO entries
|
|||
:func:`types.resolve_bases`
|
||||
Dynamically resolve bases that are not instances of :class:`type`.
|
||||
|
||||
:func:`types.get_original_bases`
|
||||
Retrieve a class's "original bases" prior to modifications by
|
||||
:meth:`~object.__mro_entries__`.
|
||||
|
||||
:pep:`560`
|
||||
Core support for typing module and generic types.
|
||||
|
||||
|
|
|
@ -407,6 +407,13 @@ threading
|
|||
profiling functions in all running threads in addition to the calling one.
|
||||
(Contributed by Pablo Galindo in :gh:`93503`.)
|
||||
|
||||
types
|
||||
-----
|
||||
|
||||
* Add :func:`types.get_original_bases` to allow for further introspection of
|
||||
:ref:`user-defined-generics` when subclassed. (Contributed by
|
||||
James Hilton-Balfe and Alex Waygood in :gh:`101827`.)
|
||||
|
||||
unicodedata
|
||||
-----------
|
||||
|
||||
|
|
|
@ -1360,6 +1360,67 @@ class ClassCreationTests(unittest.TestCase):
|
|||
D = types.new_class('D', (A(), C, B()), {})
|
||||
self.assertEqual(D.__bases__, (A1, A2, A3, C, B1, B2))
|
||||
|
||||
def test_get_original_bases(self):
|
||||
T = typing.TypeVar('T')
|
||||
class A: pass
|
||||
class B(typing.Generic[T]): pass
|
||||
class C(B[int]): pass
|
||||
class D(B[str], float): pass
|
||||
self.assertEqual(types.get_original_bases(A), (object,))
|
||||
self.assertEqual(types.get_original_bases(B), (typing.Generic[T],))
|
||||
self.assertEqual(types.get_original_bases(C), (B[int],))
|
||||
self.assertEqual(types.get_original_bases(int), (object,))
|
||||
self.assertEqual(types.get_original_bases(D), (B[str], float))
|
||||
|
||||
class E(list[T]): pass
|
||||
class F(list[int]): pass
|
||||
|
||||
self.assertEqual(types.get_original_bases(E), (list[T],))
|
||||
self.assertEqual(types.get_original_bases(F), (list[int],))
|
||||
|
||||
class ClassBasedNamedTuple(typing.NamedTuple):
|
||||
x: int
|
||||
|
||||
class GenericNamedTuple(typing.NamedTuple, typing.Generic[T]):
|
||||
x: T
|
||||
|
||||
CallBasedNamedTuple = typing.NamedTuple("CallBasedNamedTuple", [("x", int)])
|
||||
|
||||
self.assertIs(
|
||||
types.get_original_bases(ClassBasedNamedTuple)[0], typing.NamedTuple
|
||||
)
|
||||
self.assertEqual(
|
||||
types.get_original_bases(GenericNamedTuple),
|
||||
(typing.NamedTuple, typing.Generic[T])
|
||||
)
|
||||
self.assertIs(
|
||||
types.get_original_bases(CallBasedNamedTuple)[0], typing.NamedTuple
|
||||
)
|
||||
|
||||
class ClassBasedTypedDict(typing.TypedDict):
|
||||
x: int
|
||||
|
||||
class GenericTypedDict(typing.TypedDict, typing.Generic[T]):
|
||||
x: T
|
||||
|
||||
CallBasedTypedDict = typing.TypedDict("CallBasedTypedDict", {"x": int})
|
||||
|
||||
self.assertIs(
|
||||
types.get_original_bases(ClassBasedTypedDict)[0],
|
||||
typing.TypedDict
|
||||
)
|
||||
self.assertEqual(
|
||||
types.get_original_bases(GenericTypedDict),
|
||||
(typing.TypedDict, typing.Generic[T])
|
||||
)
|
||||
self.assertIs(
|
||||
types.get_original_bases(CallBasedTypedDict)[0],
|
||||
typing.TypedDict
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "Expected an instance of type"):
|
||||
types.get_original_bases(object())
|
||||
|
||||
# Many of the following tests are derived from test_descr.py
|
||||
def test_prepare_class(self):
|
||||
# Basic test of metaclass derivation
|
||||
|
|
32
Lib/types.py
32
Lib/types.py
|
@ -143,6 +143,38 @@ def _calculate_meta(meta, bases):
|
|||
"of the metaclasses of all its bases")
|
||||
return winner
|
||||
|
||||
|
||||
def get_original_bases(cls, /):
|
||||
"""Return the class's "original" bases prior to modification by `__mro_entries__`.
|
||||
|
||||
Examples::
|
||||
|
||||
from typing import TypeVar, Generic, NamedTuple, TypedDict
|
||||
|
||||
T = TypeVar("T")
|
||||
class Foo(Generic[T]): ...
|
||||
class Bar(Foo[int], float): ...
|
||||
class Baz(list[str]): ...
|
||||
Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
|
||||
Spam = TypedDict("Spam", {"a": int, "b": str})
|
||||
|
||||
assert get_original_bases(Bar) == (Foo[int], float)
|
||||
assert get_original_bases(Baz) == (list[str],)
|
||||
assert get_original_bases(Eggs) == (NamedTuple,)
|
||||
assert get_original_bases(Spam) == (TypedDict,)
|
||||
assert get_original_bases(int) == (object,)
|
||||
"""
|
||||
try:
|
||||
return cls.__orig_bases__
|
||||
except AttributeError:
|
||||
try:
|
||||
return cls.__bases__
|
||||
except AttributeError:
|
||||
raise TypeError(
|
||||
f'Expected an instance of type, not {type(cls).__name__!r}'
|
||||
) from None
|
||||
|
||||
|
||||
class DynamicClassAttribute:
|
||||
"""Route attribute access on a class to __getattr__.
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Implement :func:`types.get_original_bases` to provide further introspection
|
||||
for types.
|
Loading…
Reference in New Issue