Python и закрытые переменные - PullRequest
6 голосов
/ 26 июля 2011

Посмотрите на этот код:

def closure():
    value = False

    def method_1():
        value = True

    def method_2():
        print 'value is:', value

    method_1()
    method_2()

closure()

Я бы ожидал, что он напечатает 'Value is: True', но это не так. Почему это так и каково решение?

Ответы [ 5 ]

16 голосов
/ 26 июля 2011

Это происходит потому, что method_1 получает собственную локальную область видимости, в которой он может объявлять переменные.Python видит value = True и думает, что вы создаете новую переменную с именем value, локальную для method_1.

. Причина, по которой Python делает это, заключается в том, чтобы избежать загрязнения локальных областей внешней области видимости переменными из внутренней функции,(Вы не хотели бы, чтобы назначения в обычных функциях уровня модуля приводили к созданию глобальных переменных!)

Если вы не присваиваете value, то Python ищет внешние области в поисках переменной(поэтому чтение переменная работает, как и ожидалось, что продемонстрировано вашим method_2).

Один из способов обойти это - использовать изменяемый объект вместо присвоения:

result = { 'value': False }

def method_1():
    result['value'] = True

В Python 3 оператор nonlocal (см. Также docs ) был добавлен именно для этого сценария:

def method_1():
    nonlocal value
    value = True    # Works as expected -- assigns to `value` from outer scope
2 голосов
/ 26 июля 2011

Это потому, что вы на самом деле не модифицировали закрытую переменную - вы маскируете ее новой переменной с тем же именем.В python 2.x нет простого способа обойти это, поэтому ключевое слово nonlocal было добавлено в python 3.

Однако это можно обойти, используя изменяемые типы, такие как list и dictionary.В этом ответе есть хороший пример .

2 голосов
/ 26 июля 2011

Когда вы присваиваете переменную, она предполагает, что переменная имеет локальную область видимости. Таким образом, value в method_1 не является value в closure.

Если вы хотите, чтобы это работало на Python 3, добавьте строку в method_1: nonlocal value.

На Python 2,

def closure():
    value = [False]

    def method_1():
        value[0] = True

    def method_2():
        print 'value is:', value

    method_1()
    method_2()

closure()

является одним из возможных обходных путей.

2 голосов
/ 26 июля 2011

В method_1 Python предполагает (вполне разумно!), Что value является локальной переменной. Всякий раз, когда вы назначаете имя переменной внутри функции, предполагается, что это имя переменной является новой локальной переменной. Если вы хотите, чтобы он был глобальным, то вы должны объявить его как global, и если вы хотите, чтобы он был «нелокальным», в Python 3 вы можете объявить его nonlocal, но в Python 2 вы должны сделайте что-нибудь более уродливое: сохраните значение в контейнере. Это исключает необходимость переназначения имени переменной и, таким образом, устраняет неопределенность области видимости.

def method_1_global():
    global value
    value = True

def method_1_nonlocal_P3():
    nonlocal value
    value = True

value = [False]
def method_1_nonlocal_P2():
    value[0] = True
1 голос
/ 26 июля 2011

Чтобы избежать этого, вы можете использовать список.

value = [False]

def method_1():
    value[0] = True

То, что сейчас делает Python, - поиск на более высоких уровнях области видимости, поскольку значение недоступно в локальных переменных. Поскольку значение является списком, а Python ссылается на него как на глобальную переменную относительно * method_1 *, вы можете обработать значение как есть, список.

...