Usando a docstring de um método para substituir automaticamente a de outro método

StackOverflow https://stackoverflow.com/questions/71817

  •  09-06-2019
  •  | 
  •  

Pergunta

O problema:Eu tenho uma classe que contém um método de modelo execute que chama outro método _execute.As subclasses devem sobrescrever _execute para implementar alguma funcionalidade específica.Esta funcionalidade deve ser documentada na documentação de _execute.Usuários avançados podem criar suas próprias subclasses para estender a biblioteca.No entanto, outro usuário lidando com tal subclasse deve usar apenas execute, então ele não verá a doutrina correta se usar help(execute).

Portanto, seria bom modificar a classe base de tal forma que em uma subclasse a docstring de execute é automaticamente substituído pelo de _execute.Alguma idéia de como isso pode ser feito?

Eu estava pensando em metaclasses para fazer isso, para deixar isso completamente transparente para o usuário.

Foi útil?

Solução

Bem, se você não se importa em copiar o método original na subclasse, você pode usar a seguinte técnica.

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

Outras dicas

Existe uma razão pela qual você não pode substituir a classe base execute funcionar diretamente?

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

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

Se você não quiser fazer o acima:

Objetos de método não suportam gravação no __doc__ atributo, então você tem que mudar __doc__ no objeto de função real.Como você não deseja substituir aquele da classe base, você teria que dar a cada subclasse sua própria cópia execute:

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

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

    execute.__doc__= _execute.__doc__

mas isso é semelhante a uma forma indireta de redefinir execute...

Veja o decorador functools.wraps();ele faz tudo isso, mas não sei de antemão se você consegue fazê-lo funcionar no contexto certo

Bem, a doc-string é armazenada em __doc__ então não seria muito difícil reatribuí-lo com base na sequência de documentos de _execute depois do ocorrido.

Basicamente:

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 deve ser declarado novamente para que a string do documento seja anexada à versão de execute para o SubClass e não para MyClass (o que de outra forma interferiria em outras subclasses).

Essa não é uma maneira muito organizada de fazer isso, mas do ponto de vista do usuário de uma biblioteca deve dar o resultado desejado.Você poderia então agrupar isso em uma metaclasse para facilitar para as pessoas que estão subclassificando.

Concordo que a maneira mais simples e Python de abordar isso é simplesmente redefinir execute em suas subclasses e fazer com que ele chame o método execute da classe base:

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

Este é muito pouco código para realizar o que você deseja;a única desvantagem é que você deve repetir esse código em todas as subclasses que estendem Base.No entanto, este é um pequeno preço a pagar pelo comportamento que você deseja.

Se você deseja uma maneira desleixada e detalhada de garantir que a docstring para execução seja gerada dinamicamente, você pode usar o protocolo descritor, que seria significativamente menos código do que as outras propostas aqui.Isso é irritante porque você não pode simplesmente definir um descritor em uma função existente, o que significa que execute deve ser escrito como uma classe separada com um __call__ método.

Aqui está o código para fazer isso, mas lembre-se de que meu exemplo acima é muito mais simples e mais 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!"
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top