Внимание! На этой странице вы найдете материал урока из архивного курса по Python. Курс был написан в 2024 году и по-прежнему актуален для начинающих разработчиков.
Теоретический материал сохранен в исходном виде, а практические задания с автоматической проверкой вынесены в отдельные интенсивы и задания.
Полный список уроков доступен по тегу Архивный курс по Python и на странице первого урока.
Пространство имен
После исследования объектов классов и экземпляров, можем сказать что с пространствами имен в Python закончили. Имена могут быть неуточненными (например, A - область видимости LEGB) и уточненными (например, object.A - пространство имен объектов). Так же определенные области видимости определяют пространства имен объектов (классы и модули).
Простые имена
Неуточненные простые имена следуют правилу LEGB и являются глобальными, если не было выполнено присваивание.
A = значение: присваивание делает имя локальным в текущей локальной области видимости: создает или изменяет имя, если оно не объявлено какglobalилиnonlocal.A: если в коде используется имя, то поиск осуществляется согласно правилу LEGB. Поиск во включающих классах не осуществляется.
Имена атрибутов
Уточненные имена атрибутов подчиняются правилам для модулей и классов. В случае с классами, правила дополняются из-за процедуры наследования.
object.A = значение: создает или изменяет имя атрибутаAв пространстве именobject.object.A: в случае использования имени, интерпретатор ищет имя атрибутаAвobjectи затем во всех доступных классах выше, в соответствии с процедурой наследования (для классов). Для объектов, таких как модули, интерпретатор извлекаетAнапрямую изobject.
В Python критическое значение имеет место, где переменной присваивается значение. Оно полностью определяет область видимости или объект, в котором имя будет находиться.
Обобщим все в одном примере (без учета различных исключительных ситуаций):
# файл my_module.py
var = 1 # глобальное имя/атрибут модуля
# (var или my_module.var)
def f1():
print(var) # доступ к глобальному имени var
def f2():
var = 2 # локальная переменная в функции
print(var)
class C1:
var = 3 # атрибут класса (C1.var)
def m1(self):
var = 4 # локальная переменная в методе (var)
self.var = 5 # атрибут экземпляра (экземпляр.var)
if __name__ == '__main__':
print(var) # => 1
f1() # => 1
f2() # => 2
print(var) # => 1
o = C1()
print(o.var) # => 3
o.m1()
print(o.var) # => 5
print(C1.var) # => 3
Можно указывать класс для извлечения его атрибута (C1.var), но нельзя извлечь локальную переменную из метода класса (так нельзя: C1.m1.var). Так работает область видимости и, к тому же, локальные переменные существуют в памяти во время выполнения вызова метода (функции).
В примере выше имя var присваивается пять раз и во всех случаях это разные переменные:
1- атрибут модуля;2- локальная переменная в функции;3- атрибут класса;4- локальная переменная в методе класса;5- атрибут экземпляра.
Некоторые имена доступны за пределами файла, но для начала необходимо импортировать этот файл:
import my_module
Далее рассмотрим на примере. Создадим файл example.py и подключим предыдущий пример с помощью import:
# файл example.py
import my_module
var = 6 # глобальное имя в example.py
print(var) # => 6
print(my_module.var) # => 1
my_module.f1() # => 1
my_module.f2() # => 2
print(my_module.C1.var) # => 3
L = my_module.C1()
print(L.var) # => 3
L.m1()
print(L.var) # => 5
Усложним пример операторами global и nonlocal, чтобы функция была в состоянии изменять имена за пределами самой себя.
var = 1 # глобальное имя в модуле
def f1():
print(var) # выводим глобальное имя в модуле
def f2():
global var
var = 2 # изменяем глобальное имя в модуле
def f3():
var = 3 # локальное имя в функции
def enclosed():
var = 4 # локальное имя в функции enclosed
enclosed()
print(var)
def f4():
var = 3 # локальное имя в функции
def enclosed():
nonlocal var
var = 5 # изменение локального имени в функции f4
enclosed()
print(var)
if __name__ == '__main__':
print(var) # => 1
f1() # => 1
f2() # изменяем глобальное значение имени var в модуле на 2
print(var) # => 2
f3() # => 3
f4() # => 5
Использование переменных с одинаковыми именами - плохая практика. Пространство имен в Python предназначено для предотвращения непредумышленных конфликтов между именами, задействованных в разных контекстах.
Вложение классов в функции и пространство имен
Вкладывать можно не только функции, но и классы. Как правило, классы определяются на верхнем уровне модуля, но в некоторых случаях их вкладывают в генерирующие их функции. Это может быть сложно для понимания, поэтому не расстраивайтесь, если ничего непонятно. Рассмотрим на примере:
a = 1
def func1():
print(f'func1: ', a)
class C:
print(f'C: ', a)
def m1(self):
print(f'm1: ', a)
def m2(self):
a = 3
print(f'm2: ', a)
inst = C()
inst.m1()
inst.m2()
print(f'module: ', a)
func1()
Внутри функции func1() все ссылки на a направляются в глобальную область видимости, кроме той, что в методе m2(). Что произойдет, если мы переопределим переменную a внутри функции func1()?
a = 1
def func1():
a = 10
print(f'func1: ', a) # => 10
class C:
print(f'C: ', a) # => 10
def m1(self):
print(f'm1: ', a) # => 10
def m2(self):
a = 3
print(f'm2: ', a) # => 3
inst = C()
inst.m1()
inst.m2()
print(f'module: ', a) # => 1
func1()
Думаю, результат ожидаемый. Теперь добавим переменную a в поле класса C:
a = 1
def func1():
a = 10
print(f'func1: ', a) # => 10
class C:
a = 30
print(f'C: ', a) # => 30
def m1(self):
print(f'm1: ', a) # => 10
print(f'm1, self.a: ', self.a) # => 30
def m2(self):
a = 3
print(f'm2: ', a) # => 3
self.a = 40
print(f'm2, self.a: ', self.a) # => 40
inst = C()
inst.m1()
inst.m2()
print(f'module: ', a) # => 1
func1()
Результат интереснее. Как видите, присваивание в локальных областях видимости скрывают глобальные имена и совпадающие имена в объемлющих функциях независимо от вложенности. Так же обратите внимание на обращение к именам класса: через self (в примере self.a).
Словари пространств имен
Пространство имен модулей реализовано с помощью словарей (атрибут __dict__). Точно так же реализованы классы и экземпляры - на основе словарей. Создадим my_module.py:
# my_module.py
class C1:
def func1(self):
self.a = 'text_a'
class C2(C1):
def func2(self):
self.b = "text_b"
Создадим экземпляр и посмотрим что внутри словаря пространства имен:
# example.py
from my_module import C1, C2
inst1 = C2()
inst2 = C2()
print(inst1.__dict__) # => {}
Там пустой кортеж. Теперь метод func1() и посмотрим, что теперь хранит словарь пространства имен:
inst1.func1()
print(inst1.__dict__) # => {'a': 'text_a'}
Теперь вызовем метод func2():
inst1.func2()
print(inst1.__dict__) # => {'a': 'text_a', 'b': 'text_b'}
Так же обратите внимание на второй экземпляр inst2 - он пустой. Словари пространств имен уникальны для каждого экземпляра класса.
print(inst2.__dict__) # => {}
Подъем по дереву пространства имен
Для атрибутов __class__ и __bases__ можно найти применение на практике. Например, построить дерево классов:
def class_tree(cls, ind):
print('.' * ind + cls.__name__)
for supercls in cls.__bases__:
class_tree(supercls, ind + 3)
def inst_tree(inst):
print(f'Tree of {inst}')
class_tree(inst.__class__, 3)
class A: pass
class B(A): pass
class C(B): pass
class D(C, B): pass
class E: pass
class F(C, E): pass
inst_tree(D())
Результат следующий:
...D
......C
.........B
............A
...............object
......B
.........A
............object
Рекурсивная функция class_tree() выводит имя класса с помощью атрибута __name__ и затем поднимается к его суперклассам.
Строки документации

Создадим простой модуль my_module.py с классом C1 и добавим пару строк документации:
" my_module.py "
class C1:
" Class C1 "
def func1(self):
" func1 "
self.a = 'text_a'
С помощью атрибута __doc__ мы без проблем можем вывести задокументированные объекты:
# example.py
import my_module
print(my_module.__doc__) # => my_module.py
print(my_module.C1.__doc__) # => Class C1
С помощью встроенной функции help() можно сгенерировать отформатированную документацию на модуль my_module.
# example.py
import my_module
help(my_module)
Результат выполнения функции help() ниже:
Help on module my_module:
NAME
my_module - my_module.py
CLASSES
builtins.object
C1
class C1(builtins.object)
| Class C1
|
| Methods defined here:
|
| func1(self)
| func1
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
FILE
h:\my_module.py
Строки документации имеют перед комментариями как преимущество, так и недостатки. Например, комментарии (#) - более гибкие в использовании, а строки документации доступы во время выполнения.
Используйте комментарии для документирования небольших участков кода, а строки документации для документирования функциональности целых модулей, функций, классов и методов.
Не забудьте посмотреть новый материал на Codebra по тегу Python.