Jak udokumentować atrybuty klas w Pythonie? [Zamknięte]

115

Piszę lekką klasę, której atrybuty mają być publicznie dostępne i tylko czasami nadpisywane w określonych instancjach. W języku Python nie ma przepisu na tworzenie ciągów dokumentów dla atrybutów klas, ani żadnego innego rodzaju atrybutów. Jaki jest oczekiwany i obsługiwany sposób udokumentowania tych atrybutów, czy powinien istnieć? Obecnie robię takie rzeczy:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
    """

    flight_speed = 691
    __doc__ += """
        flight_speed (691)
          The maximum speed that such a bird can attain.
    """

    nesting_grounds = "Raymond Luxury-Yacht"
    __doc__ += """
        nesting_grounds ("Raymond Luxury-Yacht")
          The locale where these birds congregate to reproduce.
    """

    def __init__(self, **keyargs):
        """Initialize the Albatross from the keyword arguments."""
        self.__dict__.update(keyargs)

Spowoduje to, że dokumentacja klasy będzie zawierała początkową standardową sekcję docstringową, a także wiersze dodane dla każdego atrybutu poprzez rozszerzone przypisanie do __doc__.

Chociaż ten styl nie wydaje się być wyraźnie zabroniony w wytycznych dotyczących stylów dokumentów , nie jest on również wspomniany jako opcja. Zaletą jest to, że zapewnia sposób dokumentowania atrybutów wraz z ich definicjami, jednocześnie tworząc prezentowalny dokument klasy i unikając konieczności pisania komentarzy, które powtarzają informacje z dokumentu. Nadal jestem trochę poirytowany, że muszę dwukrotnie pisać atrybuty; Rozważam użycie reprezentacji ciągów wartości w dokumencie, aby przynajmniej uniknąć powielania wartości domyślnych.

Czy to ohydne naruszenie konwencji wspólnoty ad hoc? Czy to w porządku? Czy jest lepszy sposób? Na przykład, możliwe jest utworzenie słownika zawierającego wartości i ciągi dokumentacyjne dla atrybutów, a następnie dodanie zawartości do klasy __dict__i napisów dokumentacyjnych na końcu deklaracji klasy; zmniejszyłoby to potrzebę dwukrotnego wpisywania nazw i wartości atrybutów. edycja : ten ostatni pomysł jest, myślę, w rzeczywistości niemożliwy, przynajmniej nie bez dynamicznego budowania całej klasy z danych, co wydaje się naprawdę złym pomysłem, chyba że istnieje inny powód, aby to zrobić.

Jestem całkiem nowy w Pythonie i nadal pracuję nad szczegółami stylu kodowania, więc niepowiązane krytyki są również mile widziane.

intuicyjnie
źródło
Jeśli szukasz sposobu na udokumentowanie atrybutów modelu Django, może to być pomocne: djangosnippets.org/snippets/2533
Michael
3
Duplikat Jak dokumentować pola i właściwości w Pythonie? które mają inne rozwiązanie.
bufh
1
Nie rozumiem, dlaczego jest to oparte na opiniach. Python w szczególności dokumentuje akceptowalne konwencje w PEP. Istnieją różne narzędzia źródłowe Pythona, które wyodrębniają odpowiednio sformatowaną dokumentację. W rzeczywistości Python faktycznie ma attribute doc stringwspomniany w PEP 257, który nie jest dobrze znany i wydaje się trudny do znalezienia, który może odpowiedzieć na pytanie PO i jest obsługiwany przez niektóre narzędzia źródłowe. To nie jest opinia. To fakt, część języka i właściwie to, czego chce OP.
NeilG

Odpowiedzi:

83

Aby uniknąć nieporozumień: termin właściwość ma określone znaczenie w Pythonie. Mówisz o tym, co nazywamy atrybutami klas . Ponieważ zawsze działają na nie poprzez swoją klasę, uważam, że sensowne jest udokumentowanie ich w dokumentacji klasy. Coś takiego:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
        flight_speed     The maximum speed that such a bird can attain.
        nesting_grounds  The locale where these birds congregate to reproduce.
    """
    flight_speed = 691
    nesting_grounds = "Throatwarbler Man Grove"

Myślę, że jest to o wiele łatwiejsze dla oczu niż podejście z twojego przykładu. Gdybym naprawdę chciał, aby kopia wartości atrybutów pojawiła się w ciągu dokumentacyjnym, umieściłbym je obok lub poniżej opisu każdego atrybutu.

Należy pamiętać, że w Pythonie ciągi dokumentacyjne są rzeczywistymi składnikami obiektów, które dokumentują, a nie tylko adnotacjami do kodu źródłowego. Ponieważ zmienne atrybutów klasy same w sobie nie są obiektami, ale odwołaniami do obiektów, nie mają możliwości przechowywania własnych ciągów dokumentacyjnych. Wydaje mi się, że można by podać argumenty dla ciągów dokumentacyjnych w referencjach, być może w celu opisania „co tu powinno się znaleźć” zamiast „co właściwie tu jest”, ale wydaje mi się, że dość łatwo jest to zrobić w zawierającym ciąg dokumentacyjny klasy.

