Почему эта Python-реализация "работоспособной функции" не работает? - PullRequest
4 голосов
/ 30 ноября 2011

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

Теперь я хотел бы иметь декоратор, который преобразует любую функцию в «работоспособную» функцию, то есть функцию, которая ведет себя вописанный способ для любого оператора в стандартном модуле 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'

Откуда это происходит?

Second : записать op_to_function_op каклямбда-выражение:

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

Выполните несколько более сложный тестовый пример:

@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

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

1 Ответ

2 голосов
/ 30 ноября 2011

Для первой проблемы я не видел никаких проблем при изменении 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
...