Is it possible to add a documentation string to a namedtuple in an easy manner?
Yes, in several ways.
Subclass typing.NamedTuple - Python 3.6+
As of Python 3.6 we can use a class
definition with typing.NamedTuple
directly, with a docstring (and annotations!):
from typing import NamedTuple
class Card(NamedTuple):
"""This is a card type."""
suit: str
rank: str
Compared to Python 2, declaring empty __slots__
is not necessary. In Python 3.8, it isn't necessary even for subclasses.
Note that declaring __slots__
cannot be non-empty!
In Python 3, you can also easily alter the doc on a namedtuple:
NT = collections.namedtuple('NT', 'foo bar')
NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""
Which allows us to view the intent for them when we call help on them:
Help on class NT in module __main__:
class NT(builtins.tuple)
| :param str foo: foo name
| :param list bar: List of bars to bar
...
This is really straightforward compared to the difficulties we have accomplishing the same thing in Python 2.
Python 2
In Python 2, you'll need to
- subclass the namedtuple, and
- declare
__slots__ == ()
Declaring __slots__
is an important part that the other answers here miss .
If you don't declare __slots__
- you could add mutable ad-hoc attributes to the instances, introducing bugs.
class Foo(namedtuple('Foo', 'bar')):
"""no __slots__ = ()!!!"""
And now:
>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}
Each instance will create a separate __dict__
when __dict__
is accessed (the lack of __slots__
won't otherwise impede the functionality, but the lightweightness of the tuple, immutability, and declared attributes are all important features of namedtuples).
You'll also want a __repr__
, if you want what is echoed on the command line to give you an equivalent object:
NTBase = collections.namedtuple('NTBase', 'foo bar')
class NT(NTBase):
"""
Individual foo bar, a namedtuple
:param str foo: foo name
:param list bar: List of bars to bar
"""
__slots__ = ()
a __repr__
like this is needed if you create the base namedtuple with a different name (like we did above with the name string argument, 'NTBase'
):
def __repr__(self):
return 'NT(foo={0}, bar={1})'.format(
repr(self.foo), repr(self.bar))
To test the repr, instantiate, then test for equality of a pass to eval(repr(instance))
nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt
Example from the documentation
The docs also give such an example, regarding __slots__
- I'm adding my own docstring to it:
class Point(namedtuple('Point', 'x y')):
"""Docstring added here, not in original"""
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
...
The subclass shown above sets __slots__
to an empty tuple. This helps
keep memory requirements low by preventing the creation of instance
dictionaries.
This demonstrates in-place usage (like another answer here suggests), but note that the in-place usage may become confusing when you look at the method resolution order, if you're debugging, which is why I originally suggested using Base
as a suffix for the base namedtuple:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
# ^^^^^---------------------^^^^^-- same names!
To prevent creation of a __dict__
when subclassing from a class that uses it, you must also declare it in the subclass. See also this answer for more caveats on using __slots__
.
namedtuple
into a full-fledged "object"? Thereby losing some of the performance gains from named-tuples? – Guildroy