Пространства имен и область видимости в 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 существует четыре типа пространств имен:

  1. Built-In
  2. Global
  3. Замыкающий
  4. Локальный

У них разное время жизни. По мере выполнения программы 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 можно найти на следующих ресурсах:

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

Локальное и окружающее пространства имен

Как вы узнали из предыдущего урока по функциям, интерпретатор создает новое пространство имен всякий раз, когда выполняется функция. Это пространство имен является локальным для функции и существует до тех пор, пока функция не завершится.

Функции не существуют независимо друг от друга только на уровне основной программы. Вы также можете определить одну функцию внутри другой:

 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 в следующих пространствах имен в указанном порядке:

  1. Локальная: Если вы ссылаетесь на x внутри функции, то интерпретатор сначала ищет ее в самой внутренней области видимости, которая локальна для этой функции.
  2. Охватывающая: Если x не находится в локальной области видимости, а появляется в функции, которая находится внутри другой функции, то интерпретатор ищет его в области видимости охватывающей функции.
  3. Global: Если ни один из вышеперечисленных способов поиска не дал результата, то интерпретатор ищет в глобальной области видимости.
  4. Built-in: Если больше нигде не удается найти x, то интерпретатор пробует встроенную область видимости.

Это правило LEGB, как его принято называть в литературе по Python (хотя на самом деле этот термин не встречается в документации по Python). Интерпретатор ищет имя изнутри наружу, заглядывая в local, enclosing, global и, наконец, built-in scope:

Diagram of Local, Enclosed, Global, and Built-in Scopes

Если интерпретатор не находит имя ни в одном из этих мест, то 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 "<stdin>", line 1, in <module>
12   File "<stdin>", line 6, in f
13   File "<stdin>", 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())
<class 'dict'>

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}

Как видите, интерпретатор уже поместил несколько записей в globals(). В зависимости от версии Python и операционной системы, в вашем окружении это может выглядеть немного иначе. Но должно быть похоже.

Теперь посмотрите, что произойдет, если вы определите переменную в глобальной области видимости:

>>> x = 'foo'

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
'__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
'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 {'__name__': '__main__', '__doc__': None, '__package__': None,
 5 '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
 6 '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
 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)
{'s': 'foo', 'y': 0.5, 'x': 10}

При вызове внутри f(), locals() возвращает словарь, представляющий локальное пространство имен функции. Обратите внимание, что, помимо локально определенной переменной s, локальное пространство имен включает параметры функции x и y, поскольку они также являются локальными для f().

Если вы вызываете locals() вне функции в основной программе, то она ведет себя так же, как globals().

Глубокое погружение: Тонкое различие между globals() и locals()

Между globals() и locals() есть одно небольшое различие, о котором полезно знать.

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

 1 >>> g = globals()
 2 >>> g
 3 {'__name__': '__main__', '__doc__': None, '__package__': None,
 4 '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
 5 '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
 6 'g': {...}}
 7
 8 >>> x = 'foo'
 9 >>> y = 29
10 >>> g
11 {'__name__': '__main__', '__doc__': None, '__package__': None,
12 '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None,
13 '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
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 {'s': 'foo'}
15 {'s': 'foo'}
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 в глобальной области видимости:

Example of Python global keyword usage

Глобальная декларация

Как вы уже видели, 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 "<pyshell#79>", line 1, in <module>
 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 "<stdin>", 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.<<<6>> 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:

Python nonlocal keyword example

Нелокальная декларация

Утверждение print() в конце f() на строке 9 подтверждает, что вызов g() изменил значение x в объемлющей области на 40.

Лучшие практики

Несмотря на то, что в Python предусмотрены ключевые слова global и nonlocal, использовать их не всегда целесообразно.

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

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

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

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

Заключение

Практически все, что использует или с чем работает программа на Python, является объектом. Даже в короткой программе будет создано множество различных объектов. В более сложной программе их количество может исчисляться тысячами. Python должен отслеживать все эти объекты и их имена, и он делает это с помощью пространств имен.

В этом уроке вы узнали:

  • Что такое различные пространства имен в Python
  • Когда Python создает новое пространство имен
  • Какую структуру использует Python для реализации пространств имен
  • Как пространства имен определяют объем в программе на Python

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

Вернуться на верх