Если посмотреть в документацию 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()
Можно использовать __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__"
: не выполняется при импорте, что помогает избежать циклических зависимостей.