Сообщить об ошибке.

Использование атрибута объекта __name__ в Python

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

Магический атрибут __name__ как атрибут модуля

Наиболее известный вариант использования __name__ - атрибут модуля. Это случай, когда __name__ используется для создания "главных" main функций в Python. Другими словами, атрибут __name__ можно использовать для программного определения, выполняется ли код напрямую как скрипт или импортируется из другого модуля.

Для демонстрации поведения создадим файл pname.py со следующей строкой кода внутри:

# файл pname.py
print(__name__)

Теперь откроем командную строку и запустим скрипт:

$ python3 pname.py
# __main__

Видим, что при запуске кода как скрипта, атрибут __name__ был автоматически установлен __main__. Это важный момент, так как при импорте, атрибуту __name__ будет автоматически присвоено имя файла, в котором оно вызывается.

В качестве примера, создадим файл importer.py со строкой кода:

# файл importer.py
import pname # импортируем pname.py

Затем запустим importer.py:

$ python importer.py
# pname

Единственная функция print() находится в файле pname.py, следовательно это то место, откуда вызывается атрибут __name__. Обратите внимание, что часть кода была выполнена просто путем импорта кода из другого модуля.

Из примеров выше видим, что __name__ принимает разные значения в зависимости от того, запускается ли код непосредственно как скрипт или импортируется откуда-то еще. Это наиболее известный случай использования __name__.

def library_function():
    print("Функция библиотеки")

if __name__ == "__main__":
    # Дополнительная функциональность при запуске скрипта
    print("Демонстрация работы библиотеки")
    library_function()

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

Магический атрибут __name__ как атрибут типа объекта

Есть еще один распространенный шаблон использования атрибута __name__. Этот шаблон имеет отношения к атрибутам типа объекта. Как известно, все объекты Python имеют тип, который сообщает, "что" представляет собой этот объект.

>>> type(0.5)
# <class 'float'>
>>> type("hello") 
# <class 'str'>
>>> type(sum)   
# <class 'builtin_function_or_method'>

Имея эту информацию, как можно реализовать функцию get_type_name(), которая возвращает только строку с именем типа, без <class>?

Наверное как-то так:

>>> def get_type_name(obj):
...     return str(type(obj)).split("'")[1]

>>> get_type_name("hello")   
# 'str'
>>> get_type_name(sum)     
# 'builtin_function_or_method'

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

Улучшенная функция get_type_name():

>>> def get_type_name(obj):
...     return type(obj).__name__

>>> get_type_name("hello")
# 'str'
>>> get_type_name(sum)
# 'builtin_function_or_method'

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

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

Атрибут __name__ хранит в себе имя объекта из которого вызывается, например:

# имя встроенной функции `sum()`
>>> sum.__name__
# 'sum'

# имя созданной функции
>>> get_type_name.__name__
# 'get_type_name'

Такое поведение может быть актуально, если нужно получить доступ к функции программным способом и нужно выяснить, что это за функция:

>>> import random
>>> fn = random.choice([sum, get_type_name])
>>> fn.__name__
# 'sum'

Аналогично можно получить доступ к именам классов

>>> class A():
...     pass

>>> A
# <class '__main__.A'>
>>> A.__name__
# 'A'
>>> a = A()
>>> a
>>> type(a)
# <class '__main__.A'>
>>> type(a).__name__
# 'A'

Примеры использования атрибута __name__ в кодовой базе

Определение интерфейса командной строки для кода модуля

Если посмотреть исходный код встроенного модуля calendar, то увидим, что реализация модуля заканчивается следующими двумя строками:

# From Lib/calendar.py in Python 3.12.9
if __name__ == "__main__":
    main(sys.argv)

Другими словами, модуль calendar определяет ряд функций, которые можно импортировать, но если модуль запускается из консоли, то она вызовет функцию main(), передав ей любые аргументы, полученные из терминала.

В качестве примера, выполним в командной строке следующее:

$ python3 -m calendar 2025 6
#      June 2025
# Mo Tu We Th Fr Sa Su
#                    1
#  2  3  4  5  6  7  8
#  9 10 11 12 13 14 15
# 16 17 18 19 20 21 22
# 23 24 25 26 27 28 29
# 30

Проверка аргументов

