【问题标题】:How to automatically document my class properties with decorators如何使用装饰器自动记录我的类属性
【发布时间】:2014-06-25 10:27:54
【问题描述】:

假设我有两个类(A & B),其中第二个是从第一个派生的。此外,我通过在 B 中编写新的实现来隐藏在 A 中实现的一些属性。但是,我为 A 编写的文档字符串对 B 仍然有效,而且我很懒——我不想复制粘贴所有内容。

请注意,这里的一个关键问题是我的目标是属性,对于一般的类方法,已经发布了解决方案。

在代码中,类的最小示例可能如下所示:

In [1]: class A(object):
   ...:     @property
   ...:     def a(self):
   ...:         "The a:ness of it"
   ...:         return True
   ...:     

In [2]: class B(A):
   ...:     @property
   ...:     def a(self):
   ...:         return False
   ...:     

我基本上想要的是有办法让以下事情发生:

In [8]: B.a.__doc__
Out[8]: 'The a:ness of it'

实际上,B.a 的文档字符串是空的,并且无法写入 B.a.__doc__,因为它会引发 TypeError

据我所知,解决方案如下:

from inspect import getmro

def inheritDocFromA(f):
    """This decorator will copy the docstring from ``A`` for the
    matching function ``f`` if such exists and no docstring has been added
    manually.
    """
    if f.__doc__ is None:

        fname = f.__name__

        for c in getmro(A):
            if hasattr(c, fname):
                d = getattr(c, fname).__doc__
                if d is not None:
                    f.__doc__ = d
                    break

    return f

这确实有效,但是因为A 被硬编码到我发现的装饰器函数中,所以很难看 无法知道f 传递给装饰器时附加到哪个类:

In [15]: class C(A):
   ....:     @property
   ....:     @inheritDocFromA
   ....:     def a(self):
   ....:         return False
   ....:     

In [16]: C.a.__doc__
Out[16]: 'The a:ness of it'

问题: 是否可以为在类属性上应用装饰器的文档字符串构建一个通用解决方案,而无需在继承到装饰器函数中进行硬编码?

我也尝试过装饰类,但后来我遇到了属性文档字符串被写保护的问题。

最后,如果可能的话,我希望该解决方案同时适用于 Python 2.7 和 3.4。

【问题讨论】:

  • OT:当有类型​​对象的公共__mro__ 属性时,为什么要使用inspect.getmro? AFAIK inspect 最好保留用于没有用于访问某些功能的公共 API 或 API 是特定于 cpython 的情况。
  • 一个解决方案是<class>.a 返回一个代理对象,其__doc__ 返回T.__dict__.a.__doc__,T 是<class>.__mro__ 中的第一个类型,其T.__dict__.a.__doc__ 不是None。这消除了继承层次结构的硬编码,这是您所要求的,但代价是引入了代理类和过度工程的味道。当我使用真正的键盘时,我会尝试将其写成答案。
  • @JamesMills 从那个问题接受的解决方案在这里不起作用。正如 OP 所指出的:我也尝试过装饰类,但后来我遇到了属性文档字符串被写保护的问题。
  • 这里也一样.. 我自己也有一些想法。似乎他们在 Python 3 中解决了几个或更多关于此问题的问题......

标签: python python-decorators docstring


【解决方案1】:

可以编写一个装饰器,在通过类访问时返回正确的__doc__——毕竟,__get__ 接收类型并可以通过其 MRO,找到合适的__doc__,并将其设置在自身上(或在为此目的创建和返回的代理上)。但是解决__doc__不可写的问题要简单得多。

事实证明,由于property 是作为一种类型实现的,因此使其实例的__doc__ 可写就像从它继承一样简单:

class property_writable_doc(property):
    pass

那么您使用类装饰器继承__doc__ 属性的想法就可以工作了:

def inherit_doc_class(cls):
    for name, obj in cls.__dict__.iteritems():
        if isinstance(obj, property_writable_doc) and obj.__doc__ is None:
            for t in cls.__mro__:
                if name in t.__dict__ and t.__dict__[name].__doc__ is not None:
                    obj.__doc__ = t.__dict__[name].__doc__
                    break
    return cls

class A(object):
    @property
    def a(self):
        "The a:ness of it"
        return True

@inherit_doc_class
class B(A):
    @property_writable_doc
    def a(self):
        return False

@inherit_doc_class
class C(A):
    @property_writable_doc
    def a(self):
        "The C:ness of it"
        return False

【讨论】:

  • 哇,我得花点时间消化它。如果我正确理解了实现,请求的代理对象可以被存储以供将来重复使用,以牺牲一些内存来节省一些性能?
  • @deinonychusaur 您不需要关心创建代理的性能,只有当您通过B 等类访问a 时才会创建代理。当您通过类的instance 访问a 时,不会创建额外的代理。但是您可能需要担心的是每个属性实例都会经过一个额外的 Python 函数调用。但除了编写一个 C 扩展之外,没有其他办法。
  • 另外,请参阅“替代方法”以获得更简单的实现,希望更容易消化。
  • 我只是写信评论,最后一个版本,如果我做对了,可以解决你所说的间接费用,对吗?我注意到在BC 上写@a.setter 效果很好。感谢您的精彩回答!
  • @deinonychusaur 实际上,最后一个版本没有查找开销。我想我会从答案中删除以前的版本,然后留下那个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-19
  • 2019-10-08
  • 1970-01-01
  • 2017-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多