继承属性getter文档



这里提出的decorator能够继承方法的docstring,但不能继承属性和getter的docstring。

我曾试图天真地扩展它,但属性的文档字符串似乎是只读的。有办法继承这些吗?

import types
def fix_docs(cls):
    for name, func in vars(cls).items():
        if isinstance(func, (types.FunctionType, property)) and not func.__doc__:
            print func, 'needs doc'
            for parent in cls.__bases__:
                parfunc = getattr(parent, name, None)
                if parfunc and getattr(parfunc, '__doc__', None):
                    func.__doc__ = parfunc.__doc__
                    break
    return cls

class X(object):
    """
    some doc
    """
    angle = 10
    """Not too steep."""
    def please_implement(self):
        """
        I have a very thorough documentation
        :return:
        """
        raise NotImplementedError
    @property
    def speed(self):
        """
        Current speed in knots/hour.
        :return:
        """
        return 0
    @speed.setter
    def speed(self, value):
        """
        :param value:
        :return:
        """
        pass
@fix_docs
class SpecialX(X):
    angle = 30
    def please_implement(self):
        return True
    @property
    def speed(self):
        return 10
    @speed.setter
    def speed(self, value):
        self.sp = value

help(X.speed)
help(X.angle)
help(SpecialX.speed)
help(SpecialX.ange)

这只会让我

Traceback (most recent call last):
<function please_implement at 0x036101B0> needs doc
<property object at 0x035BE930> needs doc
  File "C:Program Files (x86)JetBrainsPyCharm Community Edition 2016.2helperspydevpydevd.py", line 1556, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "C:Program Files (x86)JetBrainsPyCharm Community Edition 2016.2helperspydevpydevd.py", line 940, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "C:/Users/RedX/.PyCharm2016.2/config/scratches/scratch.py", line 48, in <module>
    class SpecialX(X):
  File "C:/Users/RedX/.PyCharm2016.2/config/scratches/scratch.py", line 10, in fix_docs
    func.__doc__ = parfunc.__doc__
TypeError: readonly attribute
是的,属性文档字符串是只读的。你必须新建一处房产:
replacement = property(fget=original.fget,
                       fset=original.fset,
                       fdel=original.fdel,
                       __doc__=parentprop.__doc__)

并用它替换原来的。

最好替换原始函数的docstring,然后重新生成属性以自动传递:

original.fget.__doc__ = parentprop.__doc__
replacement = property(fget=original.fget,
                       fset=original.fset,
                       fdel=original.fdel)

此版本支持多重继承,并通过使用__mro__而不是__bases__从库的库中复制文档。

def fix_docs(cls):
    """
    This will copy all the missing documentation for methods from the parent classes.
    :param type cls: class to fix up.
    :return type: the fixed class.
    """
    for name, func in vars(cls).items():
        if isinstance(func, types.FunctionType) and not func.__doc__:
            for parent in cls.__bases__:
                parfunc = getattr(parent, name, None)
                if parfunc and getattr(parfunc, '__doc__', None):
                    func.__doc__ = parfunc.__doc__
                    break
        elif isinstance(func, property) and not func.fget.__doc__:
            for parent in cls.__bases__:
                parprop = getattr(parent, name, None)
                if parprop and getattr(parprop.fget, '__doc__', None):
                    newprop = property(fget=func.fget,
                                       fset=func.fset,
                                       fdel=func.fdel,
                                       parprop.fget.__doc__)
                    setattr(cls, name, newprop)
                    break
    return cls

测试:

import pytest

class X(object):
    def please_implement(self):
        """
        I have a very thorough documentation
        :return:
        """
        raise NotImplementedError
    @property
    def speed(self):
        """
        Current speed in knots/hour.
        :return:
        """
        return 0
    @speed.setter
    def speed(self, value):
        """
        :param value:
        :return:
        """
        pass

class SpecialX(X):
    def please_implement(self):
        return True
    @property
    def speed(self):
        return 10
    @speed.setter
    def speed(self, value):
        self.sp = value

class VerySpecial(X):
    def speed(self):
        """
        The fastest speed in knots/hour.
        :return: 100
        """
        return 100
    def please_implement(self):
        """
        I have my own words!
        :return bool: Always false.
        """
        return False
    def not_inherited(self):
        """
        Look at all these words!
        :return:
        """

class A(object):
    def please_implement(self):
        """
        This doc is not used because X is resolved first in the MRO.
        :return:
        """
        pass

class B(A):
    pass

class HasNoWords(SpecialX, B):
    def please_implement(self):
        return True
    @property
    def speed(self):
        return 10
    @speed.setter
    def speed(self, value):
        self.sp = value

def test_class_does_not_inhirit_works():
    fix_docs(X)

@pytest.mark.parametrize('clazz', [
    SpecialX,
    HasNoWords
])
def test_property_and_method_inherit(clazz):
    x = fix_docs(clazz)
    assert x.please_implement.__doc__ == """
        I have a very thorough documentation
        :return:
        """
    assert x.speed.__doc__ == """
        Current speed in knots/hour.
        :return:
        """

def test_inherited_class_with_own_doc_is_not_overwritten():
    x = fix_docs(VerySpecial)
    assert x.please_implement.__doc__ == """
        I have my own words!
        :return bool: Always false.
        """
    assert x.speed.__doc__ == """
        The fastest speed in knots/hour.
        :return: 100
        """

最新更新