Herdar docstrings na herança de classe Python

97

Estou tentando fazer alguma herança de classe em Python. Eu gostaria que cada classe e classe herdada tivessem boas docstrings. Então eu acho que para a classe herdada, eu gostaria que:

  • herdar a docstring da classe base
  • talvez anexe documentação extra relevante ao docstring

Existe alguma maneira (possivelmente elegante ou pythônica) de fazer esse tipo de manipulação de docstring em uma situação de herança de classe? Que tal para herança múltipla?

Craig McQueen
fonte
2
Não posso responder porque a pergunta foi encerrada infelizmente, mas a partir do Python 3.5, inspect.getdocpesquisarei a árvore de herança até encontrar uma docstring.
gerrit
1
Veja esta resposta .
gerrit

Respostas:

39

Você não é o único! Houve uma discussão comp.lang.pythonsobre isso há algum tempo e uma receita foi criada. Confira aqui .

"""
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
fonte
Isso é ótimo para um método herdar a docstring do método da classe pai. Isso seria útil em muitos casos, eu acho. Eu estava pensando mais sobre a docstring para toda a classe, onde gostaria de herdar e anexar.
Craig McQueen
Ah, entendi. Nesse caso, a maior parte da geração de documentos já faz isso por você.
John Feminella
36

Você pode concatenar as docstrings facilmente:

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

No entanto, isso é inútil. A maioria das ferramentas de geração de documentação ( Sphinx e Epydoc incluídos) já puxará docstring pai, incluindo métodos. Portanto, você não precisa fazer nada.

nosklo
fonte
16
Na verdade, a maioria das ferramentas de documentação faz isso. Mas a função interna help () não.
MarioVilas
2
@MarioVilas: talvez seja um bug que deva ser relatado?
naught101
Sphinx não parece estar fazendo isso por mim, talvez porque meu pai seja "privado", também conhecido como nome começa com um sublinhado.
Gringo Suave
6

Não é particularmente elegante, mas simples e direto:

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

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

Agora:

>>> print Y.__doc__
This class has a method foo(). Also bar().
Alex Martelli
fonte
Se você quiser fazer isso também para o Init docstring, há uma maneira de fazer isso na definição de Y? A única maneira que consegui fazer é __init__.__doc__ = X.__init__.__doc__ + " Also another param"seguindo a __init__definição em, Ymas isso parece bagunçar a formatação, causando espaços extras adicionados.
mgilbert de
5

Um estilo misto que pode preservar a sintaxe de docstring herdada e a ordem preferencial pode 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

Com a mesma saída de Alex:

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

Gelo fino: brincar com docstring pode tornar seu módulo inutilizável com python -OO, espere alguns:

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

Eu escrevi custom_inherit para fornecer algumas ferramentas simples e leves para lidar com a herança de docstring.

Ele também vem com alguns estilos padrão legais para mesclar diferentes tipos de docstrings (por exemplo, Numpy, Google e docstrings formatados com reST). Você também pode fornecer seu próprio estilo com muita facilidade.

Sobrepor seções docstring irá adiar para a seção filho, caso contrário, elas são mescladas com uma boa formatação.

Ryan Soklaski
fonte