¿Se puede llamar como método?
Pregunta
Digamos que tenemos una metaclase CallableWrappingMeta
que recorre el cuerpo de una nueva clase, envolviendo sus métodos con una clase, 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!'
Nuestro contenedor ficticio solo imprime los argumentos a medida que entran. Pero notará algo llamativo: el método no pasa el receptor de instancia-objeto, porque a pesar de que InstanceMethodWrapper
es invocable, no se trata como una función con el propósito de convertirse en un método de instancia durante la creación de la clase (después de que nuestra metaclase haya terminado con ella).
Una solución potencial es usar un decorador en lugar de una clase para envolver los métodos, esa función se convertirá en un método de instancia. Pero en el mundo real, InstanceMethodWrapper
es mucho más complejo: proporciona una API y publica eventos de llamadas a métodos. Una clase es más conveniente (y más eficaz, no que esto importe mucho).
También probé algunos callejones sin salida. La subclasificación de types.MethodType
y types.UnboundMethodType
no fue a ninguna parte. Un poco de introspección, y parece que descienden de type
. Así que intenté usar ambos como metaclase, pero tampoco tuve suerte. Puede ser que tengan demandas especiales como metaclase, pero parece que estamos en territorio indocumentado en este punto.
¿Alguna idea?
Solución
Solo te enriquecemos con la clase InstanceMethodWrapper
con un __get__
(que puede perfectamente bien solo return self
), es decir, convierte esa clase en un tipo descriptor , de modo que sus instancias sean objetos descriptores. Consulte http://users.rcn.com/python/download/Descriptor.htm para antecedentes y detalles.
Por cierto, si estás en Python 2.6 o mejor, considera usar un decorador de clase en lugar de esa metaclase. Agregamos decoradores de clase exactamente porque se utilizaron muchas metaclases solo para dichos fines de decoración, y los decoradores son realmente muy buenos. más simple de usar.
Otros consejos
Editar : miento otra vez. Los atributos __? Attr__
en las funciones son de solo lectura, pero aparentemente no siempre arrojan una excepción AttributeException
cuando asigna? No se. ¡De vuelta al punto de partida!
Editar : En realidad, esto no resuelve el problema, ya que la función de ajuste no enviará solicitudes de atributos al InstanceMethodWrapper
. Podría, por supuesto, golpear los atributos __? Attr__
en el decorador, y es lo que estoy haciendo ahora, pero eso es feo. Las mejores ideas son bienvenidas.
Por supuesto, inmediatamente me di cuenta de que combinar un decorador simple con nuestras clases hará el truco:
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
Luego agrega el decorador a la llamada a InstanceMethodWrapper
en la metaclase:
cls_dict[k] = methodize(v, InstanceMethodWrapper(v))
Poof. Un poco oblicuo, pero funciona.
Supongo que estás tratando de hacer una metaclase que envuelva todos los métodos de la clase con una función personalizada.
Aquí está mi versión, que creo que es un poco menos oblicua.
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")
Creo que debes ser más específico sobre tu problema. La pregunta original habla sobre ajustar una función, pero su respuesta posterior parece hablar sobre preservar los atributos de la función, lo que parece ser un factor nuevo. Si explica con mayor claridad sus objetivos de diseño, podría ser más fácil responder a su pregunta.