2011-11-29 2 views
4

Я хотел продемонстрировать полезность декораторов в python для некоторых людей и провалился на простом примере: рассмотрим две функции (для простоты без аргументов) f и g. Можно определить их сумму f + g как функцию, которая возвращает f() + g(). Конечно, добавление, вычитание и т. Д. Функций вообще не определено. Но легко написать декоратор, который преобразует каждую функцию в добавочную функцию.Почему эта реализация python для «работоспособной функции» не работает?

Теперь я хотел бы иметь декоратор, который преобразует любую функцию в «действующую» функцию, то есть функцию, которая ведет себя описанным способом для любого оператора в стандартном модуле operator. Моя реализация выглядит следующим образом:

import operator 

class function(object): 
    def __init__(self, f): 
     self.f = f 
    def __call__(self): 
     return self.f() 

def op_to_function_op(op): 
    def function_op(self, operand): 
     def f(): 
      return op(self(), operand()) 
     return function(f) 
    return function_op 
binary_op_names = ['__add__', '__and__', '__div__', '__eq__', '__floordiv__', '__ge__', '__gt__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__or__', '__pow__', '__sub__', '__truediv__', '__xor__'] 
for name in binary_op_names: 
    type.__setattr__(function, name, op_to_function_op(getattr(operator, name))) 

Давайте выполнить небольшой тест, чтобы увидеть, если он работает:

@function 
def a(): 
    return 4 

def b(): 
    return 7 

c = a + b 
print c() 
print c() == operator.__add__(4, 7) 

Выход:

11 
True 

Это последняя версия, которую я получил после того, как некоторые эксперименты , Теперь давайте сделаем два маленьких, несоответствующие изменения, чтобы посмотреть, что я пытался раньше:

Первого: В определении binary_op_names, изменить квадратные скобки в круглые скобки. Внезапно появляется (для меня) полностью несвязанное сообщение об ошибке:

Traceback (most recent call last): 
    File "example.py", line 30, in <module> 
    c = a + b 
TypeError: unsupported operand type(s) for +: 'function' and 'function' 

Откуда это?

Второй: Написать op_to_function_op как лямбда-выражения:

op = getattr(operator, name) 
type.__setattr__(function, name, lambda self, other: function(lambda: op(self(), other()))) 

Perform немного более активное участие тестовый пример:

@function 
def a(): 
    return 4 

def b(): 
    return 7 

c = a + b 
print c() 
print c() == operator.__add__(4, 7) 
print c() == operator.__xor__(4, 7) 

Выход:

3 
False 
True 

Это выглядит для меня как утечка области, но снова я Не понимаю, почему это происходит.

+0

типа .__ setattr__, кажется, сделать что-то плохое .. Дон Не знаю, что. Список для кортежей действительно странный. Если вы измените тип.__setattr__ to setattr все, кажется, работает. – mkorpela

+0

Типичный декоратор 'memoize', примененный к последовательности фибоначчи, и другие проблемы, связанные с кешем, могут быть лучшим приложением для демонстрации декораторов. – Daenyth

+0

Проблема с кортежем кажется отвратительной ошибкой. Я вижу такое же поведение. Абсолютно все о 'name' проверяется, но' type .__ setattr__' не работает. – Nate

ответ

2

Для первой проблемы я не видел проблем при изменении binary_op_names от list до tuple, не уверен, почему вы это видели.

Что касается второй проблемы, все операции будут выполнять XOR, потому что __xor__ был последним элементом, который был установлен для op. Поскольку op не передается в лямбда, когда он создается, каждый раз, когда вызывается лямбда, он будет искать op в глобальной области действия и всегда видеть __xor__.

Вы можете предотвратить это путем создания лямбда, который действует как закрытие, похожий на ваш текущий op_tofunction_op, это может в конечном итоге что-то вроде этого:

op_to_function_op = lambda f: lambda self, other: function(lambda: f(self(), other())) 
for name in binary_op_names: 
    op = getattr(operator, name) 
    type.__setattr__(function, name, op_to_function_op(op)) 

>>> (function(lambda: 4) + (lambda: 7))() 
11 
>>> (function(lambda: 4) - (lambda: 7))() 
-3 
>>> (function(lambda: 4)^(lambda: 7))() 
3 
+0

Использовали ли вы тип .__ setattr__ или setattr? (См. Обсуждение в основной теме комментариев) Спасибо за разъяснение на лямбда! – Turion

+0

Я использовал 'type .__ setattr__' и, похоже, работал, но я не использую' setattr' или 'type .__ setattr__', поэтому я не могу комментировать многое, что предпочтительнее. –

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