python - programacion - ¿Cómo envolver todos los métodos de una clase?
metodos en python (4)
¿Quiere decir establecer programáticamente un contenedor a los métodos de una clase? Bueno, esta es probablemente una muy mala práctica, pero aquí es cómo puede hacerlo:
def wrap_methods( cls, wrapper ):
for key, value in cls.__dict__.items( ):
if hasattr( value, ''__call__'' ):
setattr( cls, key, wrapper( value ) )
Si tienes clase, por ejemplo.
class Test( ):
def fire( self ):
return True
def fire2( self ):
return True
y una envoltura
def wrapper( fn ):
def result( *args, **kwargs ):
print ''TEST''
return fn( *args, **kwargs )
return result
entonces llamando
wrap_methods( Test, wrapper )
aplicará wrapper
a todos los métodos definidos en la clase Test
. Utilizar con precaución! En realidad, ¡no lo uses en absoluto!
Me gustaría envolver todos los métodos de una clase en particular en Python, y me gustaría hacerlo editando el código de la clase como mínimo. ¿Cómo debo hacer esto?
El uso de decoradores de Python es el método más limpio para hacerlo, ya que parece que desea depurar o al menos rastrear el código que aparece.
Si la modificación extensa del comportamiento predeterminado de la clase es el requisito, las Clases de Meta son el camino a seguir. Aquí hay un enfoque alternativo.
Si su caso de uso se limita a simplemente envolver los métodos de instancia de una clase, podría intentar anular el método mágico __getattribute__
.
from functools import wraps
def wrapper(func):
@wraps(func)
def wrapped(*args, **kwargs):
print "Inside Wrapper. calling method %s now..."%(func.__name__)
return func(*args, **kwargs)
return wrapped
Asegúrate de usar functools.wraps
al crear envoltorios, incluso más si el envoltorio está diseñado para la depuración, ya que proporciona TraceBacks sensatos.
import types
class MyClass(object): # works only for new-style classes
def method1(self):
return "Inside method1"
def __getattribute__(self, name):
attr = super(MyClass, self).__getattribute__(name)
if type(attr) == types.MethodType:
attr = wrapper(attr)
return attr
Una forma elegante de hacerlo se describe en el blog Voidspace de Michael Foord en una entrada sobre qué son las metaclases y cómo usarlas en la sección titulada Un método para decorar una metaclase . Simplificarlo ligeramente y aplicarlo a tu situación resultó en esto:
from types import FunctionType
from functools import wraps
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwrds):
# ... <do something to/with "method" or the result of calling it>
return wrapped
class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)
class MyClass(object):
__metaclass__ = MetaClass # wrap all the methods
def method1(self, ...):
# ...etc ...
En Python, los decoradores de funciones / métodos son simplemente envoltorios de funciones más un poco de azúcar sintáctico para que usarlos sea más fácil (y más bonito).
Actualización de compatibilidad de Python 3
El código anterior utiliza la sintaxis de metaclase de Python 2.x, que debería traducirse para poder usarse en Python 3.x, sin embargo, ya no funcionará en la versión anterior. Esto significa que necesitaría usar:
class MyBase(metaclass=MetaClass)
...
en lugar de:
class MyBase(object):
__metaclass__ = MetaClass"
...
Si lo desea, es posible escribir código que sea compatible con Python 2.xy 3.x, pero hacerlo requiere una técnica un poco más complicada que crea dinámicamente una nueva clase base que hereda la metaclase deseada, evitando así errores debidos a Las diferencias de sintaxis entre las dos versiones de Python. Esto es básicamente lo que hace la función with_metaclass()
los six módulos de Benjamin Peterson.
from types import FunctionType
from functools import wraps
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwrds):
print(''{!r} executing''.format(method.__name__))
return method(*args, **kwrds)
return wrapped
class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)
def with_metaclass(meta):
""" Create an empty class with the supplied bases and metaclass. """
return type.__new__(meta, "TempBaseClass", (object,), {})
if __name__ == ''__main__'':
# Inherit metaclass from a dynamically-created base class.
class MyClass(with_metaclass(MetaClass)):
@staticmethod
def a_static_method():
pass
@classmethod
def a_class_method(cls):
pass
def a_method(self):
pass
instance = MyClass()
instance.a_static_method() # Not decorated.
instance.a_class_method() # Not decorated.
instance.a_method() # -> ''a_method'' executing