Hinzufügen von Docstrings zu Namedtuples?

83

Ist es möglich, einem Namedtuple auf einfache Weise eine Dokumentationszeichenfolge hinzuzufügen?

Ich habe es versucht

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)"

aber das schneidet es nicht. Ist es möglich, auf andere Weise zu tun?

Rickard
quelle

Antworten:

51

Sie können dies erreichen, indem Sie eine einfache, leere Wrapper-Klasse um den zurückgegebenen Wert von erstellen namedtuple. Inhalt einer von mir erstellten Datei ( nt.py):

from collections import namedtuple

Point_ = namedtuple("Point", ["x", "y"])

class Point(Point_):
    """ A point in 2d space """
    pass

Dann in der Python REPL:

>>> print nt.Point.__doc__
 A point in 2d space 

Oder Sie könnten tun:

>>> help(nt.Point)  # which outputs...
Hilfe zur Klasse Punkt im Modul nt:

Klasse Punkt (Punkt)
 | Ein Punkt im 2. Raum
 |  
 | Reihenfolge der Methodenauflösung:
 | Punkt
 | Punkt
 | __builtin __. Tupel
 | __builtin __. Objekt
 ...

Wenn Sie das nicht jedes Mal gerne von Hand machen, ist es trivial, eine Art Factory-Funktion zu schreiben, um dies zu tun:

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__

welche Ausgänge:

A point in 3d space
Mark Rushakoff
quelle
2
Wird die Unterklasse das nicht namedtuplein ein vollwertiges "Objekt" umwandeln ? Dabei einige der Leistungssteigerungen durch Named-Tupel verlieren?
Exhuma
5
Wenn Sie __slots__ = ()der abgeleiteten Unterklasse hinzufügen , können Sie die Speicher- und Leistungsvorteile der Verwendung vonnamedtuple
ali_m
Es fügt dem MRO noch eine weitere Ebene hinzu, die für eine Dokumentzeichenfolge nicht gerechtfertigt ist. Man kann jedoch einfach __doc__eine benutzerdefinierte Dokumentzeichenfolge zuweisen und im Originalobjekt speichern lassen.
Bachsau
68

In Python 3 wird kein Wrapper benötigt, da die __doc__Attribute von Typen beschreibbar sind.

from collections import namedtuple

Point = namedtuple('Point', 'x y')
Point.__doc__ = '''\
A 2-dimensional coordinate

x - the abscissa
y - the ordinate'''

Dies entspricht genau einer Standardklassendefinition, bei der die Dokumentzeichenfolge dem Header folgt.

class Point():
    '''A 2-dimensional coordinate

    x - the abscissa
    y - the ordinate'''
    <class code>

Dies funktioniert in Python 2 nicht.

AttributeError: attribute '__doc__' of 'type' objects is not writable.

Terry Jan Reedy
quelle
64

Kam über Google auf diese alte Frage und fragte sich das Gleiche.

Ich wollte nur darauf hinweisen, dass Sie es noch mehr aufräumen können, indem Sie namedtuple () direkt aus der Klassendeklaration heraus aufrufen:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    """Here is the docstring."""
CoupleWavyLines
quelle
8
Wichtig, dass Sie __slots__ = ()in die Klasse aufnehmen. Andernfalls erstellen Sie ein __dict__für Ihre Attribute und verlieren dabei die Leichtigkeit von namedtuple.
BoltzmannBrain
34

Ist es möglich, einem Namedtuple auf einfache Weise eine Dokumentationszeichenfolge hinzuzufügen?

Ja, in mehrfacher Hinsicht.

Typisierung der Unterklasse.NamedTuple - Python 3.6+

Ab Python 3.6 können wir eine classDefinition typing.NamedTupledirekt mit einer Dokumentzeichenfolge (und Anmerkungen!) Verwenden:

from typing import NamedTuple

class Card(NamedTuple):
    """This is a card type."""
    suit: str
    rank: str

Im Vergleich zu Python 2 __slots__ist es nicht erforderlich , leer zu deklarieren . In Python 3.8 ist dies nicht einmal für Unterklassen erforderlich.

Beachten Sie, dass das Deklarieren __slots__nicht leer sein darf!

In Python 3 können Sie das Dokument auch einfach in einem benannten Tupel ändern:

NT = collections.namedtuple('NT', 'foo bar')

NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""

So können wir die Absicht für sie sehen, wenn wir Hilfe bei ihnen anrufen:

Help on class NT in module __main__:

class NT(builtins.tuple)
 |  :param str foo: foo name
 |  :param list bar: List of bars to bar
...

Dies ist im Vergleich zu den Schwierigkeiten, die wir in Python 2 haben, sehr einfach.

Python 2

In Python 2 müssen Sie

  • Unterklasse das benannte Tupel und
  • erklären __slots__ == ()

Die Erklärung __slots__ist ein wichtiger Teil, den die anderen Antworten hier vermissen .

Wenn Sie nicht deklarieren, können __slots__Sie den Instanzen veränderbare Ad-hoc-Attribute hinzufügen und Fehler einführen.

