Если вы построите химический завод, неплохо бы изолировать его от окружающего мира, чтобы не произошла утечка химикатов. Можно сказать, что в этом здании свое окружение — микроклимат, изолированный от внешней окружающей среды.
В программировании такая изоляция называется областью видимости. В этом уроке мы поговорим об окружении. Вы узнаете, на какие зоны делятся программы и как это влияет на работу кода.
Области видимости
С точки зрения окружения, программы делятся на две области видимости:
- Глобальную (внешнюю)
- Локальную (внутреннюю)
В глобальной области видимости находится все, что мы создаем снаружи функций, инструкций if
, циклов и других блоков кода:
age = 29
def multiply(num):
x = 10
return num * x
result = True
В этом примере переменная age
, функция multiply()
и переменная result
имеют глобальную область видимости.
Поговорим подробнее о локальной зоне видимости из этого примера. Внутри функции multiply()
есть переменная x
. Она находится внутри блока кода, она видна только внутри этой функции.
В функции multiply()
есть еще один компонент из локальной области видимости — аргумент num
. Он ведет себя почти как локальная переменная.
У нас нет доступа к x
снаружи, как будто ее там не существует:
def multiply(num):
x = 10
return num * x
print(x) # NameError: name 'x' is not defined
В этом примере print()
вызывается в глобальном окружении, но при этом x
не задан глобально. Поэтому мы получаем сообщение NameError
.
Попробуем задать x
глобально:
x = 55
def multiply(num):
x = 10
return num * x
print(x) # 55
Теперь существует глобальный x
. Мы вывели его значение на экран, но локальный x
внутри функции multiply()
по-прежнему виден только внутри этой функции.
Эти два x
не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, несмотря на одинаковое имя.
Все локальное недоступно снаружи, а глобальное? Здесь нам уже понадобится правило LEGB.
LEGB
Python разрешает имена с помощью так называемого правила LEGB, которое названо в честь области видимости имен. LEGB означает Local, Enclosing, Global и Built-in.
L
- Локальная область видимости или область видимости функции - тело любой функции. Внутри этой области только имена, которые вы определяете внутри функции. Они видны только из кода функции. Эта область создается при вызове функции, а не при ее определении, поэтому у вас будет столько же различных локальных областей, сколько вызовов функции, даже если вы вызываете одну и ту же функцию несколько раз или рекурсивно. При каждом отдельном вызове будет создаваться новая локальная область.E
- Охватывающая область видимости - это особая область видимости, которая существует только для вложенных функций. Эта область содержит имена, которые вы определяете во внешней функции. Имена из внешней области видимы во внутренней области.G
- Глобальная область видимости - это самая верхняя область видимости в программе, скрипте или модуле Python. Эта область видимости Python содержит все имена, которые вы определяете на верхнем уровне программы. Имена в этой области видимы везде в вашем коде.B
- Встроенная область видимости - это специальная область видимости Python, которая автоматически создается, когда вы запускаете скрипт или REPL. В ней содержатся ключевые слова, функции, исключения и другие атрибуты, которые встроены в Python. Имена в этой области видимости Python также доступны отовсюду в вашем коде.
Правило LEGB - это своего рода порядок поиска имен. Например, если вы обращаетесь в функции к определенному имени, то Python будет последовательно искать это имя в локальной, охватывающей, глобальной и встроенной областях видимости.
a = 42
def find():
def inner_find():
print(a)
# вызываем внутреннюю функцию
inner_find()
find() # => 42
Здесь внутренняя функция обращается к переменной a
. Python сперва ищет ее в локальной области функции inner_find()
, затем, переходит в область выше. Не найдя переменной и в области внешней функции, Python ищет ее в глобальной.
Немного изменим код:
a = 42
def find():
def inner_find():
print(a)
a = 5
inner_find()
find() # UnboundLocalError: local variable 'a' referenced before assignment
Почему же сейчас вышла ошибка и что она означает? Неужели Python не нашел переменную a
в глобальной области?
Сперва напомним, что локальные переменные имеют приоритет над глобальными. Переменная не может быть одновременно локальной и глобальной внутри одной и той же функции. Переменные к которым только обращаются в функции по умолчанию считаются глобальными. Но во время создания переменной где-либо в функции, Python назначает ее локальной.
В нашем примере, интерпретатор, после того как прочитал строчку a = 5
создал локальную переменную. Затем, во время вызова, мы пытаемся распечатать переменную с тем же именем, но ее значение будет задано лишь строчкой ниже. Здесь интерпретатор и выбрасывает ошибку "на локальную переменную 'a' ссылаются раньше присваивания".
global и nonlocal
В Python существует механизм для изменения области видимости переменной: выражения nonlocal
и global
.
nonlocal
позволяет не только обратиться к ранее определенной внешней переменной, это и так возможно, но и переопределить ее.
def outer():
x = 42
def inner():
x = 12
print('Local x = ', x)
# вызываем функцию
# ведь окружения создаются лишь в момент вызова, а не определения функции
inner()
print('New x = ', x)
outer()
# => Local x = 12
# => New x = 42
def outer():
x = 42
def inner():
# обращаемся к внешней х и переопределяем ее
nonlocal x
print('Nonlocal x = ', x)
x = 12
inner()
print('New x = ', x)
outer()
# => Nonlocal x = 42
# => New x = 12
global
же позволяет задать переменную глобальной изнутри функции, или переопределить уже существующую.
x = 42
def change():
# переопределяем существующую глобальную переменную
global x
x = 12
change()
print(x) # => 12
def a():
# создаем новую глобальную переменную
global y
y = 2
print(y)
def b():
# т.к. y глобальная, то доступна даже внутри другой функции
print(y + 10)
a() # => 2
b() # => 12
Хотим вас уберечь от использования global
. Может показаться, что было бы удобно все поместить в глобальную область видимости и забыть о сложностях. На самом деле, это ужасная практика. Глобальные переменные делают код невероятно хрупким. Такой код лишен главного преимущества областей видимости — изоляции. В нем может сломаться все что угодно. Поэтому избегайте глобальной области видимости — храните вещи там, где им место.
Замыкание
Замыкание — это сочетание функции и окружения, где она была заявлена. Другими словами, это всего лишь название функции, которая запоминает внешние штуки, используемые внутри.
Давайте вспомним, как функции создаются и используются:
def f():
return 0
Функция f
довольно бесполезная, она всегда возвращает 0
. Весь этот набор состоит из двух частей:
- Объявления функции
- Самой функции
Важно помнить, что эти два компонента раздельны. Первый — переменная с именем f
. Ее значение могло бы быть числом или строкой, но здесь это функция. О том, что объявление функции лишь связывает тело и имя переменной мы поговорим в следующих уроках.
Когда мы вызываем эту функцию, это выглядит вот так:
f()
Вернемся к замыканиям и рассмотрим следующий код:
def create_print():
name = "King"
def print_name():
print(name)
return print_name
my_print = create_print()
my_print() # => King
Функция create_print()
создает переменную name
и затем функцию с именем print_name()
. Созданные переменная и функция локальны — они доступны только внутри create_print()
.
У самой print_name()
нет локальных компонентов. Но у нее есть доступ к своей области видимости — то есть к внешней области, где задана переменная name
.
Затем функция create_print()
возвращает функцию print_name()
. Помните, что определения функций это присваивание описания функции имени переменной, точно такое же как присваивание числа или строки. Поэтому мы можем вернуть определение функции, как мы возвращаем число.
Во внешней области видимости мы создаем переменную my_print
и задаем ей значение вызова функции create_print()
. Этот вызов возвращал функцию, так что теперь my_print
— это функция. Вызовем ее, и на экран выведется переменная name
- "King"
.
Тут есть одна странная штука: эта переменная name
была создана внутри функции create_print()
. Функция была вызвана и исполнена. Как мы знаем, когда функция заканчивает работу, она больше не существует.
Этот магический ящик исчезает со всеми своими внутренностями, но он возвращает другую функцию, и уже она запоминает переменную name
. Поэтому когда мы вызывали my_print()
, она вывела King
— запомненное значение. При этом больше не существует та область видимости, где мы задали это значение.
Функция, которую мы вернули из create_print()
— это и есть замыкание. Другими словами, это сочетание функции и окружения, где она была задана. Функция "замкнула" в себе некоторую информацию из области видимости.
Если использовать их разумно, замыкания могут сделать код приятней, чище и проще для чтения. Даже сама идея возврата функций тем же способом, которым можно возвращать числа и строки, дает больше возможностей и гибкости.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.