[ACCEPTED]-Adding docstrings to namedtuples?-namedtuple

Accepted answer
Score: 74

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.

Score: 68

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."""
Score: 55

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
Score: 40

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__.

Score: 7

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'
Score: 4

In Python 3.6+ you can use:

class Point(NamedTuple):
    """
    A point in 2D space
    """
    x: float
    y: float

0

Score: 3

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à!

Score: 1

You could concoct your own version of the 6 namedtuple factory function by Raymond Hettinger and add an optional 5 docstring argument.&nbsp;&nbsp;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.&nbsp;&nbsp;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
Score: 0

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