2012-05-31 2 views
5

Моя идея - создать определенные объекты функций, которые можно суммировать/вычитать/... вместе, возвращая новый объект функции с теми же свойствами. Этот пример кода, надеюсь, демонстрирует идею:Программно создавать арифметические специальные методы в Python, (как фабричная функция 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) 

Я написал следующий код, который мы надеемся, документированных достаточно, чтобы объяснить, что я хочу достичь.

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) 

Я думаю, что у меня что-то не хватает, но я не вижу его.

EDIT

После Дитриха ответа я забыл упомянуть угловой случай. Предположим, что я хочу подкласс GenericInput, и мне нужно настроить вызов method__, не передавая вызываемому конструктору. У меня есть примеры (на самом деле это код, для которого я изначально разместил этот вопрос).

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) 

В этом случае есть еще одна работа.

+0

в вашем примере означает 'plus (1)' mean 'quad (1) + cube (1)'? – inspectorG4dget

+0

@ inspectorG4dget Да, это намеченное поведение. – Davide

ответ

2

Здесь много кода, который не должен существовать, и это сложнее, чем нужно.

Например, атрибут __class__ является одним из так называемых «магических» атрибутов. Магические атрибуты являются особыми и должны использоваться только в особых случаях, например, при использовании метапрограммирования. Здесь нет необходимости создавать класс по коду.

Другим примером является класс Func в вашем коде, который фактически ничего не делает. Вы можете спокойно заменить его:

def Func(x): 
    return x 

Так у вас есть противоположная проблема: вы не «хватает» ничего, у вас есть слишком много.

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)) 

Обратите внимание, что это не традиционный способ выполнения такой задачи. Традиционно один избегает лямбда и использует здесь дерево выражений. Преимущество использования дерева выражений состоит в том, что полученные выражения можно манипулировать алгебраически. Например, вы можете решить их, вычислить точные производные или распечатать их как уравнения.

0

Я предполагаю, что вы хотите получить f(x ** 2) + f(x ** 3), чтобы вернуть функцию x**2 + x**3? Вы можете попробовать следующее:

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... 

, который работает для меня и намного проще, чем у вас.

EDIT:

Вы могли бы, вероятно, даже не беспокоить с self._func вызовов в арифметических методов и просто вызвать self непосредственно.

Смежные вопросы