Type hints in namedtuple
Asked Answered
O

3

250

Consider following piece of code:

from collections import namedtuple
point = namedtuple("Point", ("x:int", "y:int"))

The Code above is just a way to demonstrate as to what I am trying to achieve. I would like to make namedtuple with type hints.

Do you know any elegant way how to achieve result as intended?

Olomouc answered 14/12, 2015 at 14:43 Comment(1)
Also, starting with py3.7 you have the dataclass option: docs.python.org/3/library/dataclasses.htmlHesychast
M
329

The preferred syntax for a typed namedtuple since Python 3.6 is

from typing import NamedTuple

class Point(NamedTuple):
    x: int
    y: int = 1  # Set default value

Point(3)  # -> Point(x=3, y=1)

Starting with Python 3.7, consider using a dataclasses:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int = 1  # Set default value

Point(3)  # -> Point(x=3, y=1)
Millen answered 26/4, 2018 at 8:45 Comment(7)
@JohnE; The OP specifically asked for named tuples. Yes, many use cases of named tuples will be better served by data classes. But to quote the excellent Why not namedtuples: If you want a tuple with names, by all means: go for a namedtupleMillen
Using dataclasses, it is not possible to deconstruct the resulting object like you could a TupleAkimbo
A tuple is immutable. A dataclass is not (by default) It does have the frozen flag which gets close to tuple's behaviour. Just something to be aware of.Roadrunner
if dataclass works for you, you can go further and use pydantic package to enforce type checking on runtime in elegant way.Spin
Dataclasses aren't subscribable, and they neither are unpackable while iterating as named tuples do so I think they are far from being a perfect alternative.Rainfall
this is effectively just adding types to an ordered dictionary. "Tuple" in "named tuple" suggests it should rely on a constant hashable immutable value.Saltire
Tuples are not always immutable (#32799800).. I mean the references to the objects are immutable, but if a member of a tuple is mutable - you can mutate it. In fact, if you "try hard" you can change both NamedTUple and frozen dataclass. Therefore, I would suggest to pick the one that looks better or is available at your python version.Jowers
P
180

You can use typing.NamedTuple

From the docs

Typed version of namedtuple.

>>> import typing
>>> Point = typing.NamedTuple("Point", [('x', int), ('y', int)])

This is present only in Python 3.5 onwards

Phelia answered 14/12, 2015 at 14:47 Comment(3)
In newer versions you may declare NamedTuples as Point = typing.NamedTuple("Point", x=int, y=int), which is much cleaner and shorter.Fracture
^ Documentation, discussion, and examples floating around for this syntax is nearly non-existent. I only found it in the cpython typing.py file. It states the syntax is available from 3.6+, and it's at least in the 3.7+ code. Though there doesn't seem to be a cleaner version of this for setting defaults adjacent to the member/type declaration.Hedelman
^^ While the keyword-argument syntax mentioned by @MarkedasDuplicate still works (tested on Python 3.10.9 (tags/v3.10.9:1dd9be6)), it was removed from the docstrings in pull 105287 because according to the pull author, "this way of creating NamedTuples isn't supported by any [known] type checker".Pennell
S
15

Just to be fair, NamedTuple from typing:

>>> from typing import NamedTuple
>>> class Point(NamedTuple):
...     x: int
...     y: int = 1  # Set default value
...
>>> Point(3)
Point(x=3, y=1)

equals to classic namedtuple:

>>> from collections import namedtuple
>>> p = namedtuple('Point', 'x,y', defaults=(1, ))
>>> p.__annotations__ = {'x': int, 'y': int}
>>> p(3)
Point(x=3, y=1)

So, NamedTuple is just syntax sugar for namedtuple

Below, you can find a creating NamedTuple function from the source code of python 3.10. As we can see, it uses collections.namedtuple constructor and adds __annotations__ from extracted types:

def _make_nmtuple(name, types, module, defaults = ()):
    fields = [n for n, t in types]
    types = {n: _type_check(t, f"field {n} annotation must be a type")
             for n, t in types}
    nm_tpl = collections.namedtuple(name, fields,
                                    defaults=defaults, module=module)
    nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types
    return nm_tpl
Sniggle answered 17/11, 2021 at 17:53 Comment(3)
Syntactic sugar is something the parser can replace with more fundamental syntax. NamedTuple is a bit more complicated than that, being a function that actually does something at runtime.Irmgardirmina
Yes, I know what it does do during runtime. It is extracting types and adds them to __annotations__ attr of just created namedtuple using constructor collections.namedtuple. I added that code to the answer for better understanding.Sniggle
@Sniggle Thanks for this. it really helped me understand what is actually happening, and that the resulting object is the same in both cases.Pemba

© 2022 - 2024 — McMap. All rights reserved.