views:

176

answers:

3

Hello,

Is it possible to add a documentation string to a namedtuple in an easy manner?

I tried

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
"""
A point in 2D space
"""

# Yet another test

"""
A(nother) point in 2D space
"""
Point2 = namedtuple("Point2", ["x", "y"])

print Point.__doc__ # -> "Point(x, y)"
print Point2.__doc__ # -> "Point2(x, y)"

but that doesn't cut it. Is it possible to do in some other way?

Thank you, Rickard

+3  A: 

No, you can only add doc strings to modules, classes and function (including methods)

Jeffrey Aylesworth
… yes, and collections.namedtuple returns an *object* (of type `type`).
EOL
+4  A: 

You can achieve this by creating a simple, empty wrapper class around the returned value 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 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 don't like doing that by hand every time, it's trivial to write a sort-of factory function 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
Mark Rushakoff
This is how I add custom methods/docstrings to `namedtuple`.
Sridhar Ratnakumar
Thank you. I also tried this and I guess I'll use this workaround. Are there any performance penalties to wrap the tuple in a new custom class in this manner?
Rickard
I'm guessing that adding the __slots__ = () to the class definition (as per the documentation for namedtuples) gives the wrapping a negligble performance impact.
Rickard
Either of the near-empty class or adding `__slots__` should have a negligible performance impact in any "normal" usage.
Mark Rushakoff
+1  A: 

You could easily create your own version of the namedtuple factory function by Raymond Hettinger by adding an optional docstring argument. Alternatively, you could hide something along the lines of your "dirty workaround" inside another factory function. For example:

from collections import namedtuple

def mynamedtuple(typename, field_names, verbose=False, rename=False, docstring=''):
    '''Create a namedtuple class with the supplied docstring
    appended to the usual one generated.

    >>> Point = mynamedtuple("Point", "x, y", docstring="A point in 2D space")
    >>> print Point.__doc__
    Point(x, y)
    A point in 2D space

    '''
    # create base class and concatenate its docstring and the one passed
    _base = namedtuple(typename, field_names, verbose, rename)
    _docstring = ''.join([_base.__doc__, '\n', docstring])

    # create and fill-in a template to create a subclass
    # with this combined  docstring
    template = '''class subclass(_base):
        %(_docstring)r
        pass\n''' % locals()

    # execute the template 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']

Point = mynamedtuple("Point", "x, y", docstring="A point in 2D space")
print Point.__doc__
martineau