Пространство имен и область видимости в классах
В определения классов происходят некоторые хитрые трюки с пространствами имен. Знание того, как работают пространства имен в объектах полезно для любого продвинутого программиста на 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, структурах, используемых для организации символических имен, присвоенных объектам в программе на Python.
Предыдущие уроки этой серии подчеркивали важность объектов в Python. Объекты есть везде! Практически все, что создает или с чем работает ваша программа на Python, является объектом.
Оператор присвоения создает символическое имя, которое можно использовать для ссылки на объект. Оператор x = ‘foo’ создает символическое имя x , которое ссылается на объект string ‘foo’ .
В программе любой сложности вы создадите сотни или тысячи таких имен, каждое из которых будет указывать на определенный объект. Как Python отслеживает все эти имена, чтобы они не мешали друг другу?
В этом уроке вы узнаете:
- Как Python организует символические имена и объекты в пространствах имен
- Когда Python создает новое пространство имен
- Как реализуются пространства имен
- Как область видимости переменных определяет видимость символьных имен
Бесплатный бонус: 5 Thoughts On Python Mastery — бесплатный курс для разработчиков на Python, который покажет вам дорожную карту и образ мышления, необходимые для того, чтобы поднять ваши навыки работы на Python на новый уровень.
Пространства имен в Python
Пространство имен — это коллекция определенных в данный момент символических имен вместе с информацией об объекте, на который ссылается каждое имя. Пространство имен можно представить как словарь, в котором ключами являются имена объектов, а значениями — сами объекты. Каждая пара ключ-значение сопоставляет имя с соответствующим объектом.
Пространства имен — это одна чертовски замечательная идея — давайте делать их больше!
— The Zen of Python, by Tim Peters
Как говорит Тим Питерс, пространства имен — это не просто здорово. Они ошеломляюще великолепны, и Python широко их использует. В программе на Python существует четыре типа пространств имен:
- Built-In
- Global
- Замыкающий
- Локальный
У них разное время жизни. По мере выполнения программы Python создает пространства имен по мере необходимости и удаляет их, когда они больше не нужны. Как правило, в каждый момент времени существует множество пространств имен.
Встроенное пространство имен
Пространство имен built-in содержит имена всех встроенных объектов 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 создает встроенное пространство имен. Это пространство имен продолжает существовать до завершения работы интерпретатора.
Глобальное пространство имен
Пространство имен global namespace содержит любые имена, определенные на уровне основной программы. Python создает глобальное пространство имен при запуске основного тела программы, и оно продолжает существовать до завершения работы интерпретатора.
Строго говоря, это может быть не единственное существующее глобальное пространство имен. Интерпретатор также создает глобальное пространство имен для любого модуля, который ваша программа загружает с помощью оператора import . Дополнительную информацию о главных функциях и модулях в Python можно найти на следующих ресурсах:
- Определение основных функций в Python
- Модули и пакеты Python — введение
- Курс: Модули и пакеты Python
Более подробно о модулях вы узнаете в одном из следующих уроков этой серии. Пока же, когда вы видите термин глобальное пространство имен, думайте о том, что оно принадлежит основной программе.
Локальное и окружающее пространства имен
Как вы узнали из предыдущего урока по функциям, интерпретатор создает новое пространство имен всякий раз, когда выполняется функция. Это пространство имен является локальным для функции и существует до тех пор, пока функция не завершится.
Функции не существуют независимо друг от друга только на уровне основной программы. Вы также можете определить одну функцию внутри другой:
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() . Аналогично, когда f() вызывает g() , g() получает свое собственное отдельное пространство имен. Пространство имен, созданное для g() , является локальным пространством имен, а пространство имен, созданное для f() , является закрывающим пространством имен.
Каждое из этих пространств имен продолжает существовать до тех пор, пока соответствующая функция не завершит свою работу. Python может не сразу освободить память, выделенную для этих пространств имен, когда их функции завершаются, но все ссылки на объекты, которые они содержат, перестают быть действительными.
Область переменных
Существование нескольких отдельных пространств имен означает, что во время выполнения программы на Python одновременно может существовать несколько различных экземпляров определенного имени. Пока каждый экземпляр находится в отдельном пространстве имен, все они обслуживаются отдельно и не мешают друг другу.
Но в связи с этим возникает вопрос: Предположим, вы ссылаетесь на имя x в своем коде, а x существует в нескольких пространствах имен. Как Python узнает, какое из них вы имеете в виду?
Ответ кроется в понятии scope. Область действия scope имени — это область программы, в которой это имя имеет смысл. Интерпретатор определяет это во время выполнения программы, основываясь на том, где встречается определение имени и где в коде на это имя есть ссылка.
Дальнейшее чтение: См. страницу Википедии scope in computer programming для очень подробного обсуждения области видимости переменных в языках программирования.
Если вы предпочитаете погрузиться в видеокурс, то посмотрите Exploring Scopes and Closures in Python или вернитесь к основам с помощью Python Basics: Scopes.
Возвращаясь к вопросу выше, если ваш код ссылается на имя x , то Python ищет x в следующих пространствах имен в указанном порядке:
- Локальная: Если вы ссылаетесь на x внутри функции, то интерпретатор сначала ищет ее в самой внутренней области видимости, которая локальна для этой функции.
- Охватывающая: Если x не находится в локальной области видимости, а появляется в функции, которая находится внутри другой функции, то интерпретатор ищет его в области видимости охватывающей функции.
- Global: Если ни один из вышеперечисленных способов поиска не дал результата, то интерпретатор ищет в глобальной области видимости.
- Built-in: Если больше нигде не удается найти x , то интерпретатор пробует встроенную область видимости.
Это правило LEGB, как его принято называть в литературе по Python (хотя на самом деле этот термин не встречается в документации по Python). Интерпретатор ищет имя изнутри наружу, заглядывая в local, enclosing, global и, наконец, built-in scope:

