python - ejemplos - django
Añadiendo dinámicamente invocables a la clase como "método" de instancia (2)
El problema con el que se está metiendo es que su objeto no está vinculado como un método de la clase Baz en la que lo está insertando. Esto se debe a que no es un descriptor, ¡ qué funciones regulares son !
Puede solucionar esto agregando un método simple __get__
a su clase Foo
que lo convierte en un método cuando se accede como un descriptor:
import types
class Foo(object):
# your other stuff here
def __get__(self, obj, objtype=None):
if obj is None:
return self # unbound
else:
return types.MethodType(self, obj) # bound to obj
Implementé una metaclase que desglosa los atributos de clase para las clases creadas con él y construye métodos a partir de los datos de esos argumentos, luego adjunta esos métodos creados dinámicamente directamente al objeto de clase (la clase en cuestión permite una fácil definición de objetos de formularios web para uso en un marco de prueba web). Ha funcionado bien, pero ahora necesito agregar un tipo de método más complejo que, para mantener las cosas limpias, lo implementé como una clase invocable. Lamentablemente, cuando trato de llamar a la clase invocable en una instancia, se trata como un atributo de clase en lugar de un método de instancia, y cuando se llama, solo recibe su propio self
. Puedo ver por qué sucede esto, pero esperaba que alguien pudiera tener una solución mejor que las que se me ocurrieron. Ilustración simplificada del problema:
class Foo(object):
def __init__(self, name, val):
self.name = name
self.val = val
self.__name__ = name + ''_foo''
self.name = name
# This doesn''t work as I''d wish
def __call__(self, instance):
return self.name + str(self.val + instance.val)
def get_methods(name, foo_val):
foo = Foo(name, foo_val)
def bar(self):
return name + str(self.val + 2)
bar.__name__ = name + ''_bar''
return foo, bar
class Baz(object):
def __init__(self, val):
self.val = val
for method in get_methods(''biff'', 1):
setattr(Baz, method.__name__, method)
baz = Baz(10)
# baz.val == 10
# baz.biff_foo() == ''biff11''
# baz.biff_bar() == ''biff12''
He pensado en:
- Usando un descriptor, pero parece mucho más complejo de lo que es necesario aquí
- Usar un cierre dentro de una fábrica para
foo
, pero los cierres anidados son reemplazos feos y desordenados para los objetos la mayor parte del tiempo, imo - Envolviendo la instancia de
Foo
en un método que pasa a la instancia deFoo
comoinstance
, básicamente decorador, eso es lo que realmente agrego aBaz
, pero eso parece superfluo y básicamente una forma más complicada de hacer lo mismo que ( 2)
¿Hay alguna manera mejor que cualquiera de estos para tratar de lograr lo que quiero, o debería simplemente morder la bala y usar algún tipo de patrón de fábrica de cierre?
Una forma de hacerlo es adjuntar los objetos invocables a la clase como métodos independientes. El constructor del método trabajará con callables arbitrarios (es decir, instancias de clases con un __call__()
), no solo funciones.
from types import MethodType
class Foo(object):
def __init__(self, name, val):
self.name = name
self.val = val
self.__name__ = name + ''_foo''
self.name = name
def __call__(self, instance):
return self.name + str(self.val + instance.val)
class Baz(object):
def __init__(self, val):
self.val = val
Baz.biff = MethodType(Foo("biff", 42), None, Baz)
b = Baz(13)
print b.biff()
>>> biff55
En Python 3, no hay tal cosa como una instancia independiente (las clases solo tienen funciones regulares adjuntas) por lo que podría hacer que su clase Foo
un descriptor que devuelva un método de instancia enlazado dándole un __get__()
. (En realidad, ese enfoque también funcionará en Python 2.x, pero lo anterior funcionará un poco mejor).
from types import MethodType
class Foo(object):
def __init__(self, name, val):
self.name = name
self.val = val
self.__name__ = name + ''_foo''
self.name = name
def __call__(self, instance):
return self.name + str(self.val + instance.val)
def __get__(self, instance, owner):
return MethodType(self, instance) if instance else self
# Python 2: MethodType(self, instance, owner)
class Baz(object):
def __init__(self, val):
self.val = val
Baz.biff = Foo("biff", 42)
b = Baz(13)
print b.biff()
>>> biff55