Dziedziczenie ciągów dokumentów w dziedziczeniu klas w Pythonie

97

Próbuję zrobić dziedziczenie klas w Pythonie. Chciałbym, żeby każda klasa i klasa odziedziczona miała dobre dokumenty. Więc myślę, że dla klasy odziedziczonej chciałbym:

  • dziedziczy dokumentację klasy bazowej
  • może dołączyć odpowiednią dodatkową dokumentację do dokumentu

Czy istnieje jakikolwiek (prawdopodobnie elegancki lub pytoniczny) sposób wykonywania tego rodzaju manipulacji dokumentacją w sytuacji dziedziczenia klas? Co powiesz na wielokrotne dziedziczenie?

Craig McQueen
źródło
2
Nie mogę odpowiedzieć, ponieważ pytanie zostało niestety zamknięte, ale od Pythona 3.5 inspect.getdocbędzie przeszukiwać drzewo dziedziczenia, aż znajdzie ciąg dokumentów.
gerrit
1
Zobacz tę odpowiedź .
gerrit

Odpowiedzi:

39

Nie jesteś jedyny! Niedawno odbyła się dyskusja na comp.lang.pythonten temat i powstał przepis. Sprawdź to tutaj .

"""
doc_inherit decorator

Usage:

class Foo(object):
    def foo(self):
        "Frobber"
        pass

class Bar(Foo):
    @doc_inherit
    def foo(self):
        pass 

Now, Bar.foo.__doc__ == Bar().foo.__doc__ == Foo.foo.__doc__ == "Frobber"
"""

from functools import wraps

class DocInherit(object):
    """
    Docstring inheriting method descriptor

    The class itself is also used as a decorator
    """

    def __init__(self, mthd):
        self.mthd = mthd
        self.name = mthd.__name__

    def __get__(self, obj, cls):
        if obj:
            return self.get_with_inst(obj, cls)
        else:
            return self.get_no_inst(cls)

    def get_with_inst(self, obj, cls):

        overridden = getattr(super(cls, obj), self.name, None)

        @wraps(self.mthd, assigned=('__name__','__module__'))
        def f(*args, **kwargs):
            return self.mthd(obj, *args, **kwargs)

        return self.use_parent_doc(f, overridden)

    def get_no_inst(self, cls):

        for parent in cls.__mro__[1:]:
            overridden = getattr(parent, self.name, None)
            if overridden: break

        @wraps(self.mthd, assigned=('__name__','__module__'))
        def f(*args, **kwargs):
            return self.mthd(*args, **kwargs)

        return self.use_parent_doc(f, overridden)

    def use_parent_doc(self, func, source):
        if source is None:
            raise NameError, ("Can't find '%s' in parents"%self.name)
        func.__doc__ = source.__doc__
        return func

doc_inherit = DocInherit 
John Feminella
źródło
To fajne, żeby metoda dziedziczyła dokumentację metody klasy nadrzędnej. Myślę, że byłoby to przydatne w wielu przypadkach. Bardziej myślałem o dokumencie dla całej klasy, w którym chciałbym dziedziczyć i dodawać.
Craig McQueen
Ach, rozumiem. W takim przypadku większość pokolenia dokumentów już to robi.
John Feminella,
36

Możesz łatwo łączyć dokumenty:

class Foo(object):
    """
    Foo Class.
    This class foos around.
    """
    pass

class Bar(Foo):
    """
    Bar class, children of Foo
    Use this when you want to Bar around.
    parent:
    """ 
    __doc__ += Foo.__doc__
    pass

Jednak jest to bezużyteczne. Większość narzędzi do tworzenia dokumentacji (w tym Sphinx i Epydoc ) będzie już pobierać macierzysty ciąg dokumentów, w tym dla metod. Więc nie musisz nic robić.

nosklo
źródło
16
Rzeczywiście, większość narzędzi do dokumentacji to robi. Ale wbudowana funkcja help () nie.
MarioVilas
2
@MarioVilas: może to błąd, który należy zgłosić?
naught101
Wydaje się, że Sfinks nie robi tego za mnie, być może dlatego, że mój rodzic jest „prywatny”, czyli imię zaczyna się od podkreślenia.
Gringo Suave
6

Niezbyt eleganckie, ale proste i bezpośrednie:

class X(object):
  """This class has a method foo()."""
  def foo(): pass

class Y(X):
  __doc__ = X.__doc__ + ' Also bar().'
  def bar(): pass

Teraz:

>>> print Y.__doc__
This class has a method foo(). Also bar().
Alex Martelli
źródło
Jeśli chcesz to zrobić Init docstringrównież w przypadku, czy istnieje sposób, aby to zrobić w definicji Y? Jedynym sposobem udało mi się zrobić to przy użyciu __init__.__doc__ = X.__init__.__doc__ + " Also another param"zgodnie z __init__definicją zawartą w Yale to wydaje się bałagan z formatowaniem, powodując dodatkowo dodawane spacje.
mgilbert
5

Mieszany styl, który może zachować zarówno dziedziczoną składnię ciągów dokumentów, jak i preferowaną kolejność, może być:

class X(object):
  """This class has a method foo()."""
  def foo(): pass

class Y(X):
  """ Also bar()."""
  __doc__ = X.__doc__ + __doc__
  def bar(): pass

Z takim samym wyjściem jak Alex:

>>> print Y.__doc__
This class has a method foo(). Also bar().

Cienki lód: zabawa z ciągiem dokumentów może sprawić, że twój moduł będzie bezużyteczny python -OO, spodziewaj się:

TypeError: cannot concatenate 'str' and 'NoneType' objects
naufraghi
źródło
4

Napisałem custom_inherit, aby zapewnić proste, lekkie narzędzia do obsługi dziedziczenia dokumentów.

Zawiera również kilka ładnych domyślnych stylów do łączenia różnych typów ciągów dokumentów (np. Numpy, Google i reST). Możesz również bardzo łatwo nadać swój własny styl.

Nakładające się sekcje docstringowe będą odkładały się do sekcji dziecka, w przeciwnym razie zostaną połączone razem z ładnym formatowaniem.

Ryan Soklaski
źródło