2011-03-01 4 views
7

Al usar metaclases, intento crear un método de instancia simplificando un método de instancia existente. El problema es que parcial no funciona con el método de instancia. Este es un ejemplo sencillo de lo que trato de lograr:Crear método de instancia en metaclase usando parcial en Python 3

from functools import partial 

class Aclass(object): 

    def __init__(self, value): 
     self._value = value 

    def complex(self, a, b):            
     return a + b + self._value 

class Atype(type): 

    def __new__(cls, name, bases, attrs): 
     return super(Atype, cls).__new__(cls, name, (Aclass,) + bases, attrs) 

    def __init__(cls, name, bases, attrs): 
     setattr(cls, 'simple', partial(cls.complex, b=1)) 

class B(metaclass=Atype): 
    pass 

b = B(10) 

print(b.complex(1, 2)) 
print(b.simple(1)) 

y la salida es:

13 
Traceback (most recent call last): 
    File "metatest.py", line 22, in <module> 
    print(b.simple(1)) 
TypeError: complex() takes exactly 3 non-keyword positional arguments (1 given) 

He resuelto utilizando lambda cambiante:

setattr(cls, 'simple', partial(cls.complex, b=1)) 

a:

setattr(cls, 'simple', lambda self, x: cls.complex(self, x, b=1)) 

pero es feo d tiene problemas con los parámetros opcionales.

Podría crear estos métodos en la instancia __init__ pero supongo que tiene más sentido, y es más eficiente hacerlo en la clase __init__ usando metaclases.

¿Alguna idea de cómo hacerlo correctamente?

+0

@martineau: estoy a favor de usar la etiqueta python-3.x, pero no veo ninguna razón para no usar "python" junto a ella. Además, ¿no sería "python3" una etiqueta más limpia? – jsbueno

+0

@jsbueno: de acuerdo, pero no me permitirá cambiar python-3.x a python3. – martineau

+0

@jsbueno: La etiqueta es python-3.x, no sé cómo (si es que lo hace) estas cosas se deciden. Sospecho que podría discutirse en meta, y luego cambiar si hay apoyo para ello. En cualquier caso, python3 es un sinónimo, por lo que puede escribirlo, se reasignará. –

Respuesta

5

Bueno, estoy un poco familiarizado con el manejo aún método Python 3 - la cosa más simple que podía pensar está reescribiendo partial por lo que conserva el primer argumento de la llamada original, a continuación, inserta los parámetros "parciales".

Funcionó con su ejemplo, pero necesita pruebas con patrones más complejos.

from functools import wraps 

class Aclass(object): 
    def __init__(self, value): 
     self._value = value 

    def complex(self, a, b):            
     return a + b + self._value 

def repartial(func, *parameters, **kparms): 
    @wraps(func) 
    def wrapped(self, *args, **kw): 
     kw.update(kparms) 
     return func(self, *(args + parameters), **kw) 
    return wrapped 

class Atype(type): 
    def __new__(cls, name, bases, attrs): 
     return super(Atype, cls).__new__(cls, name, (Aclass,) + bases, attrs) 

    def __init__(cls, name, bases, attrs): 
     setattr(cls, 'simple', repartial(cls.complex, b=1)) 

class B(metaclass=Atype): 
    pass 

b = B(10) 

print(b.complex(1, 2)) 
print(b.simple(1)) 
+0

Creo que 'wrapper()' sería un mejor nombre para la función anidada que has llamado 'wrapped()' en 'repartial()'. Solo una elección para una respuesta excelente. – martineau

1

El problema es que el objeto devuelto por functools.partial() es un objeto exigible, no una función. Así que aparentemente Python no se preocupa por una no función que intente actuar como tal en este contexto. Una solución es crear una función como un contenedor para el objeto partial. solución

class Atype(type): 

    def __init__(cls, name, bases, attrs): 
     simple = partial(cls.complex, b=1) 
     setattr(cls, 'simple', lambda cls, a: simple(cls, a)) 

de jsbueno (una reimplementación de partial que devuelve una función real) es bueno. Realmente no sé por qué functools.partial() no funciona de esa manera; no poder usarlo en este contexto es una trampa sorprendente.

+1

Esta es una buena manera de resolver la trampa del "parcial", pero creo que mi propia respuesta es mejor ;-) porque, por un lado, tiene menos sobrecarga de llamadas a la función. Por cierto, otra causa del problema que nadie ha mencionado hasta ahora es que 'cls.complex' es un método independiente. – martineau

+1

En Python 3, no parece que haya métodos independientes; 'Aclass.complex' es una función. – kindall

+1

Es interesante que cuando el parcial se da como fset o fget de una propiedad, entonces funciona bien. – Hernan

3

En lugar de utilizar partial, que acababa de definir la clase Atype así:

class Atype(type): 

    def __new__(cls, name, bases, attrs): 
     return super(Atype, cls).__new__(cls, name, (Aclass,) + bases, attrs) 

    def __init__(cls, name, bases, attrs): 
     def simple(self, a): 
      return cls.complex(self, a, 1) 
     setattr(cls, 'simple', simple) 

__init__() El método también se puede escribir de forma más compacta:

def __init__(cls, name, bases, attrs): 
    setattr(cls, 'simple', lambda self, a: cls.complex(self, a, 1)) 
+0

Sí, realmente no creo que una metaclase (o' functools.partial() 'sea es necesario aquí. Quizás esto fue solo un ejemplo simple que demostró el problema, sin embargo, y las metaclases realmente se necesitan en la aplicación real del usuario482819. – kindall

+0

De hecho ,. Necesito crear métodos de instancia de forma dinámica en función de las propiedades específicas de la clase utilizando la metaclase. – Hernan

Cuestiones relacionadas