You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
That is accepted syntactically and at runtime but no new __new__ method is generated so calling B(1, 2, 3) is flagged as an error (too many arguments) by both mypy and runtime.
I think this feels natural to you because you can do all such things (extending and merging) with TypedDict. It was simple with TypedDict since both functional and class syntax were developed at the same time, while with NamedTuple we have problem of backwards compatibility. The functional syntax exists for a long time (also in the form of collections.namedtuple). I have seen many times when people were subclassing named tuples, so that changing semantics of subclassing can potentially break things. It seems to me it was already briefly discussed some time ago, but I can't fins the discussion. There are three possible solutions now:
Status quo.
We can have different semantics for functional syntax (returning the result of plain collections.namedtuple call), and class syntax (returning some "smart class" that will support all the TypedDict tricks while mimicking collections.namedtuple).
Make both functional and class syntax return the "smart class", people who want the old (i.e. current) semantics can just use collections.namedtuple.
All three options have some downsides. If we go with the second or third option, then I think it is better to do this fast (to minimise the effect of backwards incompatibility).
Hm, the functional syntax cannot be used to create a subclass. I think it should not make a difference whether you use functional or class syntax for the base class. But the class syntax is new, so this problem is new too. I suppose the backward compatibility problem you're talking about is that in my example, one could also assume that B.z is a mutable instance variable? I think that would be a much less likely use case -- I can understand why you want to add methods, but mutable state seems quite a stretch for a NamedTuple subclass.
Or did I miss something? If not, now's the time to choose the more useful semantics.
I suppose the backward compatibility problem you're talking about is that in my example, one could also assume that B.z is a mutable instance variable?
Also the fact that currently the signature of constructor of a subclass is not changed. One can have some variables added in a subclass and be surprised when the signature of constructor is changed in a new version of typing. This is still not a major backwards incompatibility, but probably something we should consider. For example:
Base=NamedTuple('Base', [('x', int), ('y', int)])
classDerived(Base):
some_bookkeeping: List[int] = [] # mypy requires annotation hereDerived(1, 2) # will fail in a new version
I think we should allow ourselves complete freedom in changing this -- there's only the slightest mention of it in PEP 484 and none in PEP 526. That makes it a provisional feature (like everything in typing.py and in either of those PEPs).
Even for a legitimate class deriving from NamedTuple (e.g. A in my example) there seems to be an ambiguity with initial values -- e.g.
classA(NamedTuple):
x: inty: int=0a=A(12)
passes in mypy but fails at runtime:
Traceback (most recent call last):
File "__tmp__.py", line 11, in <module>
a = A(12)
TypeError: __new__() missing 1 required positional argument: 'y'
It fails at runtime with older versions of typing, but passes at runtime with the latest one (I just tried this on CPython master). In general I see your point, this is not explicitly documented, and typing is provisional, so that we can change this. I will play with this tomorrow (as I understand, we don't need to change anything in Python 2 version of typing).
B can be any type with _field_types defined, the latter should be a dictionary mapping names to types;
if B has _field_defaults attribute, then it will be also used to populate default values;
C is not a subtype (static) and not a subclass (runtime) of B;
fields from B cannot be overwritten by fields in C (this can be enforced only by static checkers as for TypedDicts currently).
Similar syntax can be also supported for functional form.
Possible rationale for this (at least what I have heard) is producing relatively many similar named tuples. In addition, in future we may switch to structural subtyping between named tuples (it will be a minor extension over current protocols implementation, we just need to take care of fields order), so that:
There is basically no experience in the field with the current class-based NamedTuple (it wasn't even mentioned in PEP 526). For the functional API this could be solved much simpler (without new syntax) by teaching mypy about constant expressions so you can write
There is basically no experience in the field with the current class-based NamedTuple (it wasn't even mentioned in PEP 526).
It is true the class syntax is mostly unknown to people. Which is a bit strange, people seem to be always excited when they encounter it.
For the functional API this could be solved much simpler (without new syntax) by teaching mypy about constant expressions so you can write
It is also true that functional API already supports this, but the class API is more flexible, it supports default values, docstrings, custom __repr__.
For anything fancier in 3.7 we can refer people to dataclasses.
Yes, we don't need anything fancier for named tuples, but It would be nice if class syntax will be as "expressive" as functional API. Although the class keyword will not allow something more complex like:
it will probably be enough for most use cases. Finally, implementing class keyword will be very simple.
On the other hand, maybe it is OK if both class and functional syntax have some strengths. Functional API can be used for more "dynamic" namedtuple creation, while class syntax is more suitable for "static" cases, so just keeping the status quo in typing is also an acceptable option.
It seems a lot of extra implementation complexity to occasionally save people a few lines of copied code...
Reacted by SoonShin, Kwon (Sam Willow)
Reacted by Richard Kurle, Ben Bogin, Metalstorm, Matej Volf, Aurélien Bombo, Balázs Simó, Ellis Breen, ItsDrike, Dimitri Kokkonis, Jamie Vaughan and 13 more
Would something like this be useful for mimicking a subclass in some cases? Major caveats obviously being that nothing is actually inherited, so you have to enforce correctness yourself.
class A(
NamedTuple("A", (("z", int), ("y", str)))
):
pass
class B(A):
base = NamedTuple("B", (("z", int), ("y", str), ("x", bool)))
def __new__(cls, *args, **kwargs):
# v--- Override the pseudo parent class
return cls.base.__new__(cls.base, *args, **kwargs)
If methods defined on one of the classes, would need to copy methods defined on the subclass onto the returned instance.
This copies methods, but loses isinstance passing on the parent class and subclass instance:
class A(
NamedTuple("A", (("z", int), ("y", str)))
):
def a(self):
print(self.z)
base = NamedTuple("B", (("z", int), ("y", str), ("x", bool)))
class Base(base, A):
def __init__(self):
for method in dir(B):
func = getattr(B, method)
if type(func) is instancemethod and not method.startswith("_"):
setattr(self, method, MethodType(func.__func__, self, B))
super(Base, self).__init__()
class B(A):
def b(self):
print(self.y)
def __new__(cls, *args, **kwargs):
# v--- Override the pseudo parent class
new = base.__new__(Base, *args, **kwargs)
new.__init__()
return new
Activity
ilevkivskyi commentedon May 10, 2017
I think this feels natural to you because you can do all such things (extending and merging) with
TypedDict. It was simple withTypedDictsince both functional and class syntax were developed at the same time, while withNamedTuplewe have problem of backwards compatibility. The functional syntax exists for a long time (also in the form ofcollections.namedtuple). I have seen many times when people were subclassing named tuples, so that changing semantics of subclassing can potentially break things. It seems to me it was already briefly discussed some time ago, but I can't fins the discussion. There are three possible solutions now:collections.namedtuplecall), and class syntax (returning some "smart class" that will support all theTypedDicttricks while mimickingcollections.namedtuple).collections.namedtuple.All three options have some downsides. If we go with the second or third option, then I think it is better to do this fast (to minimise the effect of backwards incompatibility).
gvanrossum commentedon May 10, 2017
Hm, the functional syntax cannot be used to create a subclass. I think it should not make a difference whether you use functional or class syntax for the base class. But the class syntax is new, so this problem is new too. I suppose the backward compatibility problem you're talking about is that in my example, one could also assume that
B.zis a mutable instance variable? I think that would be a much less likely use case -- I can understand why you want to add methods, but mutable state seems quite a stretch for a NamedTuple subclass.Or did I miss something? If not, now's the time to choose the more useful semantics.
ilevkivskyi commentedon May 10, 2017
Also the fact that currently the signature of constructor of a subclass is not changed. One can have some variables added in a subclass and be surprised when the signature of constructor is changed in a new version of
typing. This is still not a major backwards incompatibility, but probably something we should consider. For example:gvanrossum commentedon May 10, 2017
I think we should allow ourselves complete freedom in changing this -- there's only the slightest mention of it in PEP 484 and none in PEP 526. That makes it a provisional feature (like everything in typing.py and in either of those PEPs).
Even for a legitimate class deriving from NamedTuple (e.g. A in my example) there seems to be an ambiguity with initial values -- e.g.
passes in mypy but fails at runtime:
ilevkivskyi commentedon May 10, 2017
It fails at runtime with older versions of
typing, but passes at runtime with the latest one (I just tried this on CPython master). In general I see your point, this is not explicitly documented, andtypingis provisional, so that we can change this. I will play with this tomorrow (as I understand, we don't need to change anything in Python 2 version oftyping).gvanrossum commentedon May 11, 2017
Thanks, no hurry. I actually lost track of the thought that led me to try that. Probably another random issue or PR in the tracker. :-(
ilevkivskyi commentedon Jun 9, 2017
I would propose the following syntax:
with the following rules:
Bcan be any type with_field_typesdefined, the latter should be a dictionary mapping names to types;Bhas_field_defaultsattribute, then it will be also used to populate default values;Cis not a subtype (static) and not a subclass (runtime) ofB;Bcannot be overwritten by fields inC(this can be enforced only by static checkers as forTypedDicts currently).Similar syntax can be also supported for functional form.
Possible rationale for this (at least what I have heard) is producing relatively many similar named tuples. In addition, in future we may switch to structural subtyping between named tuples (it will be a minor extension over current protocols implementation, we just need to take care of fields order), so that:
consequently, this will also work:
@gvanrossum @JukkaL what do you think?
gvanrossum commentedon Jun 9, 2017
There is basically no experience in the field with the current class-based NamedTuple (it wasn't even mentioned in PEP 526). For the functional API this could be solved much simpler (without new syntax) by teaching mypy about constant expressions so you can write
(And similar for collections.namedtuple.)
For anything fancier in 3.7 we can refer people to dataclasses.
ilevkivskyi commentedon Jun 9, 2017
It is true the class syntax is mostly unknown to people. Which is a bit strange, people seem to be always excited when they encounter it.
It is also true that functional API already supports this, but the class API is more flexible, it supports default values, docstrings, custom
__repr__.Yes, we don't need anything fancier for named tuples, but It would be nice if class syntax will be as "expressive" as functional API. Although the class keyword will not allow something more complex like:
it will probably be enough for most use cases. Finally, implementing class keyword will be very simple.
On the other hand, maybe it is OK if both class and functional syntax have some strengths. Functional API can be used for more "dynamic" namedtuple creation, while class syntax is more suitable for "static" cases, so just keeping the status quo in
typingis also an acceptable option.gvanrossum commentedon Jun 9, 2017
It seems a lot of extra implementation complexity to occasionally save people a few lines of copied code...
ilevkivskyi commentedon Jun 9, 2017
OK, so I understand that you vote for status quo :-)
ilevkivskyi commentedon Jun 10, 2017
Following the discussion on mypy tracker, I think this issue can be closed as well.
heyakyra commentedon Apr 29, 2022
Would something like this be useful for mimicking a subclass in some cases? Major caveats obviously being that nothing is actually inherited, so you have to enforce correctness yourself.
If methods defined on one of the classes, would need to copy methods defined on the subclass onto the returned instance.
This copies methods, but loses
isinstancepassing on the parent class and subclass instance:Corrected leap second subclass default value.
Corrected leap second subclass default value.
johnslavik commentedon Apr 9, 2025
After 8 years, having accidentally run into this, I'm reiterating this issue in https://github.com/bswck/make-typed-namedtuples-final.