Usando a docstring de um método para substituir automaticamente a de outro método
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.
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!"