В Python переменные внутри if условий скрывают глобальную область видимости, даже если они не выполняются?

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    if hesitant:
        do_something = lambda: 'did nothing'
    result = do_something()
    print result

maybe_do_it()

Результат этого кода:

  File "scope_test.py", line 10, in <module>
    maybe_do_it()
  File "scope_test.py", line 7, in maybe_do_it
    result = do_something()
UnboundLocalError: local variable 'do_something' referenced before assignment

Но этот код печатает "сделал что-то...", как ожидалось:

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    result = do_something()
    print result

maybe_do_it()

Как функция была переопределена, хотя условие внутри оператора if никогда не выполнялось? Это происходит в Python 2.7 - это то же самое в Python 3?

Ответ 1

Как функция была переопределена, даже если условие внутри оператора if никогда не выполнялось?

Решение о том, является ли переменная локальной или глобальной, выполняется во время компиляции. Если есть назначение переменной в любой точке функции, это локальная переменная, независимо от того, выполняется ли задание.

Это происходит в Python 2.7 - это то же самое в python 3?

Да.

Кстати, в Python 2 вы можете переопределить это поведение, используя exec (не рекомендуется):

def do_something():
    print 'doing something...'

def maybe_do_it(hesitant=False):
    if hesitant:
        exec "do_something = lambda: 'did nothing'"
    result = do_something()
    print result

maybe_do_it(False)    # doing something...
maybe_do_it(True)    # did nothing

An exec внутри функции будет, разумеется, отложить решение о том, следует ли искать переменную глобально или локально во время выполнения.

Ответ 2

Как указано в документации Python Execution Model:

Если операция привязки имени происходит где угодно внутри блока кода,    все использования имени внутри блока рассматриваются как ссылки    к текущему блоку. Это может привести к ошибкам при использовании имени    внутри блока до его привязки. Это правило тонкое. питон    не хватает деклараций и позволяет выполнять операции привязки имени    где-нибудь внутри кодового блока. Локальные переменные кодового блока    может быть определено путем сканирования всего текста блока для имени    операции привязки.

Это правило языка. Так оно и есть.: D

Ответ 3

Когда python компилируется в байт-код (делает файл *.pyc) *, поскольку строка do_something = lambda: 'did nothing' в вашей функции do_something теперь рассматривается как локальная переменная, даже если поток управления не принимает интерпретатор есть.

Основные причины этого неожиданны:

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

* Как уже указывалось, это на самом деле не распространяется только на Python, который скомпилирован в байт-код (CPython) - на самом деле это особенность языка. Детали моего объяснения (выражение в терминах байт-кода) относятся только к CPython.

Ответ 4

Да, это то же самое в Python 3. По большей части это желательно, если не совсем интуитивное поведение. Возможно, вы должны быть голландскими. Многие люди могут быть знакомы с подъемом (популяризируется JavaScript?). Это происходит и в Python, за исключением того, что вместо значения undefined Python просто повышает значение UnboundLocalError. Для сравнения:

> // JavaScript example
> var x = 1;
> function foo() {
    if (!x) { // x is declared locally below, so locally x is undefined
      var x = 2;
    }
    return x;
  }
> foo();
2

>>> # Everything else is Python 3
>>> x = 1
>>> def foo():
...   if not x: # x is bound below, which constitutes declaring it as a local
...     x = 2
...   return x
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

До сих пор Python последователен, но существует (тревожное) обходное решение:

>>> def foo():
...   if not 'x' in locals():
...     x = 2
...   return x
... 
>>> foo()
2

Это работает, и мы сказали

Локальные переменные кодового блока могут быть определены путем сканирования всего текста блока для операций привязки имени.

Но не locals() дает нам все локальные имена? Очевидно, нет. Фактически, несмотря на предыдущее утверждение, Python намекает, что таблица локальных символов может изменяться в описании locals() builtin:

Обновить и вернуть словарь, представляющий текущую локальную таблицу символов [emphasis mine].

Раньше я думал, что слово current относится к значениям, теперь я думаю, что это относится и к клавишам. Но в конечном итоге то, что я думаю, это означает, что нет способа (не дожидаясь демпинга и анализа исходного кода для фрейма), чтобы перечислять все имена, объявленные локально (что не означает, что вы не можете использовать try/except UnboundLocalError для определения если определенное имя является локальным.)

def foo():
    # Some code, including bindings
    del x, y, z # or any other local names
    # From this point, is it programmatically knowable what names are local?

Это, я думаю, является фундаментальным различием между языком с неявными декларациями и языком с явными.