As of python 3.9, you now can't have multiple inheritance with typing.NamedTuple subclasses. This seems sensible, until you realise that typing.Generic works via inheritance. This fails whether or not from __future__ import annotations is enabled.
example:
class Group(NamedTuple, Generic[T]):
key: T
group: List[T]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-fc238c4826d7> in <module>
----> 1 class Group(NamedTuple, Generic[T]):
2 key: T
3 group: List[T]
4
~/.conda/envs/py39/lib/python3.9/typing.py in _namedtuple_mro_entries(bases)
1818 def _namedtuple_mro_entries(bases):
1819 if len(bases) > 1:
-> 1820 raise TypeError("Multiple inheritance with NamedTuple is not supported")
1821 assert bases[0] is NamedTuple
1822 return (_NamedTuple,)
TypeError: Multiple inheritance with NamedTuple is not supported
This worked fine in python 3.7 and 3.8 and as I understand it was one of the motivating cases for PEP-560.
The change was made as part of bpo-40185: Refactor typing.NamedTuple. Whilst the obvious alternative is "use dataclasses", they don't have the same runtime properties or implications as namedtuples.
This is unfortunate, especially since it used to work... Going forward, is the intention not to support this use case? Or is it possible that support for generic NamedTuples will be re-added in the future?
Was this ever documented to work? We have now disallowed this behavior in 3.9 and 3.10 with few complaints, so it doesn't seem that important to restore it. Also, static type checkers generally disallow generic NamedTuples.
Reacted by dendron2000, mark, Hans Brende, CodErik, Conchylicultor and devds96
It doesn't really. If you do x = New([1], (2, 3)) you get:
main.py:11: error: List item 0 has incompatible type "int"; expected "T"
main.py:11: error: Argument 2 to "New" has incompatible type "Tuple[int, int]"; expected "Tuple[T, T]"
There is a problem related to the fact that tuple has the __class_getitem__ method. And if Generic[T] follows NamedTuple, it is tuple.__class_getitem__ called when subscript the type.
It is not specific to NamedTuple, this problem exists when you inherit from any generic class and Generic:
>>> from typing import *
>>> T = TypeVar('T')
>>> class A(tuple, Generic[T]): ...
...
>>> class B(Generic[T], tuple): ...
...
>>> A.__bases__
(<class 'tuple'>, <class 'typing.Generic'>)
>>> B.__bases__
(<class 'typing.Generic'>, <class 'tuple'>)
>>> A[int]
__main__.A[int]
>>> B[int]
__main__.B[int]
>>> type(A[int])
<class 'types.GenericAlias'>
>>> type(B[int])
<class 'typing._GenericAlias'>
>>> A[int, str]
__main__.A[int, str]
>>> B[int, str]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/serhiy/py/cpython/Lib/typing.py", line 332, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 1767, in __class_getitem__
_check_generic(cls, params, len(cls.__parameters__))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/serhiy/py/cpython/Lib/typing.py", line 253, in _check_generic
raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Too many arguments for <class '__main__.B'>; actual 2, expected 1
And named tuple types are unexpectedly subscribtable without Generic (since they are tuple subclasses):
>>> class NT(NamedTuple):
... x: int
...
>>> NT[str, float]
__main__.NT[str, float]
The problem is how to create non-generic (or generic with the specified parameters) subclasses of generic types.
@serhiy-storchaka good catch! I'm not too concerned about non-generic subclasses of tuple being subscriptable. In general there are a lot of things you can do around annotations that are meaningless to type checkers, and we can just leave it to the static checkers to detect that.
But this does also affect NamedTuple with your PR:
>>> from typing import*
>>> T = TypeVar("T")
>>> U = TypeVar("U")
>>>
>>> classN1(NamedTuple, Generic[T, U]):
... a: T
... b: U
...
>>> classN2(Generic[T, U], NamedTuple):
... a: T
... b: U
...
>>> N1[int, str]
__main__.N1[int, str]
>>> N2[int, str]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/jelle/py/cpython/Lib/typing.py", line 329, in inner
return cached(*args, **kwds)
^^^^^^^^^^^^^^^^^^^^^
File "/Users/jelle/py/cpython/Lib/typing.py", line 1761, in __class_getitem__
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
^^^^^^^^^^^^^^^^^^
AttributeError: type object 'N2' has no attribute '__parameters__'
That's pretty confusing behavior. (I guess this is why you added the "do not merge" label to #92027.)
What if we just do this?
diff --git a/Lib/typing.py b/Lib/typing.py
index 5b34508edc..e8fc840f1e 100644
--- a/Lib/typing.py+++ b/Lib/typing.py@@ -2768,7 +2768,10 @@ def __new__(cls, typename, bases, ns):
if base is not _NamedTuple and base is not Generic:
raise TypeError(
'can only inherit from a NamedTuple type and Generic')
- bases = tuple(tuple if base is _NamedTuple else base for base in bases)+ if Generic in bases:+ bases = (tuple, Generic)+ else:+ bases = (tuple,)
types = ns.get('__annotations__', {})
default_names = []
for field_name in types:
Then things work as expected with either order for the base classes.
So that just inherits __class_getitem__ (CORRECTED) from tuple. Isn't it the job of the static checker to reject this? Runtime allows all kinds of nonsense, like list[int,int,int]. Is this any different?
In any case if you want to make a class that isn't indexable but inherits from a class that is (e.g. namedtuple inheriting from tuple) you can just set __class_getitem__ = None in the class.
In any case if you want to make a class that isn't indexable but inherits from a class that is (e.g. namedtuple inheriting from tuple) you can just set __class_getitem__ = None in the class.
Activity
FHTMitchell commented on Apr 23, 2021
As of python 3.9, you now can't have multiple inheritance with typing.NamedTuple subclasses. This seems sensible, until you realise that typing.Generic works via inheritance. This fails whether or not from __future__ import annotations is enabled.
example:
class Group(NamedTuple, Generic[T]): key: T group: List[T] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-fc238c4826d7> in <module> ----> 1 class Group(NamedTuple, Generic[T]): 2 key: T 3 group: List[T] 4 ~/.conda/envs/py39/lib/python3.9/typing.py in _namedtuple_mro_entries(bases) 1818 def _namedtuple_mro_entries(bases): 1819 if len(bases) > 1: -> 1820 raise TypeError("Multiple inheritance with NamedTuple is not supported") 1821 assert bases[0] is NamedTuple 1822 return (_NamedTuple,) TypeError: Multiple inheritance with NamedTuple is not supportedThis worked fine in python 3.7 and 3.8 and as I understand it was one of the motivating cases for PEP-560.
The change was made as part of bpo-40185: Refactor typing.NamedTuple. Whilst the obvious alternative is "use dataclasses", they don't have the same runtime properties or implications as namedtuples.
dlukes commented on Nov 10, 2021
This is unfortunate, especially since it used to work... Going forward, is the intention not to support this use case? Or is it possible that support for generic NamedTuples will be re-added in the future?
rhettinger commented on Nov 10, 2021
+1 for reverting this change and restoring the previous behavior.
gvanrossum commented on Mar 4, 2022
Couldn't there be a subtler solution than rolling back #63570?
JelleZijlstra commented on Mar 5, 2022
Was this ever documented to work? We have now disallowed this behavior in 3.9 and 3.10 with few complaints, so it doesn't seem that important to restore it. Also, static type checkers generally disallow generic NamedTuples.
gvanrossum commented on Mar 5, 2022
Mypy seems to allow this:
It's true that pyright doesn't, but maybe that's because it doesn't work in 3.9-3.10?
JelleZijlstra commented on Mar 5, 2022
It doesn't really. If you do x = New([1], (2, 3)) you get:
main.py:11: error: List item 0 has incompatible type "int"; expected "T"
main.py:11: error: Argument 2 to "New" has incompatible type "Tuple[int, int]"; expected "Tuple[T, T]"
https://mypy-play.net/?mypy=latest&python=3.10&gist=a13c7a33c55a3aeee95324d46cd03ffd
23 remaining items
serhiy-storchaka commented on Apr 28, 2022
#92027 is a limited version of #31781 that only allows combining NamedTuple with Generic.
serhiy-storchaka commented on Apr 29, 2022
There is a problem related to the fact that tuple has the __class_getitem__ method. And if Generic[T] follows NamedTuple, it is tuple.__class_getitem__ called when subscript the type.
It is not specific to NamedTuple, this problem exists when you inherit from any generic class and Generic:
>>> from typing import * >>> T = TypeVar('T') >>> class A(tuple, Generic[T]): ... ... >>> class B(Generic[T], tuple): ... ... >>> A.__bases__ (<class 'tuple'>, <class 'typing.Generic'>) >>> B.__bases__ (<class 'typing.Generic'>, <class 'tuple'>) >>> A[int] __main__.A[int] >>> B[int] __main__.B[int] >>> type(A[int]) <class 'types.GenericAlias'> >>> type(B[int]) <class 'typing._GenericAlias'> >>> A[int, str] __main__.A[int, str] >>> B[int, str] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/serhiy/py/cpython/Lib/typing.py", line 332, in inner return func(*args, **kwds) ^^^^^^^^^^^^^^^^^^^ File "/home/serhiy/py/cpython/Lib/typing.py", line 1767, in __class_getitem__ _check_generic(cls, params, len(cls.__parameters__)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/serhiy/py/cpython/Lib/typing.py", line 253, in _check_generic raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments for {cls};" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: Too many arguments for <class '__main__.B'>; actual 2, expected 1And named tuple types are unexpectedly subscribtable without Generic (since they are tuple subclasses):
The problem is how to create non-generic (or generic with the specified parameters) subclasses of generic types.
JelleZijlstra commented on Apr 29, 2022
@serhiy-storchaka good catch! I'm not too concerned about non-generic subclasses of tuple being subscriptable. In general there are a lot of things you can do around annotations that are meaningless to type checkers, and we can just leave it to the static checkers to detect that.
But this does also affect NamedTuple with your PR:
That's pretty confusing behavior. (I guess this is why you added the "do not merge" label to #92027.)
What if we just do this?
Then things work as expected with either order for the base classes.
serhiy-storchaka commented on Apr 30, 2022
Consider the following example:
>>> import collections >>> from typing import * >>> T = TypeVar('T') >>> Group = collections.namedtuple('Group', 'key group')[T, list[T]]It looks somewhere like a generic named tuple. But it conflicts with what is proposed here.
gvanrossum commented on Apr 30, 2022
So that just inherits __class_getitem__ (CORRECTED) from tuple. Isn't it the job of the static checker to reject this? Runtime allows all kinds of nonsense, like list[int,int,int]. Is this any different?
gvanrossum commented on Apr 30, 2022
In any case if you want to make a class that isn't indexable but inherits from a class that is (e.g. namedtuple inheriting from tuple) you can just set __class_getitem__ = None in the class.
AlexWaygood commented on Apr 30, 2022
I agree with Guido.
serhiy-storchaka commented on May 1, 2022
#92106, #92107.
I have also opened a topic on Python-Dev about blessing the __dunder__ = None idiom: https://mail.python.org/archives/list/python-dev@python.org/thread/YGAK34DRWJFSIV2VZ4NC2J24XO37GCMM/
Un-parameterize `Rectangle`
Add a function to compute a radar grid that spans an input geocoded g…