Python: как работает связывание - PullRequest
10 голосов
/ 27 ноября 2010

Я пытаюсь понять, как именно работает привязка переменных в python. Давайте посмотрим на это:

def foo(x):
    def bar():
        print y
    return bar

y = 5
bar = foo(2)
bar()

Это печатает 5, что мне кажется разумным.

def foo(x):
    def bar():
        print x
    return bar
x = 5
bar = foo(2)
bar()

Это печатает 2, что странно. В первом примере python ищет переменную во время выполнения, во втором - при создании метода. Почему это так?

Чтобы было ясно: это очень круто и работает именно так, как я хотел бы. Однако я не совсем понимаю, как внутренняя функция бара получает свой контекст. Хотелось бы понять, что происходит под капотом.

EDIT

Я знаю, что локальные переменные имеют больший приоритет. Мне любопытно, как Python знает во время выполнения, чтобы взять аргумент из функции, которую я вызвал ранее. bar был создан в foo, а x больше не существует. Он связал это x со значением аргумента при создании функции?

Ответы [ 5 ]

7 голосов
/ 27 ноября 2010

Проблема, на которую вы ссылаетесь, это лексическая или динамическая область видимости переменных в python. Чтобы быть явным, python определяет следующие четыре области.

  1. Самая внутренняя область, которая ищется первой, содержит локальные имена
  2. Области действия любых включающих функций, поиск которых начинается с ближайшей охватывающей области, содержат нелокальные, но и неглобальные имена
  3. следующая за последней область содержит глобальные имена текущего модуля
  4. самая дальняя область (последний поиск) - это пространство имен, содержащее встроенные имена

В первом примере, где "y" определено вне функциональной панели, python искал самую внутреннюю область и перемещался вверх по цепочке, пока не нашел глобальную переменную "y" в модуле

Во втором примере, где "x" определяется функцией foo (x), x принадлежит области действия функции, когда она печатается внутри бара. Проще говоря, замыкание было определено.

Для дальнейшего изучения области видимости в Python я нашел следующую статью отличное чтение

7 голосов
/ 27 ноября 2010

Второй пример реализует то, что называется замыканием . Функция bar ссылается на переменную x из окружающего ее контекста, то есть функцию foo. Это предшествует ссылке на глобальную переменную x.

См. Также этот вопрос Можете ли вы объяснить замыкания (как они связаны с Python)?

1 голос
/ 27 ноября 2010

В обоих примерах поиск происходит во время выполнения. Единственное отличие состоит в том, что есть локально определенная переменная x, в то время как нет локально определенной переменной y.

При выполнении ...

def foo(x):
    def bar():
        print y

    return bar

y = 5
bar = foo(2)
bar()

... оператор print ищет переменную с именем y и находит ее только в глобальном контексте, поэтому использует ее и печатает "5".

В ...

def foo(x):
    def bar():
        print x

    return bar

x = 5
bar = foo(2)
bar()

... когда происходит поиск, является определяемой переменной x, которая фиксируется на "5" при вызове функции foo.

Ключ в том, что аргументы оцениваются в момент их передачи в функции, поэтому внешняя функция foo оценивает аргументы, переданные при вызове. Это фактически создает переменную с именем x в контексте функции foo, поэтому всякий раз, когда выполняется bar, она видит эту переменную, а не глобально определенную.

Иногда это может сбивать с толку, как в следующем коде:

lst = []
for i in range(5):
    x = i
    lst.append(lambda: x)

for func in lst:
    print func()  # prints 4 4 4 4 4

Вам нужно сделать:

lst = []
for i in range(5):
    def _func(x):
        return lambda: x

    lst.append(_func(i))

for func in lst:
    print func()  # prints 0 1 2 3 4
0 голосов
/ 27 ноября 2010

Это вопрос области видимости, во втором примере используется локальная переменная области видимости x , которой предшествует глобально объявленная

0 голосов
/ 27 ноября 2010

Ничего странного, это потому, что "x" из аргумента функции имеет более высокий приоритет, чем глобальная переменная "x".

Сначала глобальные переменные - это большое зло.

В Python есть оператор"global":

>>> def foo(x):
...     def bar():
...          global x
...          print x
...     return bar
... 
>>> x = 5
>>> bar = foo(2)
>>> bar()
5
...