Если интерпретатор не находит имя ни в одном из этих мест, то 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() , '__spec__': None, '__annotations__': <>, '__builtins__': >
Как видите, интерпретатор уже поместил несколько записей в globals() . В зависимости от версии Python и операционной системы, в вашем окружении это может выглядеть немного иначе. Но должно быть похоже.
Теперь посмотрите, что произойдет, если вы определите переменную в глобальной области видимости:
>>> x = 'foo' >>> globals() , '__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 , '__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()
Python также предоставляет соответствующую встроенную функцию, называемую locals() . Она похожа на globals() , но вместо этого получает доступ к объектам в локальном пространстве имен:
>>> def f(x, y): . s = 'foo' . print(locals()) . >>> f(10, 0.5)
При вызове внутри f() , locals() возвращает словарь, представляющий локальное пространство имен функции. Обратите внимание, что, помимо локально определенной переменной s , локальное пространство имен включает параметры функции x и y , поскольку они также являются локальными для f() .
Если вы вызываете locals() вне функции в основной программе, то она ведет себя так же, как globals() .
Глубокое погружение: Тонкое различие между globals() и locals()
Между globals() и locals() есть одно небольшое различие, о котором полезно знать.
globals() возвращает фактическую ссылку на словарь, содержащий глобальное пространство имен. Это означает, что если вы вызовете globals() , сохраните возвращаемое значение и впоследствии определите дополнительные переменные, то эти новые переменные появятся в словаре, на который указывает сохраненное возвращаемое значение:
1 >>> g = globals() 2 >>> g 3 , '__spec__': None, 5 '__annotations__': <>, '__builtins__': , 6 'g': > 7 8 >>> x = 'foo' 9 >>> y = 29 10 >>> g 11 , '__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, вы узнали, что передача аргументов в Python немного похожа на pass-by-value и немного на pass-by-reference. Иногда функция может изменить свой аргумент в вызывающей среде, внеся изменения в соответствующий параметр, а иногда нет:
- Аргумент immutable никогда не может быть изменен функцией.
- А аргумент mutable не может быть переопределен оптом, но может быть изменен на месте.
Примечание: Подробнее об изменении аргументов функций см. в статьях Pass-By-Value vs Pass-By-Reference в Pascal и Pass-By-Value vs Pass-By-Reference в Python.
Аналогичная ситуация возникает, когда функция пытается изменить переменную за пределами своей локальной области видимости. Функция вообще не может изменить объект immutable вне локальной области видимости:
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, но и в других языках программирования.
Как и во многих других случаях, это в некоторой степени вопрос стиля и предпочтений. Бывают случаи, когда разумное использование модификации глобальных переменных может уменьшить сложность программы.
В Python использование ключевого слова global , по крайней мере, делает явным тот факт, что функция изменяет глобальную переменную. Во многих языках функция может изменять глобальную переменную просто путем присваивания, никак об этом не сообщая. Это может сильно затруднить отслеживание того, где именно изменяются глобальные данные.
В целом, модификация переменных за пределами локальной области видимости обычно не нужна. Почти всегда есть лучший способ, обычно с возвращаемыми значениями функций.
Заключение
Практически все, что использует или с чем работает программа на Python, является объектом. Даже в короткой программе будет создано множество различных объектов. В более сложной программе их количество может исчисляться тысячами. Python должен отслеживать все эти объекты и их имена, и он делает это с помощью пространств имен.
В этом уроке вы узнали:
- Что такое различные пространства имен в Python
- Когда Python создает новое пространство имен
- Какую структуру использует Python для реализации пространств имен
- Как пространства имен определяют объем в программе на Python
Многие приемы программирования используют тот факт, что каждая функция в Python имеет свое собственное пространство имен. В следующих двух уроках этой серии вы изучите два таких приема: функциональное программирование и рекурсия.
Решение модуля 1.4 Python: основы и применение
В какой момент создается локальное пространство имен для функции?
В момент вызова функции
Какие числа будут выведены на экран в результате выполнения данного кода?
x, y = 1, 2 def foo(): global y if y == 2: x = 2 y = 1 foo() print(x) if y == 1: x = 3 print(x)
Описание решения:
Давайте разберем шаги выполнения программы:
x, y = 1, 2 : Создаются две переменные x и y и присваиваются значения 1 и 2 соответственно.
def foo(): : Определение функции foo .
global y : Внутри функции foo объявляется, что будет использоваться глобальная переменная y , а не создаваться новая локальная переменная y внутри функции.
if y == 2: : Проверка условия. Если значение глобальной переменной y равно 2, выполняется блок кода внутри условия.
x = 2 : Создается новая локальная переменная x и ей присваивается значение 2 (не глобальная переменная x , так как она не объявлена как глобальная).
y = 1 : Глобальной переменной y присваивается новое значение 1.
foo() : Вызывается функция foo , которая изменяет значения переменных x и y внутри себя.
print(x) : Выводится значение переменной x после выполнения функции foo . Здесь используется глобальная переменная x , и ее значение не изменилось внутри функции foo . Поэтому будет выведено значение глобальной переменной x , равное 1.
if y == 1: : Проверка условия. Если глобальная переменная y равна 1, выполняется блок кода внутри условия.
x = 3 : Глобальной переменной x присваивается новое значение 3.
print(x) : Выводится значение переменной x после выполнения второго блока кода. Здесь используется глобальная переменная x , и ее значение изменилось в блоке кода после выполнения функции foo . Поэтому будет выведено значение глобальной переменной x , равное 3.
- create – создать новое пространство имен с именем внутри пространства
- add – добавить в пространство переменную
- get – получить имя пространства, из которого будет взята переменная при запросе из пространства , или None, если такого пространства не существует
# Ввод числа запросов n = int(input()) # Словарь для хранения родительских пространств имен parent = # Словарь для хранения переменных в каждом пространстве имен vs = # Обработка каждого запроса for _ in range(n): t, fst, snd = input().split() # Создание нового пространства имен if t == "create": parent[fst] = snd vs[fst] = set() # Добавление переменной в пространство имен elif t == "add": vs[fst].add(snd) # Получение имени пространства, из которого будет взята переменная elif t == "get": while fst is not None: # Проверка, содержится ли переменная в текущем пространстве if snd in vs[fst]: break # Переход к родительскому пространству fst = parent[fst] # Вывод результата print(fst)
Если у вас не отображается решение последних задач, значит у вас включен блокировщик рекламы который вырезает эти ответы
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__ где-либо кроме отладчиков, так как это нарушает абстракцию реализации пространства имён.