Adding docstrings to namedtuples?

前端 未结 10 1506
死守一世寂寞
死守一世寂寞 2020-12-08 01:52

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

I tried

from collections import namedtuple

Point = namedtuple(\"Poin         


        
相关标签:
10条回答
  • 2020-12-08 02:16

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

    0 讨论(0)
  • 2020-12-08 02:18

    You could concoct your own version of the namedtuple factory function by Raymond Hettinger and add an optional docstring argument.  However it would be easier -- and arguably better -- to just define your own factory function using the same basic technique as in the recipe.  Either 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
    
    0 讨论(0)
  • 2020-12-08 02:20

    No need to use a wrapper class as suggested by the accepted answer. Simply literally add a docstring:

    from collections import namedtuple
    
    Point = namedtuple("Point", ["x", "y"])
    Point.__doc__="A point in 2D space"
    

    This results in: (example using ipython3):

    In [1]: Point?
    Type:       type
    String Form:<class '__main__.Point'>
    Docstring:  A point in 2D space
    
    In [2]: 
    

    Voilà!

    0 讨论(0)
  • 2020-12-08 02:22

    Came across this old question via Google while wondering the same thing.

    Just wanted to point out that you can tidy it up even more by calling namedtuple() right from the class declaration:

    from collections import namedtuple
    
    class Point(namedtuple('Point', 'x y')):
        """Here is the docstring."""
    
    0 讨论(0)
提交回复
热议问题