2012-05-31 13 views
5

Mi idea es crear objetos de funciones particulares que se pueden sumar/restar/... juntos, devolviendo un nuevo objeto de función que tiene las mismas propiedades. Este código de ejemplo, es de esperar, demuestra la idea:Crear métodos aritméticos especiales en Python, (también conocido como fábrica función HOWTO)

from FuncObj import Func 

# create some functions 
quad = Func(lambda x: x**2) 
cube = Func(lambda x: x**3) 

# now combine functions as you like 
plus = quad + cube 
minus = quad - cube 
other = quad * quad/cube 

# and these can be called 
plus(1) + minus(32) * other(5) 

He escrito el siguiente código, que es de esperar comentado y documentado lo suficiente como para explicar lo que quiero lograr.

import operator 

class GenericFunction(object): 
    """ Base class providing arithmetic special methods. 
     Use derived class which must implement the 
     __call__ method. 
    """ 

    # this way of defining special methods works well 
    def __add__(self, operand): 
     """ This is an example of a special method i want to implement. """ 
     obj = GenericFunction() 
     # this is a trick from Alex Martelli at 
     # http://stackoverflow.com/questions/1705928/problem-with-making-object-callable-in-python 
     # to allow per-instance __call__ methods 
     obj.__class__ = type(obj.__class__.__name__, (obj.__class__,), {}) 
     obj.__class__.__call__ = lambda s, ti: self(ti) + operand(ti) 
     return obj 

    # on the other hand this factory function seems buggy 
    def _method_factory(operation, name): 
     """ Method factory. 
     Parameters 
     ---------- 
     op : callable 
      an arithmetic operator from the operator module 
     name : str 
      the name of the special method that will be created 
     Returns 
     ------- 
     method : callable 
      the __***__ special method 
     """ 
     def method(s, operand): 
      obj = GenericFunction() 
      obj.__class__ = type(obj.__class__.__name__, (obj.__class__,), {}) 
      obj.__class__.__call__ = lambda s, ti: operation(s(ti), operand(ti)) 
      return obj 
     return method 

    __sub__ = _method_factory(operator.__sub__, '__sub__') 
    __mul__ = _method_factory(operator.__mul__, '__mul__') 
    __truediv__ = _method_factory(operator.__truediv__, '__div__') 


class Func(GenericFunction): 
    """ A customizable callable object. 
     Parameters 
     ---------- 
     func : callable 
    """ 
    def __init__(self, func): 
     self.func = func 

    def __call__(self, *args): 
     return self.func(*args) 


if __name__ == '__main__': 

    # create some functions 
    quad = Func(lambda x: x**2) 
    cube = Func(lambda x: x**3) 

    # now combine functions 
    poly_plus = quad + cube 
    poly_minus = quad - cube 

    # this is the expected behaviour, and it works well 
    # since the __add__ method is defined correctly. 
    assert quad(1) + cube(1) == poly_plus(1) 

    # this, and the others with * and/result in a "maximum recursion depth exceeded" 
    assert quad(1) - cube(1) == poly_minus(1) 

Creo que me falta algo importante pero no puedo verlo.

EDITAR

Después de la respuesta Dietrich me olvidó mencionar un caso esquina. Supongamos que quiero subclase GenericInput y necesito personalizar el método de llamada__, sin pasar un invocable al constructor. Tengo ejemplos, (en realidad este es el código para el que originalmente publiqué esta pregunta).

class NoiseInput(GenericInput): 
    def __init__(self, sigma, a, b, t): 
     """ A band-pass noisy input. """ 
     self._noise = lfilter(b, a, np.random.normal(0, 1, len(t))) 
     self._noise *= sigma/self._noise.std() 
     self._spline = InterpolatedUnivariateSpline(t, self._noise, k=2) 

    def __call__(self, ti): 
     """ Compute value of the input at a given time. """ 
     return self._spline(ti) 

class SineInput(GenericInput): 
    def __init__(self, A, fc): 
     self.A = A 
     self.fc = fc 

    def __call__(self, ti): 
     return self.A*np.sin(2*np.pi*ti*self.fc) 

En este caso, hay algo más que hacer.

+0

en su ejemplo, ¿'plus (1)' significa 'quad (1) + cube (1)'? – inspectorG4dget

+0

@ inspectorG4dget Sí, ese es el comportamiento previsto. – Davide

Respuesta

2

Aquí hay un montón de código que no necesita existir, y es más complicado de lo que debe ser.

Por ejemplo, el atributo __class__ es uno de los llamados atributos "mágicos". Los atributos mágicos son especiales y solo deben usarse en casos especiales, como cuando se usa metaprogramación. No hay necesidad de crear una clase por código aquí.

Otro ejemplo es la clase Func en su código, que en realidad no hace nada. Se puede reemplazar de forma segura con:

def Func(x): 
    return x 

Así que tienen el problema contrario: no se "echa en falta" algo, usted tiene demasiado.

class Func(object): 
    def __init__(self, func): 
     self._func = func 
    def __call__(self, x): 
     return self._func(x) 
    def __mul__(self, other): 
     return Func(lambda x: self(x) * other(x)) 
    def __add__(self, other): 
     return Func(lambda x: self(x) + other(x)) 
    def __sub__(self, other): 
     return Func(lambda x: self(x) - other(x)) 

Tenga en cuenta que esto es no la forma tradicional de llevar a cabo tal problema. Tradicionalmente, uno evita lambdas y usa un árbol de expresiones aquí. La ventaja de utilizar un árbol de expresiones es que las expresiones resultantes se pueden manipular algebraicamente. Por ejemplo, puede resolverlos, calcular derivados exactos o imprimirlos como ecuaciones.

0

Supongo que desea f(x ** 2) + f(x ** 3) para devolver una función x**2 + x**3? Puede intentar esto:

class Func: 
    def __init__(self, func): 
     self._func = func 

    def __call__(self, *args): 
     return self._func(*args) 

    def __add__(self, other): 
     def result(*args): 
      return self._func(*args) + other(*args) 
     return Func(result) 
    __radd__ = __add__ 

    def __mul__(self, other): 
     def result(*args): 
      return self._func(*args) * other(*args) 
     return Func(result) 
    __rmul__ = __mul__ 

    # etc... 

que funciona para mí y es mucho más simple que lo que tiene.

EDIT:

Probablemente se podría ni siquiera se molestan con los self._func llamadas en los métodos aritméticos y simplemente llamar directamente self.

Cuestiones relacionadas