appelable comme instancemethod?
Question
Supposons que nous ayons une métaclasse CallableWrappingMeta ??code> qui parcourt le corps d'une nouvelle classe, encapsulant ses méthodes avec une classe,
InstanceMethodWrapper
:
import types
class CallableWrappingMeta(type):
def __new__(mcls, name, bases, cls_dict):
for k, v in cls_dict.iteritems():
if isinstance(v, types.FunctionType):
cls_dict[k] = InstanceMethodWrapper(v)
return type.__new__(mcls, name, bases, cls_dict)
class InstanceMethodWrapper(object):
def __init__(self, method):
self.method = method
def __call__(self, *args, **kw):
print "InstanceMethodWrapper.__call__( %s, *%r, **%r )" % (self, args, kw)
return self.method(*args, **kw)
class Bar(object):
__metaclass__ = CallableWrappingMeta
def __init__(self):
print 'bar!'
Notre enveloppe factice imprime simplement les arguments au fur et à mesure qu'ils arrivent. Mais vous remarquerez quelque chose de remarquable: la méthode n'est pas transmise au destinataire d'instance-objet, car même si InstanceMethodWrapper
est appelable, il n’est pas traité comme une fonction dans le but d’être converti en une méthode d’instance lors de la création de la classe (après que notre métaclasse soit terminée avec elle).
Une solution potentielle consiste à utiliser un décorateur au lieu d'une classe pour envelopper les méthodes - cette fonction deviendra une méthode d'instance. Mais dans le monde réel, InstanceMethodWrapper
est beaucoup plus complexe: il fournit une API et publie des événements d'appel de méthode. Une classe est plus pratique (et plus performante, cela ne compte pas beaucoup).
J'ai aussi essayé des impasses. Les sous-classes types.MethodType
et types.UnboundMethodType
ne sont allés nulle part. Un peu d’introspection, et il semble qu’ils soient issus de type
. J'ai donc essayé d'utiliser les deux comme métaclasse, mais sans succès non plus. Il se peut qu’ils aient des exigences particulières en tant que métaclasse, mais il semble que nous soyons en territoire non documenté à ce stade.
Des idées?
La solution
Enrichissez votre classe InstanceMethodWrapper
avec un __ get __
(qui peut parfaitement bien se retourner
) - autrement dit, transformez cette classe en un type descripteur , afin que ses instances soient des objets descripteurs. Voir http://users.rcn.com/python/download/Descriptor.htm pour le fond et les détails.
BTW, si vous utilisez Python 2.6 ou une version supérieure, envisagez d'utiliser un décorateur de classe au lieu de cette métaclasse - nous avons ajouté des décorateurs de classe exactement parce qu'un grand nombre de métaclasses étaient utilisées uniquement à cette fin, et les décorateurs sont vraiment beaucoup plus simple à utiliser.
Autres conseils
Modifier : je mens encore une fois. Les attributs __? Attr __
des fonctions sont en lecture seule, mais apparemment, ils ne génèrent pas toujours une exception AttributeException
lorsque vous affectez? Je ne sais pas. Retour à la case départ!
Modifier : cela ne résout pas le problème car la fonction d'habillage ne refusera pas les demandes d'attributs proxy à InstanceMethodWrapper
. Je pourrais bien sûr écraser les attributs __? Attr __
dans le décorateur - et c'est ce que je fais maintenant - mais c'est moche. Les meilleures idées sont les bienvenues.
Bien sûr, j'ai tout de suite compris que combiner un décorateur simple avec nos classes ferait l'affaire:
def methodize(method, callable):
"Circumvents the fact that callables are not converted to instance methods."
@wraps(method)
def wrapper(*args, **kw):
return wrapper._callable(*args, **kw)
wrapper._callable = callable
return wrapper
Vous ajoutez ensuite le décorateur à l'appel de InstanceMethodWrapper
dans la métaclasse:
cls_dict[k] = methodize(v, InstanceMethodWrapper(v))
Poof. Un peu oblique, mais ça marche.
Je suppose que vous essayez de créer une métaclasse qui enveloppe chaque méthode de la classe avec une fonction personnalisée.
Voici ma version qui, à mon avis, est un peu moins oblique.
import types
class CallableWrappingMeta(type):
def __new__(mcls, name, bases, cls_dict):
instance = type.__new__(mcls, name, bases, cls_dict)
for k in dir(instance):
v = getattr(instance, k)
if isinstance(v, types.MethodType):
setattr(instance, k, instanceMethodWrapper(v))
return instance
def instanceMethodWrapper(function):
def customfunc(*args, **kw):
print "instanceMethodWrapper(*%r, **%r )" % (args, kw)
return function(*args, **kw)
return customfunc
class Bar(object):
__metaclass__ = CallableWrappingMeta
def method(self, a, b):
print a,b
a = Bar()
a.method("foo","bar")
Je pense que vous devez être plus précis sur votre problème. La question initiale parle d'encapsuler une fonction, mais votre réponse suivante semble parler de la préservation des attributs de la fonction, ce qui semble être un nouveau facteur. Si vous définissez plus clairement vos objectifs de conception, il sera peut-être plus facile de répondre à votre question.