Mewarisi docstrings dalam pewarisan kelas Python

97

Saya mencoba melakukan warisan kelas dengan Python. Saya ingin setiap kelas dan kelas yang diwariskan memiliki dokumen yang bagus. Jadi saya pikir untuk kelas yang diwariskan, saya ingin:

  • mewarisi kelas dasar docstring
  • mungkin menambahkan dokumentasi tambahan yang relevan ke docstring

Apakah ada cara (mungkin elegan atau pythonic) untuk melakukan manipulasi docstring semacam ini dalam situasi pewarisan kelas? Bagaimana dengan multiple inheritance?

Craig McQueen
sumber
2
Saya tidak bisa menjawab karena pertanyaannya sayangnya ditutup, tetapi pada Python 3.5, inspect.getdocakan mencari pohon warisan sampai menemukan docstring.
gerrit
1
Lihat jawaban ini .
gerrit

Jawaban:

39

Kamu bukanlah satu - satunya! Ada diskusi comp.lang.pythontentang ini beberapa waktu yang lalu, dan resep telah dibuat. Lihat di sini .

"""
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
sumber
Itu rapi untuk sebuah metode untuk mewarisi docstring metode kelas induk. Itu akan berguna dalam banyak kasus menurut saya. Saya lebih memikirkan docstring untuk seluruh kelas, di mana saya ingin mewarisi dan menambahkan.
Craig McQueen
Ah, mengerti. Dalam hal ini, sebagian besar generasi dokumen sudah melakukannya untuk Anda.
John Feminella
36

Anda dapat menggabungkan docstrings dengan mudah:

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

Namun, itu tidak berguna. Kebanyakan alat pembuatan dokumentasi ( termasuk Sphinx dan Epydoc ) sudah akan menarik docstring induk, termasuk untuk metode. Jadi Anda tidak perlu melakukan apapun.

nosklo.dll
sumber
16
Memang, sebagian besar alat dokumentasi melakukan itu. Tetapi fungsi bantuan () bawaan tidak.
MarioVilas
2
@ MarioVilas: mungkin itu bug yang harus dilaporkan?
n nothing101
Sphinx tampaknya tidak melakukan itu untuk saya, mungkin karena orang tua saya bersifat "pribadi" alias nama yang dimulai dengan garis bawah.
Gringo Suave
6

Tidak terlalu elegan, tetapi sederhana dan langsung:

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

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

Sekarang:

>>> print Y.__doc__
This class has a method foo(). Also bar().
Alex Martelli
sumber
Jika Anda ingin melakukan ini Init docstringjuga, apakah ada cara untuk melakukannya dalam definisi Y? Satu-satunya cara saya dapat melakukannya adalah dengan __init__.__doc__ = X.__init__.__doc__ + " Also another param"mengikuti __init__definisi di Ytetapi ini tampaknya mengacaukan pemformatan, menyebabkan ruang tambahan ekstra.
mgilbert
5

Stile campuran yang dapat mempertahankan sintaks docstring yang diwarisi dan pengurutan yang disukai dapat berupa:

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

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

Dengan keluaran yang sama dengan keluaran Alex:

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

Es tipis: bermain dengan docstring dapat membuat modul Anda tidak dapat digunakan dengan python -OO, mengharapkan beberapa:

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

Saya menulis custom_inherit untuk menyediakan beberapa alat sederhana dan ringan untuk menangani pewarisan docstring.

Itu juga datang dengan beberapa gaya default yang bagus untuk menggabungkan berbagai jenis docstring (misalnya Numpy, Google, dan docstrings yang diformat ulang). Anda juga dapat memberikan gaya Anda sendiri dengan sangat mudah.

Bagian docstring yang tumpang tindih akan tunduk pada bagian anak, jika tidak maka akan digabungkan dengan format yang bagus.

Ryan Soklaski
sumber