class Foo(namedtuple('Foo', 'bar')):
    """no __slots__ = ()!!!"""

Und nun:

>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}

Jede Instanz erstellt __dict__beim __dict__Zugriff eine separate Instanz (das Fehlen von __slots__wird die Funktionalität sonst nicht beeinträchtigen, aber die Leichtigkeit des Tupels, die Unveränderlichkeit und die deklarierten Attribute sind wichtige Merkmale von Namedtuples).

Sie möchten auch ein __repr__, wenn Sie möchten, dass das, was in der Befehlszeile wiedergegeben wird, ein gleichwertiges Objekt enthält:

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__ = ()

Ein __repr__ähnliches wird benötigt, wenn Sie die Basis namenstupel mit einem anderen Namen erstellen (wie oben mit dem Argument name string 'NTBase'):

    def __repr__(self):
        return 'NT(foo={0}, bar={1})'.format(
                repr(self.foo), repr(self.bar))

Um den Repr zu testen, instanziieren Sie und testen Sie dann die Gleichheit eines Passes zu eval(repr(instance))

nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt

Beispiel aus der Dokumentation

Die Dokumente geben auch ein solches Beispiel in Bezug auf __slots__- ich füge meine eigene Dokumentzeichenfolge hinzu:

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)

...

Die oben gezeigte Unterklasse setzt __slots__auf ein leeres Tupel. Dies hilft, den Speicherbedarf niedrig zu halten, indem die Erstellung von Instanzwörterbüchern verhindert wird.

Dies zeigt die direkte Verwendung (wie eine andere Antwort hier vorschlägt). Beachten Sie jedoch, dass die direkte Verwendung beim Debuggen verwirrend werden kann, wenn Sie sich die Reihenfolge der Methodenauflösung ansehen. Aus diesem Grund habe ich ursprünglich die Verwendung Baseals Suffix vorgeschlagen für die Basis namenstuple:

>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
                # ^^^^^---------------------^^^^^-- same names!        

Um zu verhindern, dass eine __dict__Unterklasse aus einer Klasse erstellt wird, die sie verwendet, müssen Sie sie auch in der Unterklasse deklarieren. Siehe auch diese Antwort für weitere Einschränkungen bei der Verwendung__slots__ .

Aaron Hall
quelle
3
Obwohl nicht so präzise und klar wie die anderen Antworten, sollte dies die akzeptierte Antwort sein, da sie die Bedeutung von hervorhebt __slots__. Ohne sie verlieren Sie den geringen Wert eines benannten Tupels.
BoltzmannBrain
7

Seit Python 3.5 können Dokumentzeichenfolgen für namedtupleObjekte aktualisiert werden.

Aus dem Whatsnew :

Point = namedtuple('Point', ['x', 'y'])
Point.__doc__ += ': Cartesian coodinate'
Point.x.__doc__ = 'abscissa'
Point.y.__doc__ = 'ordinate'
vishes_shell
quelle
4

In Python 3.6+ können Sie Folgendes verwenden:

class Point(NamedTuple):
    """
    A point in 2D space
    """
    x: float
    y: float
Docent
quelle
Ich erhalte "NameError: Name 'NamedTuple' ist nicht definiert"
nbedou
3

Es ist nicht erforderlich, eine Wrapper-Klasse zu verwenden, wie in der akzeptierten Antwort vorgeschlagen. Einfach buchstäblich fügt eine docstring:

from collections import namedtuple

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

Dies führt zu: (Beispiel mit ipython3):

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

In [2]: 

Voilà!

Ein Sz
quelle
1
Hinweis: Dies gilt nur für Python 3. In Python 2 : AttributeError: attribute '__doc__' of 'type' objects is not writable.
Taylor Edmiston
1

Sie können Ihre eigene Version der Factory-Funktion namedtuple von Raymond Hettinger erstellen und ein optionales docstringArgument hinzufügen . Es wäre jedoch einfacher - und wohl besser -, einfach Ihre eigene Fabrikfunktion mit der gleichen Grundtechnik wie im Rezept zu definieren. In jedem Fall erhalten Sie etwas Wiederverwendbares.

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
Martineau
quelle
0

Ich habe diese Funktion erstellt, um schnell ein benanntes Tupel zu erstellen und das Tupel zusammen mit jedem seiner Parameter zu dokumentieren:

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

Sie können dann ein neues benanntes Tupel erstellen:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x="The x value",
    y="The y value"
)

Instanziieren Sie dann das beschriebene benannte Tupel mit Ihren eigenen Daten, dh.

t = MyTuple(4, 8)
print(t) # prints: MyTuple(x=4, y=8)

Bei der Ausführung help(MyTuple)über die Python3-Befehlszeile wird Folgendes angezeigt:

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.

Alternativ können Sie den Parametertyp auch über Folgendes angeben:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x=("int", "The x value"),
    y=("int", "The y value")
)
Steven
quelle
-2

Nein, Sie können nur Dokumentenmodulen zu Klassen, Klassen und Funktionen hinzufügen (einschließlich Methoden).

Jeffrey Aylesworth
quelle