Проверку передаваемых аргументов можно найти в исходном коде встроенного модуля fractions.

>>> import fractions
# передадим строку "3" вместо числа
>>> fractions.Fraction.from_decimal("3") 
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "/usr/lib/python3.12/fractions.py", line 312, in from_decimal
#     raise TypeError(
# TypeError: Fraction.from_decimal() only takes Decimals, not '3' (str)

Вот кусок исходного кода, где функция fractions.from_decimal() выполняет проверку типа передаваемых аргументов:

class Fraction(numbers.Rational):
    # ...

    @classmethod
    def from_decimal(cls, dec):
        """Converts a finite Decimal instance to a rational number, exactly."""
        from decimal import Decimal
        if isinstance(dec, numbers.Integral):
            dec = Decimal(int(dec))
        elif not isinstance(dec, Decimal):
            raise TypeError(
                "%s.from_decimal() only takes Decimals, not %r (%s)" %
                (cls.__name__, dec, type(dec).__name__))
        return cls(*dec.as_integer_ratio())

Обратите внимание, как функция принимает аргумент dec и пытается преобразовать его Decimal. Передаваемая строка "3" не является numbers.Integral и не является Decimal, поэтому dec не проходит тесты, и следовательно получаем ошибку:

raise TypeError(
    "%s.from_decimal() only takes Decimals, not %r (%s)" %
    (cls.__name__, dec, type(dec).__name__))

Обратите внимание, что здесь атрибут __name__ используется дважды. Первое, берется объект класса cls и опрашивается его имя. Второе, это сообщение, какой тип на самом деле передал пользователь. То есть выясняется тип аргумента dec и затем опрашивается его __name__, отсюда type(dec).__name__.

Красивый вывод ошибок

Создадим перечисление enum:

>>> import enum
>>> class Colour(enum.Enum):
...   RED = "RED"
...   GREEN = "GREEN"
...   BLUE = "BLUE"

Что будет, если удалить один из цветов, например, GREEN?

>>> del Colour.GREEN
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "/usr/lib/python3.12/enum.py", line 789, in __delattr__
#     raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr))
# AttributeError: 'Colour' cannot delete member 'GREEN'.

Нельзя удалить элемент из перечисления, т.к. атрибут GREEN не похож на атрибут простого класса, а является неотъемлемой частью структуры перечисления. Но в данный момент интересует как было получено красивое сообщение "AttributeError: 'Colour' cannot delete member 'GREEN'.". Что бы понять это, даже не нужно смотреть исходный код, достаточно посмотреть на вывод самой ошибки и оператор raise

raise AttributeError("%s: cannot delete Enum member." % cls.__name__)

Логирование и отладка

Можно использовать __name__ для настройки логирования в зависимости от контекста выполнения.

import logging

if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)
else:
    logging.basicConfig(level=logging.INFO)

logging.debug("Это отладочное сообщение")
logging.info("Это информационное сообщение")
  • Уровень логирования можно настроить в зависимости от того, запущен ли код как скрипт или импортирован как модуль.
  • Упрощает отладку.

Модульное тестирование

Можно использовать __name__ для запуска модульных тестов только при выполнении скрипта.

import unittest

class TestMathOperations(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)

if __name__ == "__main__":
    unittest.main()
  • Тесты выполняются только при запуске скрипта, а не при импорте модуля.
  • Удобно для интеграции с CI/CD (Continuous Integration/Continuous Deployment).

Конфигурация модуля

Можно использовать __name__ для настройки модуля в зависимости от контекста выполнения.

if __name__ == "__main__":
    DEBUG = True
else:
    DEBUG = False

def log(message):
    if DEBUG:
        print(f"[DEBUG] {message}")

log("Это сообщение будет выведено только в режиме отладки")
  • Позволяет включать или отключать отладочную информацию в зависимости от контекста.
  • Упрощает отладку без изменения основного кода.

Избежание циклических импортов

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

# module_a.py
import module_b

def function_a():
    print("Функция A")

if __name__ == "__main__":
    function_a()
# module_b.py
import module_a

def function_b():
    print("Функция B")

if __name__ == "__main__":
    function_b()
  • Код внутри if __name__ == "__main__": не выполняется при импорте, что помогает избежать циклических зависимостей.