В Python есть шесть предопределённых «констант»: True, False, None, __debug__, Ellipsis (или, что то же самое, ...) и NotImplemented. Однако по какой-то причине все они ведут себя немного по-разному.
True, False и None
True, False и None — это ключевые слова. Они не являются идентификаторами, а представляют собой отдельные лексические токены. Это довольно необычно: больше ничего подобного в Python нет. Обычно всё разрешается на этапе обычного поиска имён, а не прямо в лексере.
Интересное следствие этого — выражения вида x.True вызывают SyntaxError. Остаётся только гадать, чем было продиктовано такое решение (если оно вообще было осознанным).
С этими константами связаны и другие любопытные моменты, но о них — чуть позже, поскольку они пересекаются с остальными константами.
__debug__
__debug__ — булева константа: обычно она равна True, но при запуске с флагом -O становится False. Идея похожа на то, как отключается assert в «недебажных» сборках: можно обернуть код в if __debug__, если проверка была бы слишком затратной в «оптимизированной» сборке.
__debug__ интересен тем, что, хотя формально это обычный идентификатор (в отличие от True, False и None), это единственный идентификатор в языке, которому нельзя присвоить значение:
>>> __debug__ = 67 File "<stdin>", line 1 SyntaxError: cannot assign to __debug__
Присвоить значение нельзя даже как атрибуту:
>>> x.__debug__ = 67 File "<stdin>", line 1 SyntaxError: cannot assign to __debug__
Опять же, ни один другой идентификатор так себя не ведёт. Это по-настоящему особый случай.
Но поскольку это не ключевое слово, ведёт он себя чуть иначе, чем True, False и None:
x.__debug__ вызывает AttributeError (а не SyntaxError), поскольку синтаксически конструкция корректна — просто идёт обращение к несуществующему атрибуту.
Любопытно, что существует и специальное сообщение об ошибке при попытке удалить __debug__ (хотя без этого особого случая всё равно возникла бы NameError), но это не распространяется на удаление атрибута с именем __debug__:
>>> del __debug__ File "<stdin>", line 1 SyntaxError: cannot delete __debug__ >>> del x.__debug__ Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined
Если бы x было определено, возникла бы AttributeError. В любом случае это не SyntaxError (в отличие от присваивания) — почему-то.
Отступление: SyntaxError — не всегда правда
Раз уж речь зашла об ошибках: присваивание значения __debug__ — один из немногих известных случаев, когда SyntaxError возникает, хотя формально синтаксис вполне корректен. В этом легко убедиться самому:
>>> assert (__debug__ := 67)
Выполнение этого assert в debug-сборке вызовет SyntaxError, но с флагом -O assert вообще не компилируется, поэтому никакого исключения не возникнет.
Ещё два похожих случая — использование yield или await вне функции:
>>> assert (yield) >>> assert (await 67)
Ellipsis и NotImplemented
Ellipsis и NotImplemented описаны в разделе «константы» справочника, но, в отличие от остальных четырёх, «настоящими» константами не являются. Это обычные встроенные объекты, поэтому их можно затенить глобальными переменными:
>>> NotImplemented = 67 >>> NotImplemented 67
И снова возникает вопрос: почему эти два не особые, а остальные константы — особые?
Перезапись констант
Вот ещё один интересный момент: несмотря на то, что True, False и None — лексические токены, они одновременно существуют и как обычные встроенные объекты:
>>> import builtins >>> getattr(builtins, 'True') True >>> getattr(builtins, 'False') False >>> getattr(builtins, 'None') is None True
Обратиться к ним напрямую, минуя getattr, не получится.
А вот тут начинается самое интересное: setattr тоже работает!
>>> setattr(builtins, 'True', 67) >>> getattr(builtins, 'True') 67
Однако это не меняет значение при обращении через лексический токен:
>>> True True
Но __debug__ ведёт себя точно так же!
>>> setattr(builtins, '__debug__', 67) >>> builtins.__debug__ 67 >>> __debug__ True
Получается, значение __debug__ в каком-то смысле можно изменить, но, несмотря на то, что это не лексический токен, для него сделано такое же исключение, как для True, False и None: его значение не зависит от изменений модуля builtins. Так что это действительно константа!
Ellipsis и NotImplemented, повторимся, настоящими константами не являются:
>>> setattr(builtins, 'Ellipsis', 67) >>> Ellipsis 67
Но при этом значение ... не меняется:
>>> ... Ellipsis
Получается, в каком-то смысле ... — настоящая константа, а вот Ellipsis — нет. Странно, не правда ли?