Adding docstrings to namedtuples?

前端 未结 10 1538
死守一世寂寞
死守一世寂寞 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()
    [, , , ]
                    # ^^^^^---------------------^^^^^-- 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__.

提交回复
热议问题