Изменить связанные переменные замыкания в Python - PullRequest
33 голосов
/ 25 декабря 2008

Есть ли способ изменить значение привязки одной из переменных внутри замыкания? Посмотрите на пример, чтобы понять его лучше.

def foo():
    var_a = 2
    var_b = 3

    def _closure(x):
        return var_a + var_b + x

    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

Ответы [ 7 ]

25 голосов
/ 25 декабря 2008

Это вполне возможно в Python 3 благодаря магии нелокальной .

def foo():
        var_a = 2
        var_b = 3

        def _closure(x, magic = None):
                nonlocal var_a
                if magic is not None:
                        var_a = magic

                return var_a + var_b + x

        return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6
print(a)

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
localClosure(0, 0)

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4
print(b)
20 голосов
/ 25 декабря 2008

Я не думаю, что в Python есть способ сделать это. Когда замыкание определено, текущее состояние переменных в охватывающей области действия фиксируется и больше не имеет имени, на которое можно напрямую ссылаться (извне замыкания). Если бы вам пришлось снова вызывать foo(), новое замыкание будет иметь другой набор переменных, чем окружающая область.

В вашем простом примере вам лучше использовать класс:

class foo:
        def __init__(self):
                self.var_a = 2
                self.var_b = 3

        def __call__(self, x):
                return self.var_a + self.var_b + x

localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?
localClosure.var_a = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

Если вы используете эту технику, я бы больше не использовал имя localClosure, потому что это больше не закрытие. Тем не менее, он работает так же, как один.

10 голосов
/ 25 декабря 2008

Я нашел альтернативный ответ ответа Грега, немного менее многословный, поскольку он использует атрибуты пользовательских функций Python 2.1 (к которым достаточно удобно обращаться изнутри их собственной функции).

def foo():
    var_b = 3

    def _closure(x):
        return _closure.var_a + var_b + x

    _closure.func_dict['var_a'] = 2
    return _closure


localClosure = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
# ...but what magic? Is this even possible?
# apparently, it is
localClosure.var_a = 0

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

Думаю, я выложу это для полноты. Приветствия в любом случае.

9 голосов
/ 03 августа 2010

Мы сделали следующее. Я думаю, что это проще, чем другие решения здесь.

class State:
    pass

def foo():
    st = State()
    st.var_a = 2
    st.var_b = 3

    def _closure(x):
        return st.var_a + st.var_b + x
    def _set_a(a):
        st.var_a = a

    return _closure, _set_a


localClosure, localSetA = foo()

# Local closure is now "return 2 + 3 + x"
a = localClosure(1) # 2 + 3 + 1 == 6

# DO SOME MAGIC HERE TO TURN "var_a" of the closure into 0
localSetA(0)

# Local closure is now "return 0 + 3 + x"
b = localClosure(1) # 0 + 3 +1 == 4

print a, b
4 голосов
/ 02 января 2011

Я обошел подобное ограничение, используя списки из одного элемента вместо простой переменной. Это некрасиво, но работает, потому что изменение элемента списка интерпретатором не рассматривается как операция привязки.

Например:

def my_function()
    max_value = [0]

    def callback (data)

        if (data.val > max_value[0]):
            max_value[0] = data.val

        # more code here
        # . . . 

    results = some_function (callback)

    store_max (max_value[0])
0 голосов
/ 09 января 2012
def foo():
    var_a = 2
    var_b = 3

    def _closure(x):
            return var_a + var_b + x

    return _closure

def bar():
        var_a = [2]
        var_b = [3]

        def _closure(x):
                return var_a[0] + var_b[0] + x


        def _magic(y):
            var_a[0] = y

        return _closure, _magic

localClosureFoo = foo()
a = localClosureFoo(1)
print a



localClosureBar, localClosureBarMAGIC = bar()
b = localClosureBar(1)
print b
localClosureBarMAGIC(0)
b = localClosureBar(1)
print b
0 голосов
/ 25 декабря 2008

Почему бы не сделать аргументы var_a и var_b функции foo?

def foo(var_a = 2, var_b = 3):
    def _closure(x):
        return var_a + var_b + x
    return _closure

localClosure = foo() # uses default arguments 2, 3
print localClosure(1) # 2 + 3 + 1 = 6

localClosure = foo(0, 3)
print localClosure(1) # 0 + 3 + 1 = 4
...