Czy można w łatwy sposób dodać ciąg dokumentacji do namedtuple?
próbowałem
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)"
ale to nie wystarcza. Czy można to zrobić w inny sposób?
python
docstring
namedtuple
Rickard
źródło
źródło
namedtuple
klasy w pełnoprawny „obiekt”? W ten sposób tracisz część zysków wydajnościowych z nazwanych krotek?__slots__ = ()
do pochodnej podklasy, możesz zachować pamięć i zalety wydajności wynikające z używanianamedtuple
__doc__
i zapisać niestandardowy ciąg dokumentów w oryginalnym obiekcie.W Pythonie 3 opakowanie nie jest potrzebne, ponieważ
__doc__
atrybuty typów są zapisywalne.from collections import namedtuple Point = namedtuple('Point', 'x y') Point.__doc__ = '''\ A 2-dimensional coordinate x - the abscissa y - the ordinate'''
To ściśle odpowiada standardowej definicji klasy, w której ciąg dokumentacyjny występuje po nagłówku.
class Point(): '''A 2-dimensional coordinate x - the abscissa y - the ordinate''' <class code>
To nie działa w Pythonie 2.
AttributeError: attribute '__doc__' of 'type' objects is not writable
.źródło
Przeszedłem przez to stare pytanie przez Google, zastanawiając się nad tym samym.
Chciałem tylko zwrócić uwagę, że możesz to jeszcze bardziej uporządkować, wywołując namedtuple () bezpośrednio z deklaracji klasy:
from collections import namedtuple class Point(namedtuple('Point', 'x y')): """Here is the docstring."""
źródło
__slots__ = ()
w zajęciach. W przeciwnym razie utworzysz a__dict__
dla swoich atrybutów, tracąc lekką naturę namedtuple.Tak, na kilka sposobów.
Wpisywanie podklas NamedTuple - Python 3.6+
Od Pythona 3.6 możemy użyć
class
definicjityping.NamedTuple
bezpośrednio, z ciągiem dokumentów (i adnotacjami!):from typing import NamedTuple class Card(NamedTuple): """This is a card type.""" suit: str rank: str
W porównaniu z Pythonem 2 deklarowanie pustego elementu
__slots__
nie jest konieczne. W Pythonie 3.8 nie jest to konieczne nawet w przypadku podklas.Zwróć uwagę, że deklaracja
__slots__
nie może być niepusta!W Pythonie 3 możesz również łatwo zmienić dokument na nazwanym tulei:
NT = collections.namedtuple('NT', 'foo bar') NT.__doc__ = """:param str foo: foo name :param list bar: List of bars to bar"""
Co pozwala nam zobaczyć ich zamiary, gdy wzywamy ich do pomocy:
Help on class NT in module __main__: class NT(builtins.tuple) | :param str foo: foo name | :param list bar: List of bars to bar ...
Jest to naprawdę proste w porównaniu z trudnościami, jakie napotykamy, wykonując to samo w Pythonie 2.
Python 2
W Pythonie 2 musisz
__slots__ == ()
Deklarowanie
__slots__
jest ważną częścią, której brakuje w innych odpowiedziach .Jeśli nie zadeklarujesz
__slots__
- możesz dodać zmienne atrybuty ad-hoc do instancji, wprowadzając błędy.class Foo(namedtuple('Foo', 'bar')): """no __slots__ = ()!!!"""
I teraz:
>>> f = Foo('bar') >>> f.bar 'bar' >>> f.baz = 'what?' >>> f.__dict__ {'baz': 'what?'}
Każda instancja utworzy oddzielną,
__dict__
gdy__dict__
zostanie uzyskana (brak w__slots__
inny sposób nie utrudni funkcjonalności, ale lekkość krotki, niezmienność i zadeklarowane atrybuty są ważnymi cechami nazwanych krotek).Będziesz także chciał a
__repr__
, jeśli chcesz, aby to, co jest wyświetlane w linii poleceń, dawało ci równoważny obiekt: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__ = ()
coś
__repr__
takiego jest potrzebne, jeśli utworzysz bazę o nazwie tuple z inną nazwą (tak jak zrobiliśmy to powyżej z argumentem nazwa string,'NTBase'
):def __repr__(self): return 'NT(foo={0}, bar={1})'.format( repr(self.foo), repr(self.bar))
Aby przetestować repr, utwórz wystąpienie, a następnie przetestuj równość przejścia do
eval(repr(instance))
nt = NT('foo', 'bar') assert eval(repr(nt)) == nt
Przykład z dokumentacji
Dokumenty podają również taki przykład, dotyczący
__slots__
- dodaję do niego własny dokument:Pokazuje to użycie w miejscu (jak sugeruje inna odpowiedź), ale zwróć uwagę, że użycie w miejscu może być mylące, gdy spojrzysz na kolejność rozwiązywania metod, jeśli debugujesz, dlatego pierwotnie sugerowałem użycie
Base
jako sufiksu dla podstawy o nazwietuple:>>> Point.mro() [<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>] # ^^^^^---------------------^^^^^-- same names!
Aby zapobiec tworzeniu
__dict__
klasy podczas tworzenia podklasy z klasy, która jej używa, należy również zadeklarować ją w podklasie. Zobacz także tę odpowiedź, aby uzyskać więcej zastrzeżeń dotyczących używania__slots__
.źródło
__slots__
. Bez tego tracisz lekką wartość imiennej sumy.Od Pythona 3.5, dokumenty dla
namedtuple
można aktualizować obiektów.Z whatsnew :
źródło
W Pythonie 3.6+ możesz użyć:
class Point(NamedTuple): """ A point in 2D space """ x: float y: float
źródło
Nie ma potrzeby używania klasy opakowującej, jak sugeruje zaakceptowana odpowiedź. Po prostu dosłownie dodaj ciąg dokumentów:
from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) Point.__doc__="A point in 2D space"
Skutkuje to: (przykład przy użyciu
ipython3
):In [1]: Point? Type: type String Form:<class '__main__.Point'> Docstring: A point in 2D space In [2]:
Voilà!
źródło
AttributeError: attribute '__doc__' of 'type' objects is not writable
.Możesz wymyślić własną wersję funkcji namedtuple factory przez Raymonda Hettingera i dodać opcjonalny
docstring
argument. Byłoby jednak łatwiej - i prawdopodobnie lepiej - po prostu zdefiniować własną funkcję fabryczną przy użyciu tej samej podstawowej techniki, co w recepturze. Tak czy inaczej, otrzymasz coś wielokrotnego użytku.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
źródło
Utworzyłem tę funkcję, aby szybko utworzyć nazwaną krotkę i udokumentować krotkę wraz z każdym z jej parametrów:
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
Następnie możesz utworzyć nową nazwaną krotkę:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x="The x value", y="The y value" )
Następnie utwórz wystąpienie opisanej nazwanej krotki z własnymi danymi, tj.
t = MyTuple(4, 8) print(t) # prints: MyTuple(x=4, y=8)
Podczas wykonywania
help(MyTuple)
za pomocą wiersza poleceń python3 pokazano: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.
Alternatywnie możesz również określić typ parametru poprzez:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x=("int", "The x value"), y=("int", "The y value") )
źródło
Nie, ciągi dokumentacyjne można dodawać tylko do modułów, klas i funkcji (w tym metod)
źródło