Codebra
29 января 2026 в 18:23

Урок 68. Пространство имен и строки документации в Python

Познакомимся с пространством имен в Python и научимся документировать код Python.
📝

Внимание! На этой странице вы найдете материал урока из архивного курса по 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

В 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.