Области видимости в Python

Перевод статьи Understanding Python scope.

Рассмотрим функцию Python и модульный тест для неё. Что в ней неверно?

Запустим тест:

Выглядит так как будто функция callback не вызвана. Проверим это: если изменить аргумент для callback передаваемый в method_under_test(), вы увидите что проверка в функции callback срабатывает.

Разберёмся почему так происходит.

Виды областей видимости

Начнём с основ. Как и большинство языков, Python использует статические области видимости переменных. Упрощённо, это означает, что для определения объекта на который ссылается идентификатор достаточно только изучения текста программы.

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

Динамические области видимости редко применяются в современных языках программирования, так как затрудняют анализ программы: определить поведение функции значительно сложнее. Примеры языков которые использующих динамические области видимости: Perl и Emacs Lisp. Все ANGOL3-подобные языки (Pascal, C, C#, Java и др.) используют статические области видимости.

Статические области видимости также называют лексическими областями видимости. Иногда термин лексические области видимости используется для отделения подмножества языков со статическими областями видимости которые разрешают произвольные вложенные области видимости: для определения привязки переменной к объекту используются родительские области видимости.

Замыкания

Никакое из определений выше не имеет ничего общего с замыканиями. Замыкания это экземпляр функции ссылающийся на нелокальные переменные. Таким образом, замыкание это комбинация лямбда-выражения (функции со свободными переменными) вместе с привязкой свободных переменных к специальным объектам. Замыкание не означает анонимную функцию, или функцию без переменных.

В тоже время, различия между языками могут быть в том могут ли перепрвязываться нелокальные переменные внутри замыкания и видны ли последствия этого другим замыканиям (и даже при повторном вызове одного замыкания).

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

global и nonlocal

Вернёмся к примеру. Python использует статические области видимости и не содержит явного оператора определения переменной (как ключевое слово var). Это означает что когда оператор присваивания ссылается на переменную, то интерпретатор должен знать как определить что должно произойти: изменение существующей нелокальной привязки этой переменной к объекту или создание новой локальной привязки которая скроет существующую.

Правило используемое в Python: любое присваивание внутри блока устанавливает новую локальную привязку. Если используется ключевое слово global перед именем переменной внутри блока то привязка создаётся на уровне модуля.

Когда мы пишем:

Второе присваивание создаёт новую локальную привязку для переменной callback_called которая скрывает нелокальную. Исходная переменная, объявленная в начале метода test_function_calls_callback не меняется.

Python мог бы делать и по-другому: например, Ruby 1.8 интерпретирует присваивание внутри блока как создание новой привязки только если она не скроет уже существующую локальную привязку, если привязка уже существует то происходит перепривязка существующей переменной.

В Python 3 добавлено ключевое слово nonlocal, которое как global, может использоваться для принудительной интерпретации присваивания как создания (или переписывания) привязки во внешней области видимости.

Проблему также можно решить использование изменяемых структур данных:

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *