programacion - Decorador de clase Python
metodos en python (6)
Además de la cuestión de si los decoradores de clase son la solución correcta para su problema:
En Python 2.6 y superior, hay decoradores de clase con @ -syntax, por lo que puede escribir:
@addID
class Foo:
pass
En versiones anteriores, puede hacerlo de otra manera:
class Foo:
pass
Foo = addID(Foo)
Sin embargo, tenga en cuenta que esto funciona igual que para los decoradores de funciones, y que el decorador debe devolver la clase nueva (o original modificado), que no es lo que está haciendo en el ejemplo. El decorador addID se vería así:
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
Luego puede usar la sintaxis apropiada para su versión de Python como se describe arriba.
Pero estoy de acuerdo con otros en que la herencia es más adecuada si quieres anular __init__
.
En Python 2.5, ¿hay alguna manera de crear un decorador que decore una clase? Específicamente, quiero usar un decorador para agregar un miembro a una clase y cambiar el constructor para tomar un valor para ese miembro.
Buscando algo como lo siguiente (que tiene un error de sintaxis en ''clase 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 __name__ == ''__main__'':
foo1 = Foo(5,1)
print foo1.value1, foo1.getId()
foo2 = Foo(15,2)
print foo2.value1, foo2.getId()
Creo que lo que realmente busco es una forma de hacer algo como una interfaz C # en Python. Necesito cambiar mi paradigma, supongo.
De hecho, hay una implementación bastante buena de un decorador de clase aquí:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
De hecho, creo que esta es una implementación bastante interesante. Debido a que subclasifica la clase que decora, se comportará exactamente como esta clase en cosas como verificaciones de isinstance
.
Tiene un beneficio adicional: no es raro que la sentencia __init__
en un Formulario django personalizado haga modificaciones o adiciones a self.fields
así que es mejor que los cambios en self.fields
sucedan después de que __init__
haya ejecutado para la clase en cuestión.
Muy inteligente.
Sin embargo, en su clase realmente desea que la decoración altere el constructor, lo que no creo que sea un buen caso de uso para un decorador de clase.
Esa no es una buena práctica y no hay un mecanismo para hacerlo por eso. La forma correcta de lograr lo que quieres es la herencia.
Eche un vistazo a la documentación de la clase .
Un pequeño ejemplo:
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 esta forma, Boss tiene todo lo que tiene Employee
, con su propio método __init__
y sus propios miembros.
Estoy de acuerdo en que la herencia es una mejor opción para el problema planteado.
Sin embargo, encontré esta pregunta realmente útil en la decoración de clases, gracias a todos.
Aquí hay otro par de ejemplos, basados en otras respuestas, que incluyen cómo la herencia afecta las cosas en Python 2.7, (y @wraps , que mantiene la cadena de documentos de la función original, 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...
A menudo quieres agregar parámetros a tu decorador:
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()
Productos:
@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
He usado un decorador de funciones, ya que las encuentro más concisas. Aquí hay una clase para decorar una clase:
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
Una versión más robusta que busca esos paréntesis, y funciona si los métodos no existen en la clase decorada:
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
La assert
comprueba que el decorador no se haya utilizado sin paréntesis. Si lo tiene, la clase que se está decorando se pasa al parámetro msg
del decorador, lo que genera un AssertionError
.
@decorate_if
solo aplica el decorator
si la condition
evalúa como True
.
El getattr
, prueba callable
y @decorate_if
se utilizan para que el decorador no se rompa si el método foo()
no existe en la clase que se está decorando.
Me gustaría secundar la idea de que es posible que desee considerar una subclase en lugar del enfoque que ha descrito. Sin embargo, sin conocer su escenario específico, YMMV :-)
Lo que estás pensando es una metaclase. La función __new__
en una metaclase pasa la definición propuesta completa de la clase, que luego puede reescribir antes de que se cree la clase. En ese momento, puede subcontratar al constructor para obtener uno nuevo.
Ejemplo:
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
Reemplazar el constructor es tal vez un poco dramático, pero el lenguaje proporciona soporte para este tipo de profunda introspección y modificación dinámica.
Nadie ha explicado que se pueden definir clases dinámicamente. Entonces puede tener un decorador que defina (y regrese) una subclase:
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
Que se puede usar en Python 2 (el comentario de Blckknght que explica por qué debería continuar haciendo esto en 2.6+) de esta manera:
class Foo:
pass
FooId = addId(Foo)
Y en Python 3 de esta manera (pero tenga cuidado de usar super()
en sus clases):
@addId
class Foo:
pass
Así que puedes tener tu pastel y comértelo: ¡herencia y decoradores!