9.2. Области видимости и пространства имён
Перед тем, как знакомиться с классами, следует рассказать о правилах языка, касающихся областей видимости. Определения классов выполняют несколько изящных приемов с пространствами имён, и Вам следует знать о работе областей видимости и пространств имён, для полного понимания происходящего.
Начнём с нескольких определений. Пространство имён определяет отображение имён в объекты. Большинство пространств имен в языке Python реализованы как словари, что, однако, никак себя не проявляет (кроме производительности) и может быть изменено в будущем. Вот несколько примеров пространств имён: множество встроенных имён (функции, исключения), глобальные имена в модуле, локальные имена при вызове функций. В этом смысле множество атрибутов объекта также образует пространство имён. Важно понимать, что между именами в разных пространствах имён нет связи. Например, два модуля могут определить функции с именем «maximize» не создавая при этом путаницы — пользователь должен ссылаться на них с использованием имени модуля в качестве приставки.
Под словом атрибут мы подразумеваем любое имя, следующее после точки: например, в выражении z.real , real является атрибутом объекта z . Строго говоря, имена в модулях являются атрибутами модуля: в выражении modname.funcname , modname является объектом-модулем и funcname является его атрибутом. В этом случае имеет место прямое соответствие между атрибутами модуля и глобальными именами, определёнными в модуле: они совместно используют одно пространство имён 1 !
Атрибуты могут быть доступны только для чтения, а могут и допускать присваивание. Во втором случае Вы можете записать ‘modname.attribute = 42′ или даже удалить его, используя инструкцию del : ‘del modname.attribute‘ .
Пространства имён создаются в различные моменты времени и имеют разную продолжительность жизни. Пространство имён, содержащее встроенные имена, создаётся при запуске интерпретатора и существует всё время его работы. Глобальное пространство имён модуля создаётся, когда он считывается, и, обычно, также существует до завершения работы интерпретатора. Инструкции, выполняемые на верхнем уровне, т.е. читаемые из файла-сценария или интерактивно, рассматриваются как часть модуля __main__ , имеющего собственное глобальное пространство имён. (В действительности, встроенные имена также находятся в модуле — __builtin__ ).
Локальное пространство имён функции создается при вызове функции и удаляется при выходе из неё (возвращается значение или генерируется исключение, которое не обрабатывается внутри функции). Безусловно, при рекурсивном вызове создаётся собственное локальное пространство имен для каждого вызова.
Область видимости — фрагмент программы, в котором пространство имён непосредственно доступно, то есть нет необходимости в использовании записи через точку для того, чтобы поиск имени производился в данном пространстве имён.
Несмотря на статическое определение, области видимости используются динамически. В любой момент времени выполнения программы используется ровно три вложенных области видимости (три непосредственно доступных пространства имен). Сначала поиск имени производится во внутренней области видимости, содержащей локальные имена. Далее — в средней, содержащей глобальные имена модуля. И, наконец, во внешней, содержащей встроенные имена.
Обычно локальная область видимости соответствует локальному пространству имён текущей функции (класса, метода). За пределами функции (класса, метода), локальная область видимости соответствует тому же пространству имён, что и глобальная: пространству имён текущего модуля.
Важно понимать, что область видимости определяется по тексту: глобальная область видимости функции, определенной в модуле — пространство имён этого модуля, независимо от того, откуда или под каким псевдонимом функция была вызвана. С другой стороны, реально поиск имён происходит динамически, во время выполнения. Однако язык развивается в сторону статического разрешения имён, определяемого во время «компиляции», поэтому не стоит полагаться на динамическое разрешение имён! (Фактически, локальные переменные уже определяются статически.)
В языке Python есть особенность: присваивание всегда производится имени в локальной области видимости, если перед этим не было указано явно (инструкция global ), что переменная находится в глобальной области видимости. Присваивание не копирует данные — оно только привязывает имя к объекту. То же самое верно и для удаления: инструкция ‘del x’ удаляет имя x из пространства имён, соответствующего локальной области видимости. В сущности, все операции, которые вводят новые имена, используют локальную область. Так, импортирование модуля и определение функции привязывают модуль или функцию к локальной области видимости.
- Есть одно исключение. Объект-модуль имеет секретный атрибут __dict__, содержащий словарь, используемый для реализации пространства имён модуля. Имя __dict__ является атрибутом, однако не является глобальным именем. Не следует использовать атрибут __dict__ где-либо кроме отладчиков, так как это нарушает абстракцию реализации пространства имён.
Пространство имен и область видимости в классах
В определения классов происходят некоторые хитрые трюки с пространствами имен. Знание того, как работают пространства имен в объектах полезно для любого продвинутого программиста на Python.
Пространство имен — это отображение имен, определенных в объектах. Большинство пространств имен в настоящее время реализованы в виде словарей Python, но это обычно не заметно, кроме производительности, в будущем оно может измениться.
Примерами пространств имен являются:
- набор встроенных имен, содержащих такие функции, как abs() и имена встроенных исключений;
- глобальные переменные в модуле;
- локальные имена переменных в вызове функции.
В некотором смысле набор атрибутов объекта также образует пространство имен. Важно понимать о пространствах имен, то что между именами переменных в разных областях видимости нет абсолютно никакой связи. Например, два разных модуля могут определить функцию maximize без путаницы, пользователи модулей должны использовать точечную нотацию, для извлечения ее значения из модуля.
В языке Python используется слово атрибут для любого имени, следующего за точкой. Например, в выражении z.real , real — это атрибут объекта z . Строго говоря, ссылки на имена переменных, классов и функций в модулях — это ссылки на атрибуты. В выражении modname.funcname , modname — это объект модуля, а funcname — его атрибут. В этом случае происходит прямое сопоставление между атрибутами модуля и глобальными именами, определенными в модуле, они используют одно и то же пространство имен!
Атрибуты могут быть доступны для чтения или записи. В последнем случае возможно присвоение значений атрибутам. Для атрибутов модуля, доступных для записи можно написать modname.the_answer = 42 . Атрибуты, доступные для записи, также могут быть удалены с помощью оператора del . Например, del modname.the_answer удаляет атрибут the_answer из объекта с именем modname .
Пространства имен создаются в разные моменты времени и имеют разное время жизни. Пространство имен, содержащее встроенные имена (имена встроенных функций, имена встроенных исключений и т.д.), создается при запуске интерпретатора Python и никогда не удаляется. Глобальное пространство имен для модуля создается при чтении определения модуля, обычно пространства имен модулей также сохраняются до завершения работы интерпретатора. Операторы, выполняемые вызовом интерпретатора верхнего уровня, считываются из файла сценария или в интерактивном режиме, считаются частью модуля с именем __main__ , поэтому они имеют свое собственное глобальное пространство имен с префиксом __main__ в точечной нотации. Встроенные имена на самом деле, также живут в модуле, такое пространство имен называется builtins .
Локальное пространство имен для функции создается при вызове функции и удаляется, когда функция возвращает или создает исключение, которое не обрабатывается внутри функции. На самом деле, забвение было бы лучшим способом описать то, что происходит на самом деле. Рекурсивные вызовы имеют свое собственное локальное пространство имен.
Область видимости — это текстовая область программы Python, в которой пространство имен доступно непосредственно. “Непосредственно доступный » здесь означает, что безусловная ссылка на имя пытается найти имя в пространстве имен.
Области видимости определяются статически, но используются динамически. В любой момент во время выполнения существует по крайней мере три вложенные области, пространства имен которых доступны непосредственно:
- Самая внутренняя область, которая ищется первой, содержит локальные имена. Области видимости любых вложенных функций, поиск которых начинается с ближайшей охватывающей области, содержит нелокальные, но также и неглобальные имена.
- Следующая за последней область содержит глобальные имена текущего модуля.
- Самая внешняя область (последний поиск) — это пространство имен, содержащее встроенные имена.
Если имя перемененной объявлено глобальным global , то все ссылки и присвоения переходят непосредственно в среднюю область — [2], содержащую глобальные имена модуля. Для повторного связывания переменных, найденных вне внутренней области (вложенные функции), можно использовать нелокальный оператор nonlocal . Если переменные не являются локальными, то эти переменные доступны только для чтения. Попытка записи в такую переменную просто создаст новую локальную переменную в самой внутренней области, оставив внешнюю переменную с идентичным именем неизменной.
Обычно локальная область ссылается на локальные имена (текстуально) текущей функции. Вне функций локальная область ссылается на то же пространство имен, что и глобальная область — пространство имен модуля. Определения классов помещают в локальную область еще одно пространство имен.
Важно понимать, что области видимости определяются текстуально: глобальная область видимости функции, определенной в модуле, является пространством имен этого модуля, независимо от того, откуда и под каким псевдонимом вызывается функция. С другой стороны, фактический поиск имен выполняется динамически во время выполнения, однако определение языка эволюционирует в сторону статического разрешения имен во время “компиляции”, поэтому не полагайтесь на динамическое разрешение имен! На самом деле локальные переменные уже определены статически.
Особенность Python заключается в том, что если нет глобального global или нелокального nonlocal оператора — присвоения имен всегда идут в самую внутреннюю область. Присвоение не копируют данные, они просто привязывают имена к объектам. То же самое верно и для удалений. Оператор del x удаляет привязку x из пространства имен, на которое ссылается локальная область. Фактически все операции, вводящие новые имена, используют локальную область: в частности, операторы импорта и определения функций связывают имя модуля или функции в локальной области.
Глобальный оператор global может использоваться для указания определенным переменным, что они находятся в глобальной области видимости и должны быть восстановлены в этой области. Нелокальный оператор nonlocal указывает, что определенные переменные находятся в закрытой области видимости и должны быть восстановлены в этой области.
Пример:
Пример отчетливо показывает динамическую сущность языка Python, последовательность поиска имен переменных, а так же демонстрирует, как ссылаться на различные области видимости в пространстве имен.
def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam" def do_global(): global spam spam = "global spam" spam = "test spam" do_local() print("After local assignment:", spam) do_nonlocal() print("After nonlocal assignment:", spam) do_global() print("After global assignment:", spam) scope_test() print("In global scope:", spam)
Выходные данные примера кода:
# After local assignment: test spam # After nonlocal assignment: nonlocal spam # After global assignment: nonlocal spam # In global scope: global spam
Обратите внимание, что локальное присвоение spam = «local spam» во внутренней функции do_local() не изменило значение spam , присвоенное во внешней функции scope_test . Нелокальное nonlocal присвоение spam = «nonlocal spam» во внутренней функции do_nonlocal() изменило значение spam , а глобальное global присвоение spam = «global spam» во внутренней функции do_global() изменило значение spam на уровне модуля.
Обратите внимание, что перед выполнением функции scope_test() не было никакого присвоения значения для переменной spam . Использование оператора global во внутренней функции do_global() восстановил переменную spam в глобальной области видимости, прежде чем она вывелась на печать!
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Пространства имен. Области видимости#
Область видимости определяет где переменная доступна. Область видимость переменной зависит от того, где переменная создана.
Чаще всего, речь будет о двух областях видимости:
- глобальной — переменные, которые определены вне функции
- локальной — переменные, которые определены внутри функции
При использовании имен переменных в программе, Python каждый раз ищет, создает или изменяет эти имена в соответствующем пространстве имен. Пространство имен, которое доступно в каждый момент, зависит от области, в которой находится код.
Поиск переменных#
При поиске переменных, Python использует правило LEGB. Например, если внутри функции выполняется обращение к имени переменной, Python ищет переменную в таком порядке по областям видимости (до первого совпадения):
L (local) — в локальной (внутри функции) E (enclosing) — в локальной области объемлющих функций (это те функции, внутри которых находится наша функция) G (global) — в глобальной (в скрипте) B (built-in) — во встроенной (зарезервированные значения Python)
Локальные и глобальные переменные#
- переменные, которые определены внутри функции
- эти переменные становятся недоступными после выхода из функции
- переменные, которые определены вне функции
- эти переменные „глобальны“ только в пределах модуля, чтобы они были доступны в другом модуле, их надо импортировать
Пример локальной intf_config:
In [1]: def configure_intf(intf_name, ip, mask): . : intf_config = f'interface intf_name>\nip address ip> mask>' . : return intf_config . : In [2]: intf_config --------------------------------------------------------------------------- NameError Traceback (most recent call last) ipython-input-2-5983e972fb1c> in module> ----> 1 intf_config NameError: name 'intf_config' is not defined
Обратите внимание, что переменная intf_config недоступна за пределами функции. Для того чтобы получить результат функции, надо вызвать функцию и присвоить результат в переменную:
In [3]: result = configure_intf('F0/0', '10.1.1.1', '255.255.255.0') In [4]: result Out[4]: 'interface F0/0\nip address 10.1.1.1 255.255.255.0'
Пространства имен и области видимости в Python
Данная статья посвящена теме пространств имен в Python. Это структуры, которые используются для организации символических имен, присваиваемых объектам в программе.
В Python понятие объекта является ключевым. Они везде! Фактически все, что программа Python создает или с чем работает, — это объект.
Выражение присваивания создает символическое имя, которое вы можете использовать для ссылки на объект. Так выражение x = ‘foo’ создает символическое имя x , которое ссылается на строковый объект ‘foo’ .
В более сложных программах вам предстоит создавать сотни или тысячи подобных имен, указывающих на конкретные объекты. Как же Python отслеживает все эти имена и предотвращает их путаницу?
Изучив данную статью, вы узнаете:
- как Python структурирует символические имена и объекты в пространствах имен;
- когда Python создает новое пространство имен;
- как осуществляется реализация пространств имен;
- как область переменной определяет видимость символических имен.
Пространства имен в Python
Пространство имен — это совокупность определенных в настоящий момент символических имен и информации об объектах, на которые они ссылаются. Вы можете рассматривать такое пространство как словарь, в котором ключи являются именами объектов, а значения — самими объектами. Каждая пара ключ-значение соотносит имя с соответствующим ему объектом.
Пространства имен — отличная штука! Будем использовать их чаще! — Тим Петерс в “Дзен Python”.
Как утверждает Тим Петерс, пространства имен — отличная штука, которую активно использует Python. Существует 4 типа пространств имен:
- Встроенное.
- Глобальное.
- Объемлющее.
- Локальное.
Они обладают разными жизненными циклами. По мере выполнения программы Python создает необходимые пространства имен и удаляет их, когда потребность в них пропадает. Как правило, в любой момент времени существует множество пространств имен.
Встроенное пространство имен
Встроенное пространство имен содержит имена всех встроенных объектов, которые всегда доступны при работе в Python. Вы можете перечислить объекты во встроенном пространстве с помощью следующей команды:
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError',
'BaseException','BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
'Exception', 'False', 'FileExistsError', 'FileNotFoundError',
'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError',
'ImportError', 'ImportWarning', 'IndentationError', 'IndexError',
'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt',
'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None',
'NotADirectoryError', 'NotImplemented', NotImplementedError', 'OSError',
'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning',
'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration',
'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError',
'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray',
'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate',
'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input',
'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct',
'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
Перечень включает, например, исключение StopIteration , такие встроенные функции, как max() и len() , а также типы объектов — int и str .
При запуске интерпретатор Python создает встроенное пространство имен. Оно сохраняется до тех пор, пока интерпретатор не завершит работу.
Глобальное пространство имен
Глобальное пространство имен содержит имена, определенные на уровне основной программы, и создаётся сразу при запуске тела этой программы. Сохраняется же оно до момента завершения работы интерпретатора.
Строго говоря, могут существовать и другие глобальные пространства имен. Интерпретатор также создает пространство данного типа для любого модуля, загружаемого программой при помощи выражения import .
Теперь, встречая понятие “глобальное пространство имен”, вы будете знать, что оно принадлежит основной программе.
Локальное и объемлющее пространства имен
Интерпретатор создает новое пространство имен при каждом выполнении функции. Это пространство является локальным для функции и сохраняется до момента завершения ее действия.
Функции не существуют независимо друг от друга только на уровне основной программы. Вы также можете определять одну функцию внутри другой.
1 >>> def f():
2 . print('Start f()')
3 .
4 . def g():
5 . print('Start g()')
6 . print('End g()')
7 . return
8 .
9 . g()
10 .
11 . print('End f()')
12 . return
13 .
14
15 >>> f()
16 Start f()
17 Start g()
18 End g()
19 End f()
В этом примере функция g() определена внутри тела f() . Вот что происходит в данном коде:
- Строки с 1 по 12 определяют f() , объемлющую функцию.
- Строки с 4 по 7 определяют g() , вложенную функцию.
- В строке 15 основная программа вызывает f() .
- В строке 9 f() вызывает g() .
Когда основная программа вызывает f() , Python создает для нее новое пространство имен. Аналогичным образом, когда f() вызывает g() , последняя получает свое собственное отдельное пространство. Пространство, созданное для g() , является локальным, а пространство, созданное для f() , — объемлющим.
Все эти пространства существуют до тех пор, пока выполняются соответствующие им функции. По завершении же этих функций Python может не сразу отозвать их из памяти, но при этом все ссылки на содержащиеся в них объекты сразу становятся недоступными.
Область видимости переменной
Наличие нескольких отличных пространств имен означает, что в процессе выполнения программы Python несколько разных экземпляров одного имени могут существовать одновременно. Пока каждый из них находится в собственном пространстве, все они обслуживаются по отдельности, и путаницы не происходит.
Но тут возникает вопрос. Предположим, что вы ссылаетесь на имя x в коде, а оно существует в нескольких пространствах. Как Python узнает, какое именно вы имеете в виду?
Ответ кроется в понятии области видимости имени, представляющей из себя часть программы, в которой данное имя обладает значением. Интерпретатор определяет эту область в среде выполнения, основываясь на том, где располагается определение имени и из какого места в коде на него ссылаются.
С более детальной информацией об области видимости в программировании вы можете ознакомиться на соответствующей странице Википедии.
Отвечая на заданный выше вопрос, отметим, что если ваш код ссылается на имя x , то Python будет искать его следующих областях видимости в таком порядке:
- Локальная. Если вы ссылаетесь на x внутри функции, то интерпретатор сначала ищет его в самой внутренней области, локальной для этой функции.
- Объемлющая. Если x не находится в локальной области, но появляется в функции, располагающейся внутри другой функции, то интерпретатор ищет его в области видимости объемлющей функции.
- Глобальная. Если ни один из вышеуказанных вариантов не принес результатов, то интерпретатор продолжит поиск в глобальной области видимости.
- Встроенная. Если интерпретатор не может найти x где-либо еще, то он направляет поиски во встроенную область видимости.
Эта последовательность составляет суть правила областей видимости LEGB, как его обычно называют в публикациях о Python (хотя, на самом деле, данный термин не встречается в его официальной документации). Интерпретатор начинает поиски имени изнутри, последовательно переходя от локальной области видимости к объемлющей, затем к глобальной и в завершении к встроенной.
Если интерпретатор не находит имя ни в одной из этих областей, то Python вызывает исключение NameError .
Примеры
Ниже представлен ряд примеров с правилом LEGB. В каждом из них самая внутренняя вложенная функция g() пытается вывести в консоль значение переменной с именем x . Обратите внимание, как в каждом примере происходит вывод разного значения x в зависимости от области видимости.
Пример 1. Одно определение
В этом примере имя x определено только в одной области. Оно находится за пределами функций f() и g() и поэтому относится к глобальной области видимости.
1 >>> x = 'global'
2
3 >>> def f():
4 .
5 . def g():
6 . print(x)
7 .
8 . g()
9 .
10
11 >>> f()
12 global
Выражение print() в строке 6 может ссылаться только на одно возможное имя x . Оно отображает объект x , определенный в глобальном пространстве имен, которым является строка ‘global’ .
Пример 2. Двойное определение
В следующем примере определение x появляется в двух местах: одно — вне f() и другое — внутри f() , но за пределами g() .
1 >>> x = 'global'
2
3 >>> def f():
4 . x = 'enclosing'
5 .
6 . def g():
7 . print(x)
8 .
9 . g()
10 .
11
12 >>> f()
13 enclosing
Как и в предыдущем примере g() ссылается на x . Но на этот раз предполагается выбор из двух определений:
- Строка 1 определяет x в глобальной области видимости.
- Строка 4 определяет x снова в объемлющей области видимости.
Согласно правилу LEGB интерпретатор находит значение в объемлющей области перед тем, как искать в глобальной. Поэтому выражение print() в строке 7 отображает ‘enclosing’ вместо ‘global’ .
Пример 3. Тройное определение
Теперь рассмотрим ситуацию, в которой x определен везде и всюду. Одно определение находится вне f() , другое — внутри f() , но за пределами g() , а третье — внутри g() .
1 >>> x = 'global'
2
3 >>> def f():
4 . x = 'enclosing'
5 .
6 . def g():
7 . x = 'local'
8 . print(x)
9 .
10 . g()
11 .
12
13 >>> f()
14 local
Теперь выражение print() в строке 8 должно выбрать из трех возможных вариантов:
- Строка 1 определяет x в глобальной области видимости.
- Строка 4 определяет x в объемлющей области видимости.
- Строка 7 определяет x в третий раз в локальной области g() .
В данном случае правило LEGB утверждает, что g() сначала видит свое собственное значение x , определенное в локальной области видимости. Поэтому выражение print() отображает ‘local’ .
Пример 4. Отсутствие определения
В заключительном примере рассмотрим случай, в котором g() пытается вывести значение x , но x нигде не определен, поэтому мы получим ошибку.
1 >>> def f():
2 .
3 . def g():
4 . print(x)
5 .
6 . g()
7 .
8
9 >>> f()
10 Traceback (most recent call last):
11 File "", line 1, in
12 File "", line 6, in f
13 File "", line 4, in g
14 NameError: name 'x' is not defined
На этот раз Python не находит x ни в одном из пространств имен, поэтому выражение print() в строке 4 выдает исключение NameError .
Словари пространств имен Python
В первом разделе мы уже рекомендовали вам рассматривать пространство имен как словарь, в котором ключи — это имена объектов, а значения — сами объекты. По сути, для глобальных и локальных пространств они именно таковыми и являются! Python действительно реализует их как словари.
Примечание. Встроенное пространство работает не как словарь. Python реализует его как модуль.
Python предоставляет встроенные функции globals() и locals() , обеспечивающие доступ к глобальным и локальным словарям пространств имен.
Функция globals()
Встроенная функция globals() возвращает ссылку на текущий словарь глобального пространства имен. Ее можно использовать для обращения к объектам в этом пространстве. Посмотрим, как это будет выглядеть при запуске основной программы.
>>> type(globals())
>>> globals()
'__loader__': , '__spec__': None,'__annotations__': <>, '__builtins__': >
Как видно из примера, интерпретатор уже поместил ряд записей в globals() . В зависимости от версии Python и операционной системы в вашей среде это может выглядеть несколько иначе, но в целом все равно будет похоже.
Теперь посмотрим, что происходит при определении переменной в глобальной области видимости.
>>> x = 'foo'>>> globals()
'__loader__': , '__spec__': None,'__annotations__': <>, '__builtins__': ,
'x': 'foo'>
Вслед за выражением присваивания x = ‘foo’ в словаре глобального пространства имен появляется новый элемент. Ключ словаря — это имя объекта, т. е. x , а его значение — значение объекта, а именно ‘foo’ .
Как правило, вы обращаетесь к этому объекту обычным способом, ссылаясь на его символическое имя x . Но можно получить к нему доступ косвенным путем посредством словаря глобального пространства имен.
1 >>> x
2 'foo'
3 >>> globals()['x']
4 'foo'
5
6 >>> x is globals()['x']
7 True
Оператор проверки типов is в строке 6 подтверждает, что в действительности это один и тот же объект.
Вы можете создавать и изменять записи в глобальном пространстве имен, также используя функцию globals() .
1 >>> globals()['y'] = 100
2
3 >>> globals()
4 5 '__loader__': , '__spec__': None, 6 '__annotations__': <>, '__builtins__': ,
7 'x': 'foo', 'y': 100>
8
9 >>> y
10 100
11
12 >>> globals()['y'] = 3.14159
13
14 >>> y
15 3.14159
Выражение в строке 1 равнозначно выражению y = 100 , а в строке 12 — выражению y = 3.14159 .
Этот способ создания и изменения объектов в глобальной области видимости выглядит немного оригинальным, учитывая, что с этой задачей справятся и простые выражения присваивания. Но он работает и прекрасно отражает идею.
Функция locals()
Pyhton также предоставляет соответствующую встроенную функцию locals() . Она похожа на globals() , но отличается от нее тем, что обращается к объектам в локальном пространстве имен.
>>> def f(x, y):
. s = 'foo'
. print(locals())
. >>> f(10, 0.5)
Когда locals() вызывается внутри f() , она возвращает словарь, представляющий локальное пространство имен функции. Обратите внимание, что помимо локально определенной переменной s это пространство включает параметры функций x и y , поскольку они также являются локальными для f() .
Если вы вызываете locals() за пределами функции в основой программе, то она ведет себя так же как и globals() .
Между globals() и locals() существует небольшое отличие, о котором вам будет полезно узнать.
globals() возвращает актуальную ссылку на словарь, содержащий глобальное пространство имен. Это значит, что если вы вызываете globals() , сохраняете возвращаемое значение и после этого определяете дополнительные переменные, то эти новые переменные появятся в словаре, на который указывает сохраненное возвращаемое значение.
1 >>> g = globals()
2 >>> g
3 4 '__loader__': , '__spec__': None, 5 '__annotations__': <>, '__builtins__': ,
6 'g': >
7
8 >>> x = 'foo'
9 >>> y = 29
10 >>> g
11 12 '__loader__': , '__spec__': None,13 '__annotations__': <>, '__builtins__': ,
14 'g': , 'x': 'foo', 'y': 29>
В этом примере g является ссылкой на словарь глобального пространства имен. После выражений присваивания в строках 8 и 9 x и y появляются в словаре, на который указывает g .
В свою очередь, locals() возвращает словарь, являющийся текущей копией локального пространства имен, а не ссылкой на него. Дальнейшие дополнения к локальному пространству не повлияют на предыдущее возвращаемое значение locals() до момента ее повторного вызова. Кроме того, вы не можете изменять объекты в текущем локальном пространстве имен, используя возвращаемое значение locals() .
1 >>> def f():
2 . s = 'foo'
3 . loc = locals()
4 . print(loc)
5 .
6 . x = 20
7 . print(loc)
8 .
9 . loc['s'] = 'bar'
10 . print(s)
11 .
12
13 >>> f()
14
15
16 foo
В этом примере loc указывает на возвращаемое значение locals() , являющееся копией локального пространства. Выражение x = 20 в строке 6 добавляет x в локальное пространство, а не в копию, на которую указывает loc . Аналогично этому, выражение в строке 9 изменяет значение для ключа ‘s’ в копии, на которую указывает loc , но это никак не влияет на значение s в текущем локальном пространстве имен.
Это едва уловимое отличие может доставить вам хлопот, если вы его не запомните.
Изменение переменных вне области видимости
Как вам известно, передача аргументов функции в Python может проходить двумя способами: по значению и по ссылке. Иногда функция может изменить свой аргумент в среде вызова, внося коррективы в соответствующий параметр, а иногда у нее такой возможности нет:
- Неизменяемый аргумент никогда не может быть изменен функцией.
- Изменяемый аргумент нельзя переопределять целиком, но зато можно изменять.
Похожая ситуация возникает, когда функция пытается изменить переменную вне своей локальной области, ведь изменение неизменяемого объекта вне ее границ ей недоступно.
1 >>> x = 20
2 >>> def f():
3 . x = 40
4 . print(x)
5 .
6
7 >>> f()
8 40
9 >>> x
10 20
Когда f() выполняет выражение присваивания x = 40 в строке 3, она создает новую локальную ссылку на объект целого числа со значением 40 . На этом этапе f() теряет ссылку на объект с именем x в глобальном пространстве имен. Таким образом, выражение присваивания не влияет на глобальный объект.
Примечание. Когда функция f() выполняет print(x) в строке 4, она отображает 40 , значение своей собственной локальной переменной x . Но после завершения действия f() значение x в глобальной области видимости по прежнему 20 .
Функция может скорректировать объект изменяемого типа, находящийся за пределами ее локальной области видимости, если изменит его внутри:
>>> my_list = ['foo', 'bar', 'baz']
>>> def f():
. my_list[1] = 'quux'
.
>>> f()
>>> my_list
['foo', 'quux', 'baz']
В этом случае my_list — это список, а списки являются изменяемыми типами данных. f() может вносить изменения внутрь my_list , даже если он находится вне локальной области видимости. Но если f() стремится полностью переназначить my_list , то она создаст новый локальный объект и не изменит глобальный my_list .
>>> my_list = ['foo', 'bar', 'baz']
>>> def f():
. my_list = ['qux', 'quux']
.
>>> f()
>>> my_list
['foo', 'bar', 'baz']
Это похоже на процесс, при котором f() стремится модифицировать изменяемый аргумент функции.
Объявление global
А что если вам действительно необходимо изменить значение в глобальной области видимости изнутри f() ? Python делает это возможным благодаря использованию объявления global .
>>> x = 20
>>> def f():
. global x
. x = 40
. print(x)
. >>> f()
40
>>> x
40
Выражение global x указывает на то, что пока выполняется f() , ссылки на имя x будут вести к x , находящемуся в глобальном пространстве имен. Это значит, что присваивание x = 40 не создает новую ссылку. Вместо этого оно присваивает новое значение x в глобальной области видимости.
Как видите, globals() возвращает ссылку на словарь глобального пространства имен. При желании, вместо использования выражения global , можно было бы осуществить то же самое, применив globals() :
>>> x = 20
>>> def f():
. globals()['x'] = 40
. print(x)
. >>> f()
40
>>> x
40
Но особых причин делать это таким способом у нас нет, поскольку объявление global , вероятно, точнее отражает наше намерение. Но тем не менее этот вариант позволяет продемонстрировать принцип работы globals() .
Если же имя, определенное в объявлении global , не существует в глобальной области при запуске функции, то его создаст комбинация выражений global и присваивания.
1 >>> y
2 Traceback (most recent call last):
3 File "", line 1, in
4 y
5 NameError: name 'y' is not defined
6
7 >>> def g():
8 . global y
9 . y = 20
10 .
11
12 >>> g()
13 >>> y
14 20
В этом примере при запуске g() в глобальной области нет объекта с именем y , но g() создаст его с помощью выражения global y в строке 8.
Вы также можете указать несколько имен, разделенных запятыми, в одном объявлении global .
1 >>> x, y, z = 10, 20, 30
2
3 >>> def f():
4 . global x, y, z
5 .
Здесь x , y и z объявляются для ссылок на объекты в глобальной области видимости посредством одного выражения global в строке 4.
Имя, определенное в объявлении global , не может появиться в функции раньше выражения global .
1 >>> def f():
2 . print(x)
3 . global x
4 .
5 File "", line 3
6 SyntaxError: name 'x' is used prior to global declaration
Цель выражения global x в строке 3 состоит в том, чтобы ссылки на x вели к объекту в глобальной области видимости. Но выражение print() в строке 2 ссылается на x до объявления global , что приводит к выводу исключения SyntaxError .
Объявление nonlocal
Схожая ситуация наблюдается с определениями вложенных функций. Объявление global позволяет функции обращаться к объекту в глобальной области видимости и менять его. А что если вложенной функции необходимо изменить объект в объемлющей области? Рассмотрим пример:
1 >>> def f():
2 . x = 20
3 .
4 . def g():
5 . x = 40
6 .
7 . g()
8 . print(x)
9 .
10
11 >>> f()
12 20
В этом примере первое определение x дано в объемлющей области, а не в глобальной. Точно так же, как g() не может напрямую изменить переменную в глобальной области, она не способна изменить x в объемлющей области функции. После присваивания x = 40 в строке 5 x в объемлющей области остается 20 .
Ключевое слово global не способствует решению этой ситуации:
>>> def f():
. x = 20
.
. def g():
. global x
. x = 40
.
. g()
. print(x)
. >>> f()
20
Поскольку x находится в объемлющей области функции, а не в глобальной, ключевое слово global здесь не сработает. После завершения действия g() значение x в объемлющей области остается 20 .
На самом деле, в этом примере выражение global x не только не предоставляет доступ к x в объемлющей области, но также создает объект с именем x в глобальной области со значением 40 .
>>> def f():
. x = 20
.
. def g():
. global x
. x = 40
.
. g()
. print(x)
. >>> f()
20
>>> x
40
Для модификации x в объемлющей области изнутри g() вам потребуется аналогичное ключевое слово nonlocal . Имена, определенные после nonlocal , ссылаются на переменные в ближайшей объемлющей области.
1 >>> def f():
2 . x = 20
3 .
4 . def g():
5 . nonlocal x
6 . x = 40
7 .
8 . g()
9 . print(x)
10 .
11
12 >>> f()
13 40
После выражения nonlocal x в строке 5, когда g() ссылается на x , оно обращается к x в ближайшей объемлющей области, чье определение дано внутри f() в строке 2.
Выражение print() в завершении f() в строке 9 подтверждает, что вызов g() изменил значение x в объемлющей области на 40 .
Лучшие практики
Несмотря на то, что Python предоставляет ключевые слова global и nonlocal , бывают ситуации, когда их использование не рекомендовано.
Когда функция меняет данные вне локальной области, используя ключевые слова ( global или nonlocal ) или напрямую преобразуя изменяемый тип внутри, то это своего рода побочный эффект, аналогичный ситуации с изменением функцией одного из своих аргументов. Частая модификация глобальных переменных обычно не приветствуется ни в Python, ни в других языках программирования.
Как и со многими другими аспектами жизни, это, скорее всего, дело вкуса. Бывают ситуации, когда разумный подход к изменению глобальных переменных помогает уменьшить сложность программы.
По крайней мере, использование ключевого слова global в Python явно свидетельствует об изменении функцией глобальной переменной. Во многих других языках функция может менять глобальную переменную только посредством присваивания, не объявляя об этом каким-либо образом. Вследствие чего становится довольно сложно отследить, где происходит изменение глобальных данных.
В общем, изменение переменных за пределами локальной области видимости обычно не требуется. Почти всегда существует лучший способ — обычно с возвращаемыми значениями функции.
Заключение
Фактически все, что программа Python использует или с чем работает, — это объект. Даже будучи короткой, она создаст много разных объектов. А в более сложной программе их количество исчисляется тысячами. Python должен отслеживать все эти объекты и их имена, и помогают ему в этом пространства имен.
Теперь вы знаете:
- какие типы пространств имен существуют в Python;
- когда Python создает новое пространство имен;
- какую структуру использует Python для реализации пространств имен;
- как пространства имен определяют область видимости в программе Python.
- Утиная типизация в Python — 3 примера
- Python в 2021: расписание релизов и основные функции
- Python: 5 ошибок в применении охвата списка