ʇsәɹoɈ
źródło
Sądzę, że w większości przypadków jest to w porządku, ponieważ atrybuty - dzięki poprawce terminologii - są wystarczająco zwięźle zadeklarowane, że można je zgrupować na początku deklaracji klasy, nie czyniąc niepraktycznym przewijaniem w tę i z powrotem, aby albo {przeczytaj oba dokumentacja i wartość domyślna} lub {zaktualizuj oba wystąpienia dokumentacji i / lub wartość domyślną}.
intuicyjnie
1
Zauważ również, że mój przykład spowoduje, że dokumentacja atrybutów pojawi się w docstringu klasy. Właściwie wolałbym umieścić dokumentację w dokumentach samych atrybutów, ale to nie działa w przypadku większości typów wbudowanych.
intuicyjnie
Tak, moim początkowym pomysłem było po prostu zadeklarowanie np flight_speed = 691; flight_speed.__doc__ = "blah blah". Myślę, że właśnie o tym wspominasz w swojej edycji . Niestety, nie działa to w przypadku instancji (większości?) Typów wbudowanych (jak intw tym przykładzie). Działa w przypadku wystąpień typów zdefiniowanych przez użytkownika. =========== Właściwie istniał PEP (przepraszam, zapomnij o numerze), który proponował dodanie ciągów dokumentacji dla atrybutów klas / modułów, ale został odrzucony, ponieważ nie mogli znaleźć sposobu, aby to wyjaśnić czy ciągi dokumentacyjne dotyczyły poprzednich czy następnych atrybutów.
intuicyjnie
2
a co jeśli są to atrybuty instancji? nadal dokument w klasie docstring czy co?
n611x007
1
@intuited Czy to był PEP? legacy.python.org/dev/peps/pep-0224
taz
30

Cytujesz PEP257: Docstring Conventions, w sekcji Co to jest dokumentacja, o której mowa:

Literały łańcuchowe występujące w innym miejscu kodu Pythona mogą również służyć jako dokumentacja. Nie są one rozpoznawane przez kompilator kodu bajtowego Pythona i nie są dostępne jako atrybuty obiektów środowiska wykonawczego (tj. Nie są przypisane do __doc__), ale narzędzia programowe mogą wyodrębnić dwa rodzaje dodatkowych dokumentów:

Literały łańcuchowe występujące bezpośrednio po prostym przypisaniu na najwyższym poziomie modułu, klasy lub metody __init__ nazywane są „ciągami dokumentów atrybutów”.

I jest to wyjaśnione bardziej szczegółowo w PEP 258: Dokumentacja atrybutów. Jak wyjaśniono powyżej ʇsәɹoɈ. atrybut nie jest obiektem, który może posiadać __doc__, więc nie pojawią się w help()ani w pydoc. Te dokumenty mogą być używane tylko do generowanej dokumentacji.

Są używane w Sphinx z dyrektywą autoattribute

Sphinx może używać komentarzy w wierszu przed przydziałem lub specjalnego komentarza po przydziale lub ciągu dokumentu po definicji, który będzie automatycznie dokumentowany.

Marcz
źródło
1
Wtyczka jedi-vim rozpoznaje również ciągi dokumentów atrybutów.
Long Vu
1
Nie wiem, kiedy to zostało wprowadzone, ale wydaje się, że Sphinx 1.2.2 zawiera dokumentację atrybutów w wygenerowanej dokumentacji.
jochen
1
Dziękuję @jochen, aktualizuję swoją odpowiedź.
marcz
3
Należy pamiętać, że PEP 258 jest odrzucany. W zawiadomieniu o odrzuceniu stwierdza się: „Chociaż może to służyć jako interesujący dokument projektowy dla obecnie niezależnych dokumentów, nie jest już przeznaczone do włączenia do biblioteki standardowej”.
Michał Łazowik 14.11.18
13

W tym celu możesz nadużywać właściwości. Właściwości zawierają getter, setter, deleter i docstring . Naiwnie byłoby to bardzo rozwlekłe:

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Docstring goes here."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Wtedy będziesz miał ciąg dokumentów należący do Cx:

In [24]: print(C.x.__doc__)
Docstring goes here.

Robienie tego dla wielu atrybutów jest kłopotliwe, ale możesz sobie wyobrazić funkcję pomocniczą myprop:

def myprop(x, doc):
    def getx(self):
        return getattr(self, '_' + x)

    def setx(self, val):
        setattr(self, '_' + x, val)

    def delx(self):
        delattr(self, '_' + x)

    return property(getx, setx, delx, doc)

class C:
    a = myprop("a", "Hi, I'm A!")
    b = myprop("b", "Hi, I'm B!")

In [44]: c = C()

In [46]: c.b = 42

In [47]: c.b
Out[47]: 42

In [49]: print(C.b.__doc__)
Hi, I'm B!

Następnie wywołanie interaktywności Pythona helpda:

Help on class C in module __main__:

class C
 |  Data descriptors defined here:
 |  
 |  a
 |      Hi, I'm A!
 |  
 |  b
 |      Hi, I'm B!

co myślę, że powinno być tym, czego szukasz.

Edycja : teraz zdaję sobie sprawę, że być może możemy mypropw ogóle uniknąć konieczności przekazywania pierwszego argumentu , ponieważ nazwa wewnętrzna nie ma znaczenia. Jeśli kolejne wywołania mypropmogą w jakiś sposób komunikować się ze sobą, może automatycznie zdecydować o długiej i mało prawdopodobnej nazwie atrybutu wewnętrznego. Jestem pewien, że istnieją sposoby na wdrożenie tego, ale nie jestem pewien, czy są tego warte.

gerrit
źródło