Dans Python 2.5, existe-t-il un moyen de créer un décorateur qui décore une classe? Plus précisément, je souhaite utiliser un décorateur pour ajouter un membre à une classe et modifier le constructeur en prenant une valeur pour ce membre.
Vous recherchez quelque chose comme ce qui suit (qui a une erreur de syntaxe sur 'class Foo:':
def getId(self): return self.__id
class addID(original_class):
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
original_class.__init__(self, *args, **kws)
@addID
class Foo:
def __init__(self, value1):
self.value1 = value1
if __== '__main__':
foo1 = Foo(5,1)
print foo1.value1, foo1.getId()
foo2 = Foo(15,2)
print foo2.value1, foo2.getId()
Je suppose que ce que je recherche vraiment, c'est un moyen de faire quelque chose comme une interface C # en Python. Je suppose que je dois changer de paradigme.
Deuxièmement, vous voudrez peut-être envisager une sous-classe plutôt que l’approche que vous avez décrite. Cependant, ne connaissant pas votre scénario spécifique, YMMV :-)
Vous pensez à une métaclasse. Le __new__
fonction dans une métaclasse reçoit la définition complète proposée pour la classe, qu’elle peut ensuite réécrire avant la création de la classe. Vous pouvez, à ce moment-là, remplacer le constructeur par un nouveau.
Exemple:
def substitute_init(self, id, *args, **kwargs):
pass
class FooMeta(type):
def __new__(cls, name, bases, attrs):
attrs['__init__'] = substitute_init
return super(FooMeta, cls).__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = FooMeta
def __init__(self, value1):
pass
Remplacer le constructeur est peut-être un peu spectaculaire, mais le langage fournit un support pour ce type d'introspection profonde et de modification dynamique.
Outre la question de savoir si les décorateurs de classe sont la bonne solution à votre problème:
Dans Python 2.6 et supérieur, il existe des décorateurs de classe dotés de la syntaxe @, afin que vous puissiez écrire:
@addID
class Foo:
pass
Dans les anciennes versions, vous pouvez le faire d'une autre manière:
class Foo:
pass
Foo = addID(Foo)
Notez cependant que cela fonctionne de la même manière que pour les décorateurs de fonctions et que le décorateur doit renvoyer la nouvelle classe (ou la classe d'origine modifiée), ce qui n'est pas ce que vous faites dans l'exemple. Le décorateur addID ressemblerait à ceci:
def addID(original_class):
orig_init = original_class.__init__
# Make copy of original __init__, so we can call it without recursion
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
orig_init(self, *args, **kws) # Call the original __init__
original_class.__init__ = __init__ # Set the class' __init__ to the new one
return original_class
Vous pouvez ensuite utiliser la syntaxe appropriée pour votre version Python comme décrit ci-dessus).
Mais je suis d’accord avec d’autres pour dire que l’héritage convient mieux si vous souhaitez remplacer __init__
.
Personne n'a expliqué que vous pouvez définir de manière dynamique des classes. Vous pouvez donc avoir un décorateur qui définit (et retourne) une sous-classe:
def addId(cls):
class AddId(cls):
def __init__(self, id, *args, **kargs):
super(AddId, self).__init__(*args, **kargs)
self.__id = id
def getId(self):
return self.__id
return AddId
Ce qui peut être utilisé dans Python 2 (le commentaire de Blckknght qui explique pourquoi vous devriez continuer à le faire dans la version 2.6+)) comme ceci:
class Foo:
pass
FooId = addId(Foo)
Et dans Python 3 comme ceci (mais faites attention à utiliser super()
dans vos classes)):
@addId
class Foo:
pass
Ainsi, vous pouvez avoir votre gâteau et le manger - héritage et décorateurs!
Ce n'est pas une bonne pratique et il n'y a pas de mécanisme pour le faire à cause de cela. La bonne façon d'accomplir ce que vous voulez, c'est l'héritage.
Jetez un coup d'oeil dans le documentation de la classe .
Un petit exemple:
class Employee(object):
def __init__(self, age, sex, siblings=0):
self.age = age
self.sex = sex
self.siblings = siblings
def born_on(self):
today = datetime.date.today()
return today - datetime.timedelta(days=self.age*365)
class Boss(Employee):
def __init__(self, age, sex, siblings=0, bonus=0):
self.bonus = bonus
Employee.__init__(self, age, sex, siblings)
De cette façon, Boss a tout ce que Employee
a, ainsi que sa propre méthode __init__
et ses propres membres.
Il y a en fait une très bonne implémentation d'un décorateur de classe ici:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
Je pense en fait que c'est une implémentation assez intéressante. Comme il classe la classe qu'il décore, il se comportera exactement comme cette classe dans des choses comme isinstance
chèques.
Cela présente un avantage supplémentaire: il n’est pas rare que l’instruction __init__
Dans une coutume Django Form pour apporter des modifications ou des ajouts à self.fields
, Il est donc préférable de modifier self.fields
À arriver après tout le __init__
A été exécuté pour la classe en question.
Très intelligent.
Cependant, dans votre classe, vous souhaitez réellement que la décoration modifie le constructeur, ce qui, à mon avis, n’est pas un bon cas d’utilisation pour un décorateur de classe.
Je conviens que l'héritage convient mieux au problème posé.
J'ai trouvé cette question très pratique sur les cours de décoration, merci à tous.
Voici deux autres exemples, basés sur d'autres réponses, y compris l'impact de l'héritage sur Python 2.7, (et @ wraps) , qui conserve la docstring de la fonction d'origine, etc. ):
def dec(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
@dec # No parentheses
class Foo...
Souvent, vous voulez ajouter des paramètres à votre décorateur:
from functools import wraps
def dec(msg='default'):
def decorator(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
@dec('foo decorator') # You must add parentheses now, even if they're empty
class Foo(object):
def foo(self, *args, **kwargs):
print('foo.foo()')
@dec('subfoo decorator')
class SubFoo(Foo):
def foo(self, *args, **kwargs):
print('subfoo.foo() pre')
super(SubFoo, self).foo(*args, **kwargs)
print('subfoo.foo() post')
@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
def foo(self, *args, **kwargs):
print('subsubfoo.foo() pre')
super(SubSubFoo, self).foo(*args, **kwargs)
print('subsubfoo.foo() post')
SubSubFoo().foo()
Les sorties:
@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator
J'ai utilisé un décorateur de fonction, car je les trouve plus concis. Voici une classe pour décorer une classe:
class Dec(object):
def __init__(self, msg):
self.msg = msg
def __call__(self, klass):
old_foo = klass.foo
msg = self.msg
def decorated_foo(self, *args, **kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
Une version plus robuste qui vérifie la présence de ces parenthèses et fonctionne si les méthodes n'existent pas dans la classe décorée:
from inspect import isclass
def decorate_if(condition, decorator):
return decorator if condition else lambda x: x
def dec(msg):
# Only use if your decorator's first parameter is never a class
assert not isclass(msg)
def decorator(klass):
old_foo = getattr(klass, 'foo', None)
@decorate_if(old_foo, wraps(klass.foo))
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
if callable(old_foo):
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
Le assert
vérifie que le décorateur n'a pas été utilisé sans parenthèses. Si tel est le cas, la classe en cours de décoration est transmise au paramètre msg
du décorateur, ce qui déclenche un AssertionError
.
@decorate_if
Applique uniquement le decorator
si condition
est évalué à True
.
Les tests getattr
, callable
et @decorate_if
Sont utilisés pour que le décorateur ne s'interrompe pas si la méthode foo()
n'existe pas dans la classe. être décoré.