Имена и области видимости в языке Python
Пространства имен в Python
Пространства имен (namespaces) это отображения имен на объекты. Каждое имя существует в определенном пространстве имен, и поиск имени происходит по правилу LEGB (Local, Enclosing, Global, Built-in). Наиболее эффективный подход к работе с пространствами имен заключается в четком следовании этому порядку и явном объявлении намерений изменить переменную из объемлющей области с помощью ключевых слов global или nonlocal. Это минимизирует побочные эффекты и делает код предсказуемым.
x = 10 # глобальная переменная
def func():
global x
x = 20 # изменение глобальной переменной
func()
print(x) # 20Namespaces python (пространства имен в python)
20
Типичная ошибка: попытка изменить глобальную переменную без global приводит к созданию новой локальной переменной (UnboundLocalError при обращении до присваивания). Решение: всегда явно объявлять global при необходимости изменения глобального имени.
Как изменить глобальную переменную внутри функции?
Если требуется не только читать, но и изменять глобальную переменную, внутри функции следует указать global имя_переменной.
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1
1
Проблема: если забыть global, Python создаст локальную переменную с тем же именем, и глобальная останется неизменной. Возникает ошибка UnboundLocalError при попытке прочитать глобальную переменную до локального присваивания.
Цель: явное указание на изменение глобального состояния. Используется редко, для счетчиков, конфигурационных переменных.
Как изменить переменную внешней (объемлющей) функции из вложенной?
Для изменения переменной из ближайшей объемлющей области (не глобальной) применяется nonlocal.
def outer():
msg = 'hello'
def inner():
nonlocal msg
msg = 'world'
inner()
print(msg) # world
outer()
world
Ошибка: если переменная не существует в объемлющей области (например, она глобальная), nonlocal вызовет SyntaxError. Решение: проверить, что переменная определена именно в enclosing scope, а не в глобальном.
Цель: поддержка состояния в замыканиях, вложенных декораторах, счетчиках внутри фабрик функций.
Как просмотреть содержимое текущего пространства имен?
Функции locals() и globals() возвращают словари локальных и глобальных имен соответственно.
a = 1
def show():
b = 2
print('local:', locals())
show()
print('global:', globals()['a'])
local: {'b': 2}
global: 1
Важно помнить: locals() внутри класса возвращает пространство имен класса на момент определения, а внутри функции - локальные переменные. Не стоит полагаться на изменение словаря locals().
Цель: отладка, динамическая проверка имен, генерация документации.
Как динамически создать глобальную переменную?
Можно добавлять имена в словарь, возвращаемый globals(), но это считается плохой практикой.
globals()['new_var'] = 42
print(new_var) # 42
42
Проблема: такой код затрудняет чтение, может привести к конфликтам имен. Решение: использовать обычное присваивание или словарь для хранения динамических данных.
Цель: ситуации, когда имя переменной формируется динамически (например, из конфигурации), но лучше применять словарь.
Как избежать конфликтов имен при импорте?
Импорт создает отдельное пространство имен модуля. Конфликты решаются через from модуль import имя (копирует имя в текущее пространство) или import модуль (сохраняет префикс).
import math
print(math.pi) # 3.14159
from math import pi
print(pi) # 3.14159
3.141592653589793 3.141592653589793
Ошибка: при from math import * могут быть затенены встроенные имена. Решение: избегать звездочки, использовать явные имена или псевдонимы.
Цель: контроль пространства имен импортируемых модулей, предотвращение неожиданных совпадений.
Как получить список всех имен в пространстве имен?
dir() без аргументов возвращает имена текущего пространства, с аргументом - имена модуля или объекта.
import string
print([n for n in dir(string) if n.startswith('ascii')])
['ascii_lowercase', 'ascii_uppercase']
dir() не различает атрибуты класса и экземпляра, а также включает унаследованные имена. Для точного анализа используется vars() или __dict__.
Цель: интроспекция, отладка, изучение атрибутов объектов.
Расширенные примеры работы с пространствами имен
Вложенные функции и несколько уровней nonlocal:
def level1():
x = 1
def level2():
nonlocal x
x = 2
def level3():
nonlocal x
x = 3
level3()
level2()
print(x) # 3
level1()
3
Использование locals() в теле класса (пространство имен класса создается во время выполнения тела):
class MyClass:
a = 1
b = a + 2 # 3
print('locals during class definition:', locals())
# Вывод (пример): locals during class definition: {'__module__': '__main__', '__qualname__': 'MyClass', 'a': 1, 'b': 3}
locals during class definition: {'__module__': '__main__', '__qualname__': 'MyClass', 'a': 1, 'b': 3}
Динамическое создание переменных с помощью exec (опасно, но демонстрирует пространства имен):
namespace = {'x': 10}
exec('y = x + 5', namespace)
print(namespace['y']) # 15
15
Проверка UnboundLocalError и скрытие глобальной переменной:
z = 100
def problem():
print(z) # пытается прочитать, но потом есть локальное присваивание
z = 200
# problem() # UnboundLocalError: local variable 'z' referenced before assignment
(раскомментировав, получим ошибку)
Использование __dict__ объекта для просмотра его пространства имен:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p.__dict__) # {'x': 3, 'y': 4}
{'x': 3, 'y': 4}
Передача пространства имен через аргумент функции (например, для модульного тестирования):
def apply_func(func, ns):
"""Выполняет функцию в заданном пространстве имен."""
return func(ns)
namespace = {'value': 10}
result = apply_func(lambda ns: ns['value'] * 2, namespace)
print(result) # 20
20
Имитация пространства имен через обычный словарь (без использования global):
config = {'debug': False, 'port': 8080}
def set_debug(val):
config['debug'] = val
set_debug(True)
print(config) # {'debug': True, 'port': 8080}
{'debug': True, 'port': 8080}
Все примеры показывают, как Python управляет именами, и как можно явно контролировать области видимости.