使用一种方法的文档字符串自动覆盖另一种方法的文档字符串

时间:2020-03-05 18:55:48  来源:igfitidea点击:

问题:我有一个包含模板方法" execute"的类,该方法调用了另一个方法" _execute"。子类应该覆盖_execute以实现某些特定功能。此功能应记录在_execute的文档字符串中。
高级用户可以创建自己的子类来扩展库。但是,另一个处理此类子类的用户只能使用" execute",因此,如果他使用" help(execute)",则不会看到正确的文档字符串。

因此,最好以这样的方式修改基类,以便在子类中将execute的文档字符串自动替换为_execute的文档字符串。任何想法如何做到这一点?

我正在考虑使用元类来做到这一点,以使它对用户完全透明。

解决方案

回答

那么文档字符串存储在__doc__中,因此事后基于_execute的文档字符串重新分配它并不难。

基本上:

`

class MyClass(object):
    def execute(self):
        '''original doc-string'''
        self._execute()

class SubClass(MyClass):
    def _execute(self):
        '''sub-class doc-string'''
        pass

    # re-assign doc-string of execute
    def execute(self,*args,**kw):
        return MyClass.execute(*args,**kw)
    execute.__doc__=_execute.__doc__

`

必须将Execute重新声明为doc字符串添加到SubClass而不是MyClass的execute版本上(否则会干扰其他子类)。

这不是一个很整洁的方法,但是从库用户的POV来看,它应该可以提供理想的结果。然后,我们可以将其包装在元类中,以使子类化的人员更轻松地进行操作。

回答

我们是否有理由不能直接覆盖基类的execute函数?

class Base(object):
    def execute(self):
        ...

class Derived(Base):
    def execute(self):
        """Docstring for derived class"""
        Base.execute(self)
        ...stuff specific to Derived...

如果我们不想执行上述操作:

方法对象不支持写入__doc__属性,因此我们必须在实际函数对象中更改__doc__属性。由于我们不想覆盖基类中的那个,因此我们必须为每个子类赋予其自己的execute副本:

class Derived(Base):
    def execute(self):
        return Base.execute(self)

    class _execute(self):
        """Docstring for subclass"""
        ...

    execute.__doc__= _execute.__doc__

但这类似于重新定义执行的回旋方式...

回答

好吧,如果我们不介意在子类中复制原始方法,则可以使用以下技术。

import new

def copyfunc(func):
    return new.function(func.func_code, func.func_globals, func.func_name,
                        func.func_defaults, func.func_closure)

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        for key in attrs.keys():
            if key[0] == '_':
                skey = key[1:]
                for base in bases:
                    original = getattr(base, skey, None)
                    if original is not None:
                        copy = copyfunc(original)
                        copy.__doc__ = attrs[key].__doc__
                        attrs[skey] = copy
                        break
        return type.__new__(meta, name, bases, attrs)

class Class(object):
    __metaclass__ = Metaclass
    def execute(self):
        '''original doc-string'''
        return self._execute()

class Subclass(Class):
    def _execute(self):
        '''sub-class doc-string'''
        pass

回答

我同意最简单,最Python化的方法是在子类中重新定义execute,并让它调用基类的execute方法:

class Sub(Base):
    def execute(self):
        """New docstring goes here"""
        return Base.execute(self)

这是完成所需内容的代码很少。唯一的缺点是我们必须在扩展Base的每个子类中重复此代码。但是,这是为我们想要的行为付出的很小的代价。

如果我们需要一种草率而冗长的方式来确保动态生成要执行的文档字符串,则可以使用描述符协议,该协议的代码量明显少于此处的其他建议。这很烦人,因为我们不能仅在现有函数上设置描述符,这意味着必须使用__call__方法将execute编写为单独的类。

这是执行此操作的代码,但请记住,我上面的示例更简单,更Pythonic:

class Executor(object):
    __doc__ = property(lambda self: self.inst._execute.__doc__)

    def __call__(self):
        return self.inst._execute()

class Base(object):
    execute = Executor()

class Sub(Base):
    def __init__(self):
        self.execute.inst = self

    def _execute(self):
        """Actually does something!"""
        return "Hello World!"

spam = Sub()
print spam.execute.__doc__  # prints "Actually does something!"
help(spam)                  # the execute method says "Actually does something!"

回答

看一下functools.wraps()装饰器;它可以完成所有这些工作,但是我不知道是否可以让它在正确的环境中运行