È possibile aggiungere una stringa di documentazione a una namedtuple in modo semplice?
Sì, in diversi modi.
Tipo di sottoclasse.NamedTuple - Python 3.6+
A partire da Python 3.6 possiamo usare una classdefinizione typing.NamedTupledirettamente con, con una docstring (e annotazioni!):
from typing import NamedTuple
class Card(NamedTuple):
"""This is a card type."""
suit: str
rank: str
Rispetto a Python 2, dichiarando vuoto __slots__ non è necessaria. In Python 3.8, non è necessario nemmeno per le sottoclassi.
Nota che la dichiarazione __slots__ non può essere non vuota!
In Python 3, puoi anche modificare facilmente il documento su una namedtuple:
NT = collections.namedtuple('NT', 'foo bar')
NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""
Ciò ci consente di visualizzare l'intento per loro quando chiamiamo aiuto su di loro:
Help on class NT in module __main__:
class NT(builtins.tuple)
| :param str foo: foo name
| :param list bar: List of bars to bar
...
Questo è davvero semplice rispetto alle difficoltà che abbiamo nel realizzare la stessa cosa in Python 2.
Python 2
In Python 2, dovrai
- sottoclasse la namedtuple e
- dichiarare
__slots__ == ()
La dichiarazione __slots__è una parte importante che le altre risposte qui mancano .
Se non dichiari __slots__, potresti aggiungere attributi ad-hoc modificabili alle istanze, introducendo bug.
class Foo(namedtuple('Foo', 'bar')):
"""no __slots__ = ()!!!"""
E adesso:
>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}
Ogni istanza creerà un separato __dict__quando __dict__si accede (la mancanza di __slots__non impedirà altrimenti la funzionalità, ma la leggerezza della tupla, l'immutabilità e gli attributi dichiarati sono tutte caratteristiche importanti di namedtuples).
Avrai anche bisogno di un __repr__, se vuoi che ciò che viene echeggiato sulla riga di comando ti dia un oggetto equivalente:
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__ = ()
una __repr__come questo è necessario se si crea la namedtuple di base con un altro nome (come abbiamo fatto in precedenza con l'argomento stringa di nome, 'NTBase'):
def __repr__(self):
return 'NT(foo={0}, bar={1})'.format(
repr(self.foo), repr(self.bar))
Per testare la replica, creare un'istanza, quindi verificare l'uguaglianza di un passaggio a eval(repr(instance))
nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt
Esempio dalla documentazione
I documenti forniscono anche un esempio del genere, per quanto riguarda __slots__: sto aggiungendo la mia docstring ad esso:
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)
...
La sottoclasse mostrata sopra imposta __slots__una tupla vuota. Ciò aiuta a mantenere bassi i requisiti di memoria impedendo la creazione di dizionari di istanza.
Questo dimostra l'utilizzo sul posto (come suggerisce un'altra risposta qui), ma tieni presente che l'utilizzo sul posto potrebbe creare confusione quando guardi l'ordine di risoluzione del metodo, se stai eseguendo il debug, motivo per cui ho originariamente suggerito di usare Basecome suffisso per la base denominata coppia:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
# ^^^^^---------------------^^^^^-- same names!
Per impedire la creazione di una __dict__sottoclasse da una classe che la utilizza, è necessario dichiararla anche nella sottoclasse. Vedi anche questa risposta per ulteriori avvertenze sull'uso__slots__ .
namedtuplein un vero e proprio "oggetto"? Perdendo così alcuni dei guadagni in termini di prestazioni dalle tuple con nome?