Utiliser la docstring d'une méthode pour écraser automatiquement celle d'une autre méthode
Question
Le problème: j'ai une classe qui contient une méthode modèle execute
qui appelle une autre méthode _execute
. Les sous-classes sont supposées écraser _execute
pour implémenter des fonctionnalités spécifiques. Cette fonctionnalité doit être documentée dans la docstring de _execute
.
Les utilisateurs avancés peuvent créer leurs propres sous-classes pour étendre la bibliothèque. Cependant, un autre utilisateur traitant avec une telle sous-classe ne devrait utiliser que execute
. Il ne verra donc pas la docstring correcte s'il utilise help (execute)
.
Par conséquent, il serait intéressant de modifier la classe de base de telle sorte que, dans une sous-classe, la chaîne de documents de execute
soit automatiquement remplacée par celle de _execute
. Des idées sur la façon dont cela pourrait être fait?
Je pensais aux métaclasses pour faire cela, pour rendre cela complètement transparent pour l'utilisateur.
La solution
Eh bien, si la copie de la méthode d'origine dans la sous-classe ne vous dérange pas, vous pouvez utiliser la technique suivante.
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
Autres conseils
Y a-t-il une raison pour laquelle vous ne pouvez pas remplacer directement la fonction execute
de la classe de base?
class Base(object):
def execute(self):
...
class Derived(Base):
def execute(self):
"""Docstring for derived class"""
Base.execute(self)
...stuff specific to Derived...
Si vous ne voulez pas faire ce qui précède:
Les objets de méthode ne supportant pas l'écriture dans l'attribut __ doc __
, vous devez donc modifier __ doc __
dans l'objet de fonction réel. Puisque vous ne voulez pas remplacer celui de la classe de base, vous devez donner à chaque sous-classe sa propre copie de execute
:
class Derived(Base):
def execute(self):
return Base.execute(self)
class _execute(self):
"""Docstring for subclass"""
...
execute.__doc__= _execute.__doc__
mais cela ressemble à un moyen détourné de redéfinir execute
...
Regardez le décorateur functools.wraps (); il fait tout cela, mais je ne sais pas si vous pouvez le faire tourner dans le bon contexte
La chaîne de documentation est stockée dans __ doc __
afin qu'il ne soit pas trop difficile de la réaffecter en fonction de la chaîne de documentation de _execute
après coup. .
Fondamentalement:
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 doit être à nouveau déclaré en indiquant que la chaîne de documentation est attachée à la version de execute pour la SubClass
et non pour MyClass
(qui interfèrerait sinon avec d'autres sous-classes).
Ce n'est pas une façon très ordonnée de le faire, mais du POV de l'utilisateur d'une bibliothèque, cela devrait donner le résultat souhaité. Vous pouvez ensuite résumer cela dans une méta-classe afin de faciliter la tâche des personnes qui font des sous-classes.
Je conviens que la méthode la plus simple et la plus pythonique consiste à simplement redéfinir execute dans vos sous-classes et à l'appeler la méthode execute de la classe de base:
class Sub(Base):
def execute(self):
"""New docstring goes here"""
return Base.execute(self)
Ceci est très peu de code pour accomplir ce que vous voulez; le seul inconvénient est que vous devez répéter ce code dans chaque sous-classe qui étend Base. Cependant, c’est un petit prix à payer pour le comportement que vous souhaitez.
Si vous voulez une méthode bâclée et détaillée pour vous assurer que la docstring for execute est générée de manière dynamique, vous pouvez utiliser le protocole de descripteur, qui consisterait beaucoup moins en code que les autres propositions présentées ici. Ceci est gênant car vous ne pouvez pas définir un descripteur sur une fonction existante, ce qui signifie que execute doit être écrit en tant que classe séparée avec une méthode __ call __
.
Voici le code pour le faire, mais gardez à l'esprit que mon exemple ci-dessus est beaucoup plus simple et plus pythonique:
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!"