[ACCEPTED]-Adding docstrings to namedtuples?-namedtuple
In Python 3, no wrapper is needed, as the 4 __doc__
attributes of types is writable.
from collections import namedtuple
Point = namedtuple('Point', 'x y')
Point.__doc__ = '''\
A 2-dimensional coordinate
x - the abscissa
y - the ordinate'''
This closely 3 corresponds to a standard class definition, where 2 the docstring follows the header.
class Point():
'''A 2-dimensional coordinate
x - the abscissa
y - the ordinate'''
<class code>
This does 1 not work in Python 2.
AttributeError: attribute '__doc__' of 'type' objects is not writable
.
Came across this old question via Google 4 while wondering the same thing.
Just wanted 3 to point out that you can tidy it up even 2 more by calling namedtuple() right from 1 the class declaration:
from collections import namedtuple
class Point(namedtuple('Point', 'x y')):
"""Here is the docstring."""
You can achieve this by creating a simple, empty 6 wrapper class around the returned value 5 from namedtuple
. Contents of a file I created (nt.py
):
from collections import namedtuple
Point_ = namedtuple("Point", ["x", "y"])
class Point(Point_):
""" A point in 2d space """
pass
Then 4 in the Python REPL:
>>> print nt.Point.__doc__
A point in 2d space
Or you could do:
>>> help(nt.Point) # which outputs...
Help on class Point in module nt: class Point(Point) | A point in 2d space | | Method resolution order: | Point | Point | __builtin__.tuple | __builtin__.object ...
If you 3 don't like doing that by hand every time, it's 2 trivial to write a sort-of factory function 1 to do this:
def NamedTupleWithDocstring(docstring, *ntargs):
nt = namedtuple(*ntargs)
class NT(nt):
__doc__ = docstring
return NT
Point3D = NamedTupleWithDocstring("A point in 3d space", "Point3d", ["x", "y", "z"])
p3 = Point3D(1,2,3)
print p3.__doc__
which outputs:
A point in 3d space
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 41 can use a class
definition with typing.NamedTuple
directly, with 40 a docstring (and annotations!):
from typing import NamedTuple
class Card(NamedTuple):
"""This is a card type."""
suit: str
rank: str
Compared 39 to Python 2, declaring empty __slots__
is not necessary. In 38 Python 3.8, it isn't necessary even for 37 subclasses.
Note that declaring __slots__
cannot be 36 non-empty!
In Python 3, you can also easily 35 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 34 us to view the intent for them when we call 33 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 32 compared to the difficulties we have accomplishing 31 the same thing in Python 2.
Python 2
In Python 2, you'll 30 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 29 __slots__
- you could add mutable ad-hoc attributes 28 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 27 instance will create a separate __dict__
when __dict__
is 26 accessed (the lack of __slots__
won't otherwise impede 25 the functionality, but the lightweightness 24 of the tuple, immutability, and declared 23 attributes are all important features of 22 namedtuples).
You'll also want a __repr__
, if you 21 want what is echoed on the command line 20 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 19 this is needed if you create the base namedtuple 18 with a different name (like we did above 17 with the name string argument, 'NTBase'
):
def __repr__(self):
return 'NT(foo={0}, bar={1})'.format(
repr(self.foo), repr(self.bar))
To test 16 the repr, instantiate, then test for equality 15 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 14 __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 13 subclass shown above sets
__slots__
to an empty tuple. This 12 helps keep memory requirements low by 11 preventing the creation of instance dictionaries.
This 10 demonstrates in-place usage (like another 9 answer here suggests), but note that the 8 in-place usage may become confusing when 7 you look at the method resolution order, if 6 you're debugging, which is why I originally 5 suggested using Base
as a suffix for the base 4 namedtuple:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
# ^^^^^---------------------^^^^^-- same names!
To prevent creation of a __dict__
when 3 subclassing from a class that uses it, you 2 must also declare it in the subclass. See 1 also this answer for more caveats on using __slots__
.
Since Python 3.5, docstrings for namedtuple
objects 1 can be updated.
From the whatsnew:
Point = namedtuple('Point', ['x', 'y']) Point.__doc__ += ': Cartesian coodinate' Point.x.__doc__ = 'abscissa' Point.y.__doc__ = 'ordinate'
In Python 3.6+ you can use:
class Point(NamedTuple):
"""
A point in 2D space
"""
x: float
y: float
0
No need to use a wrapper class as suggested 3 by the accepted answer. Simply literally 2 add a docstring:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
Point.__doc__="A point in 2D space"
This results in: (example using 1 ipython3
):
In [1]: Point?
Type: type
String Form:<class '__main__.Point'>
Docstring: A point in 2D space
In [2]:
Voilà!
You could concoct your own version of the 6 namedtuple factory function by Raymond Hettinger and add an optional 5 docstring
argument. However it 4 would be easier -- and arguably better -- to 3 just define your own factory function using 2 the same basic technique as in the recipe. Either 1 way, you'll end up with something reusable.
from collections import namedtuple
def my_namedtuple(typename, field_names, verbose=False,
rename=False, docstring=''):
'''Returns a new subclass of namedtuple with the supplied
docstring appended to the default one.
>>> Point = my_namedtuple('Point', 'x, y', docstring='A point in 2D space')
>>> print Point.__doc__
Point(x, y): A point in 2D space
'''
# create a base class and concatenate its docstring and the one passed
_base = namedtuple(typename, field_names, verbose, rename)
_docstring = ''.join([_base.__doc__, ': ', docstring])
# fill in template to create a no-op subclass with the combined docstring
template = '''class subclass(_base):
%(_docstring)r
pass\n''' % locals()
# execute code string in a temporary namespace
namespace = dict(_base=_base, _docstring=_docstring)
try:
exec template in namespace
except SyntaxError, e:
raise SyntaxError(e.message + ':\n' + template)
return namespace['subclass'] # subclass object created
I created this function to quickly create 7 a named tuple and document the tuple along 6 with each of its parameters:
from collections import namedtuple
def named_tuple(name, description='', **kwargs):
"""
A named tuple with docstring documentation of each of its parameters
:param str name: The named tuple's name
:param str description: The named tuple's description
:param kwargs: This named tuple's parameters' data with two different ways to describe said parameters. Format:
<pre>{
str: ( # The parameter's name
str, # The parameter's type
str # The parameter's description
),
str: str, # The parameter's name: the parameter's description
... # Any other parameters
}</pre>
:return: collections.namedtuple
"""
parameter_names = list(kwargs.keys())
result = namedtuple(name, ' '.join(parameter_names))
# If there are any parameters provided (such that this is not an empty named tuple)
if len(parameter_names):
# Add line spacing before describing this named tuple's parameters
if description is not '':
description += "\n"
# Go through each parameter provided and add it to the named tuple's docstring description
for parameter_name in parameter_names:
parameter_data = kwargs[parameter_name]
# Determine whether parameter type is included along with the description or
# if only a description was provided
parameter_type = ''
if isinstance(parameter_data, str):
parameter_description = parameter_data
else:
parameter_type, parameter_description = parameter_data
description += "\n:param {type}{name}: {description}".format(
type=parameter_type + ' ' if parameter_type else '',
name=parameter_name,
description=parameter_description
)
# Change the docstring specific to this parameter
getattr(result, parameter_name).__doc__ = parameter_description
# Set the docstring description for the resulting named tuple
result.__doc__ = description
return result
You can then 5 create a new named tuple:
MyTuple = named_tuple(
"MyTuple",
"My named tuple for x,y coordinates",
x="The x value",
y="The y value"
)
Then instantiate 4 the described named tuple with your own 3 data, ie.
t = MyTuple(4, 8)
print(t) # prints: MyTuple(x=4, y=8)
When executing help(MyTuple)
via the python3 2 command line the following is shown:
Help on class MyTuple:
class MyTuple(builtins.tuple)
| MyTuple(x, y)
|
| My named tuple for x,y coordinates
|
| :param x: The x value
| :param y: The y value
|
| Method resolution order:
| MyTuple
| builtins.tuple
| builtins.object
|
| Methods defined here:
|
| __getnewargs__(self)
| Return self as a plain tuple. Used by copy and pickle.
|
| __repr__(self)
| Return a nicely formatted representation string
|
| _asdict(self)
| Return a new OrderedDict which maps field names to their values.
|
| _replace(_self, **kwds)
| Return a new MyTuple object replacing specified fields with new values
|
| ----------------------------------------------------------------------
| Class methods defined here:
|
| _make(iterable) from builtins.type
| Make a new MyTuple object from a sequence or iterable
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(_cls, x, y)
| Create new instance of MyTuple(x, y)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| x
| The x value
|
| y
| The y value
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| _fields = ('x', 'y')
|
| _fields_defaults = {}
|
| ----------------------------------------------------------------------
| Methods inherited from builtins.tuple:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getitem__(self, key, /)
| Return self[key].
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __iter__(self, /)
| Implement iter(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __len__(self, /)
| Return len(self).
|
| __lt__(self, value, /)
| Return self<value.
|
| __mul__(self, value, /)
| Return self*value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __rmul__(self, value, /)
| Return value*self.
|
| count(self, value, /)
| Return number of occurrences of value.
|
| index(self, value, start=0, stop=9223372036854775807, /)
| Return first index of value.
|
| Raises ValueError if the value is not present.
Alternatively, you 1 can also specify the parameter's type via:
MyTuple = named_tuple(
"MyTuple",
"My named tuple for x,y coordinates",
x=("int", "The x value"),
y=("int", "The y value")
)
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.