Erben Sie Dokumentzeichenfolgen in der Python-Klassenvererbung

97

Ich versuche, eine Klassenvererbung in Python durchzuführen. Ich möchte, dass jede Klasse und jede geerbte Klasse gute Dokumentzeichenfolgen hat. Ich denke, für die geerbte Klasse möchte ich:

  • erben Sie die Basisklasse docstring
  • Fügen Sie möglicherweise relevante zusätzliche Dokumentation an die Dokumentzeichenfolge an

Gibt es eine (möglicherweise elegante oder pythonische) Möglichkeit, diese Art der Manipulation von Dokumentenstrings in einer Klassenvererbungssituation durchzuführen? Wie wäre es mit Mehrfachvererbung?

Craig McQueen
quelle
2
Ich kann nicht antworten, da die Frage leider geschlossen wurde, aber ab Python 3.5 inspect.getdocwird der Vererbungsbaum durchsucht, bis eine Dokumentzeichenfolge gefunden wird.
Gerrit
1
Siehe diese Antwort .
Gerrit

Antworten:

39

Du bist nicht der Einzige! comp.lang.pythonVor einiger Zeit gab es eine Diskussion darüber, und ein Rezept wurde erstellt. Schau es dir hier an .

"""
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
quelle
Das ist gut für eine Methode, um die Dokumentzeichenfolge der Methode der übergeordneten Klasse zu erben. Das wäre in vielen Fällen nützlich, denke ich. Ich habe mehr über die Dokumentzeichenfolge für die gesamte Klasse nachgedacht, die ich erben und anhängen möchte.
Craig McQueen
Ah, gotcha. In diesem Fall erledigen die meisten Doc-Generatoren dies bereits für Sie.
John Feminella
36

Sie können die Dokumentzeichenfolgen einfach verketten:

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

Das ist jedoch nutzlos. Die meisten Tools zur Generierung von Dokumentationen ( einschließlich Sphinx und Epydoc ) ziehen bereits übergeordnete Dokumentzeichenfolgen, auch für Methoden. Sie müssen also nichts tun.

nosklo
quelle
16
In der Tat tun dies die meisten Dokumentationstools. Die integrierte help () -Funktion funktioniert jedoch nicht.
MarioVilas
2
@MarioVilas: Vielleicht ist das ein Fehler, der gemeldet werden sollte?
naught101
Sphinx scheint das nicht für mich zu tun, vielleicht weil mein Elternteil "privat" ist, auch bekannt als Name, der mit einem Unterstrich beginnt.
Gringo Suave
6

Nicht besonders elegant, aber einfach und direkt:

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

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

Jetzt:

>>> print Y.__doc__
This class has a method foo(). Also bar().
Alex Martelli
quelle
Wenn Sie dies auch für das tun möchten Init docstring, gibt es eine Möglichkeit, dies in der Definition von zu tun Y? Die einzige Möglichkeit, dies zu tun, besteht darin, __init__.__doc__ = X.__init__.__doc__ + " Also another param"der __init__Definition in zu folgen, Yaber dies scheint die Formatierung zu beeinträchtigen und zusätzliche zusätzliche Leerzeichen zu verursachen.
mgilbert
5

Ein gemischter Stil, der sowohl die geerbte Docstring-Syntax als auch die bevorzugte Reihenfolge beibehalten kann, kann sein:

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

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

Mit der gleichen Ausgabe wie Alex:

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

Dünnes Eis: Wenn Sie mit Docstring spielen, kann Ihr Modul unbrauchbar werden python -OO. Erwarten Sie Folgendes :

TypeError: cannot concatenate 'str' and 'NoneType' objects
Naufraghi
quelle
4

Ich habe custom_inherit geschrieben , um einige einfache, leichte Tools für die Handhabung der Docstring-Vererbung bereitzustellen.

Es enthält auch einige nette Standardstile zum Zusammenführen verschiedener Arten von Dokumentzeichenfolgen (z. B. Numpy-, Google- und reST-formatierte Dokumentzeichenfolgen). Sie können auch ganz einfach Ihren eigenen Stil angeben.

Überlappende Docstring-Abschnitte werden auf den Abschnitt des Kindes verschoben, andernfalls werden sie mit einer schönen Formatierung zusammengeführt.

Ryan Soklaski
quelle