-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't create generic NamedTuple as of py3.9 #88089
Comments
As of python 3.9, you now can't have multiple inheritance with example:
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? |
+1 for reverting this change and restoring the previous behavior. |
Couldn't there be a subtler solution than rolling back #63570? |
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. |
Mypy seems to allow this: from typing import NamedTuple, TypeVar, Generic, List, Tuple
T = TypeVar("T")
class New(NamedTuple, Generic[T]):
x: List[T]
y: Tuple[T, T] It's true that pyright doesn't, but maybe that's because it doesn't work in 3.9-3.10? |
It doesn't really. If you do main.py:11: error: List item 0 has incompatible type "int"; expected "T" https://mypy-play.net/?mypy=latest&python=3.10&gist=a13c7a33c55a3aeee95324d46cd03ffd |
So if it doesn't work in mypy why bother making it work at runtime? Is there an actual use case that broke? (There might be, but probably esoteric if nobody's run into it until now.) |
I actually have quite a few use cases for this feature. It's true that type checkers don't (yet) support it, but that doesn't mean that it should be disallowed at runtime. In fact, allowing it at runtime will surely give type checkers room to experiment with implementing this feature if it is requested by enough users. As it is, they are blocked from doing so. |
No, because this was never usable in the first place. But there are those who wish it were usable :) |
Please don't revert all changes. It will not make it working right in any case. See bpo-44863 for more correct approach. |
Can you be more specific about your use cases? |
Consider the typeshed stub for
Until two days ago, this stub actually had a bug: With generic NamedTuples, we could write the stub for the class far more simply (and more accurately) like this:
And in code that actually needs to run at runtime, I frequently find it frustrating that I have to use dataclasses instead of NamedTuples if I want a simple class that just happens to be generic. dataclasses are great, but for small, lightweight classes, I prefer to use NamedTuples where possible. I often find that I don't need to use the full range of features dataclasses provide; and NamedTuples are often more performant than dataclasses, especially in cases where there's a lot of tuple unpacking. |
Okay, that's a sensible use case. I do doubt your intuition of preferring named tuples over dataclasses a bit. This seems to encourage premature optimization. I'd say for simple cases use plain tuples (most performant), for complex cases use dataclasses (named fields and many other features that you may eventually want). Compare concurent.futures.wait()'s return type (a named tuple) to asyncio.tasks.wait()'s return type (a plain tuple). I don't think that naming the fields of the return tuple (awkwardly :-) makes the c.f.wait() API easier to understand than the asyncio.wait() API. Maybe named tuples, like typed dicts, are "in-between" solutions on the spectrum of data types (tuple - named tuple - dataclass; dict - typed dict - dataclass), and we should encourage people to use the neighboring solutions instead. I'd rather spend efforts making dataclasses faster than adding features to named tuples. |
I sense we'll have to agree to disagree on the usefulness of NamedTuples in the age of dataclasses :) For me, I find the simplicity of the underlying idea behind namedtuples — "tuples with some properties bolted on" — very attractive. Yes, standard tuples are more performant, but it's great to have a tool in the arsenal that's essentially the same as a tuple (and is backwards-compatible with a tuple, for APIs that require a tuple), but can also, like dataclasses, be self-documenting. (You're right that DoneAndNotDoneFutures isn't a great example of this.) But I agree that this shouldn't be a priority if it's hard to accomplish; and there'll certainly be no complaints from me if energy is invested into making dataclasses faster. |
The main advantage for my usecase is support for heterogeneous unpacking On Sat, Mar 5, 2022, 6:04 PM Alex Waygood <[email protected]> wrote:
|
The use case that prompted #31679 is that we are adding typings to We have an existing options class that subclasses Our current workaround is to create a separate stub file that uses Switching to |
Playing tricks where compile-time and run-time see slightly different types is probably more productive than trying to revert a PR that was in Python 3.9 and 3.10. :-) I'm not opposed to supporting generic NamedTuple, but I expect the fix will never hit 3.9 and 3.10, and it needs to be a "fix forward" PR. Would you mind closing the "revert" PR unmerged? |
I agree we're stuck with the typing stub workaround for our use case. We can re-submit a "fix forward" PR. |
PR 31781 is a simple PR which enables multiple inheritance with NamedTuple. As a side effect, it adds support of generic NamedTuple. I am not sure that all details work as expected. It is easy to limit multiple inheritance only for Generic if needed. |
+1 for the more minimal changeset proposed in PR 31781. I've never felt a need for NamedTuple multiple inheritance other than with Generic, so wouldn't be opposed to restricting it only to Generic. |
What about Protocol? It is possible to create a dataclass that is a protocol, so it would be nicer from a symmetry perspective to allow it on both dataclasses and NamedTuples. |
Hi, just saying that I have ran into that issue as well. My use case is packing some arguments for a function call which would look somewhat like this (a much simplified example): T = TypeVar("T")
class MessageCallArgs(NamedTuple, Generic[T]):
caller: Actor
actorAddress: TypedActorAddress[T] so that later a function can be called: class A:
@staticmethod
def foo(caller: Actor, actorAddress: TypedActorAddress["A"], name: str):
# Remote call here
callArgs = MessageCallArgs(caller, actorAddress)
A.foo(*callArgs , "blah-blah")
Using just NamedTuple looses type information which is useful for type-checking. So far I have reverted to using just tuple[T] but this looses all benefits of NamedTuple, like named fields and a user-defined type. It seems to be me that the reason to forbid the creation of generic named tuples is purely technical, which is unfortunate. I do not think that this case is rare, contrary to what has been said in this thread. |
It did not actually worked in 3.8:
A NamedTuple is not generic, it is just that other base classes were silently ignored. |
There is a problem related to the fact that It is not specific to
And named tuple types are unexpectedly subscribtable without Generic (since they are
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")
>>>
>>> class N1(NamedTuple, Generic[T, U]):
... a: T
... b: U
...
>>> class N2(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. |
Consider the following example:
It looks somewhere like a generic named tuple. But it conflicts with what is proposed here. |
So that just inherits |
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 |
I agree with Guido. |
I have also opened a topic on Python-Dev about blessing the |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: