Heredar cadenas de documentos en la herencia de clases de Python

97

Estoy tratando de hacer algo de herencia de clases en Python. Me gustaría que cada clase y clase heredada tenga buenas cadenas de documentación. Así que creo que para la clase heredada, me gustaría:

  • heredar la clase base docstring
  • tal vez agregar documentación adicional relevante a la cadena de documentos

¿Hay alguna forma (posiblemente elegante o pitónica) de hacer este tipo de manipulación de cadenas de documentos en una situación de herencia de clases? ¿Qué tal la herencia múltiple?

Craig McQueen
fuente
2
No puedo responder porque desafortunadamente la pregunta se cerró, pero a partir de Python 3.5, inspect.getdocbuscará en el árbol de herencia hasta que encuentre una cadena de documentos.
gerrit
1
Vea esta respuesta .
gerrit

Respuestas:

39

¡No eres el único! Hubo una discusión comp.lang.pythonsobre esto hace un tiempo y se creó una receta. Compruébalo aquí .

"""
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
fuente
Eso es genial para que un método herede la cadena de documentos del método de la clase principal. Creo que eso sería útil en muchos casos. Estaba pensando más en la cadena de documentos para toda la clase, donde me gustaría heredar y agregar.
Craig McQueen
Ah, te pillo. En ese caso, la mayoría de la generación de documentos ya lo hace por ti.
John Feminella
36

Puede concatenar las cadenas de documentos fácilmente:

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

Sin embargo, eso es inútil. La mayoría de las herramientas de generación de documentación ( incluidas Sphinx y Epydoc ) ya extraerán la cadena de documentos principal, incluidos los métodos. Entonces no tienes que hacer nada.

nosklo
fuente
16
De hecho, la mayoría de las herramientas de documentación hacen eso. Pero la función de ayuda incorporada () no lo hace.
MarioVilas
2
@MarioVilas: ¿quizás ese es un error que debería informarse?
naught101
Sphinx no parece estar haciendo eso por mí, tal vez porque mi padre es "privado", alias, el nombre comienza con un guión bajo.
Gringo Suave
6

No particularmente elegante, pero simple y directo:

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

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

Ahora:

>>> print Y.__doc__
This class has a method foo(). Also bar().
Alex Martelli
fuente
Si también desea hacer esto Init docstring, ¿hay alguna manera de hacerlo en la definición de Y? La única forma en que he podido hacerlo es usando __init__.__doc__ = X.__init__.__doc__ + " Also another param"la __init__definición siguiente, Ypero esto parece interferir con el formato, lo que genera espacios adicionales.
mgilbert
5

Un estilo mixto que puede preservar tanto la sintaxis de la cadena de documentos heredada como el orden preferido puede ser:

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

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

Con el mismo resultado que el de Alex:

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

Hielo fino: jugar con docstring puede inutilizar su módulo con python -OO, espere algo:

TypeError: cannot concatenate 'str' and 'NoneType' objects
naufraghi
fuente
4

Escribí custom_inherit para proporcionar algunas herramientas simples y ligeras para manejar la herencia de cadenas de documentos.

También viene con algunos estilos predeterminados agradables para fusionar diferentes tipos de cadenas de documentos (por ejemplo, cadenas de documentos con formato Numpy, Google y reST). También puede proporcionar su propio estilo con mucha facilidad.

Las secciones de cadenas de documentos superpuestas se transferirán a la sección del niño, de lo contrario, se fusionarán con un formato agradable.

Ryan Soklaski
fuente