<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x" -->

---
title: python&#x27;s pre-declared constants are kinda weird | daily.dev
description: An exploration of Python&#x27;s six pre-declared constants (True, False, None, __debug__, Ellipsis, NotImplemented) reveals inconsistent behavior under the hood....
canonical: https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: python&#x27;s pre-declared constants are kinda weird | daily.dev
og:description: An exploration of Python&#x27;s six pre-declared constants (True, False, None, __debug__, Ellipsis, NotImplemented) reveals inconsistent behavior under the hood....
og:url: https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x
og:image: https://api.daily.dev/og/posts/xRpbLGg4x.png
og:image:alt: python&#x27;s pre-declared constants are kinda weird
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# python's pre-declared constants are kinda weird

**[Lobsters](https://daily.dev/sources/lobsters)** · 4 min read · 0 upvotes · 0 comments

## Summary

An exploration of Python's six pre-declared constants (True, False, None, __debug__, Ellipsis, NotImplemented) reveals inconsistent behavior under the hood. True, False, and None are lexical keywords resolved by the lexer, not name resolution, so accessing them as attributes raises SyntaxError. __debug__ is a normal identifier yet uniquely cannot be assigned or deleted, and its value stays fixed even if the builtins module's __debug__ attribute is overwritten via setattr - meaning it behaves like a true constant despite not being a keyword. Ellipsis and NotImplemented, despite appearing in the constants section of the docs, are ordinary builtins that can be shadowed by reassigning globals or via setattr on the builtins module, though the literal ... syntax still always resolves to the original Ellipsis object. The piece walks through interactive examples demonstrating these quirks, including cases where Python raises SyntaxError for things that aren't strictly invalid syntax, such as assigning to __debug__ or using yield/await outside a function.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://sebsite.pw/w/20260801-pythonconstants.html>

## Questions this post answers

### why can't I assign a value to __debug__ in Python?

__debug__ is the only identifier in Python that cannot be assigned to; attempting `__debug__ = 67` raises a SyntaxError rather than working normally or raising a NameError. This is a hardcoded special case in the language, distinct from True, False, and None, which are actual lexical keyword tokens rather than identifiers at all.

_daily.dev surfaces quirks like this for developers digging into how Python's parser really works._

### can Ellipsis and NotImplemented be overwritten as global variables in Python?

Yes, unlike True, False, and None, Ellipsis and NotImplemented are ordinary builtins rather than protected constants, so assigning `NotImplemented = 67` or using setattr on the builtins module successfully changes their value when referenced by name. However, the literal `...` token still always evaluates to the original Ellipsis object regardless of such reassignment.

_track subtle language behaviors like this on daily.dev before they surprise you in production code._

### does setattr on the builtins module change the value of True or False in Python?

No. Calling setattr(builtins, 'True', 67) changes what getattr(builtins, 'True') returns, but the lexical token True still evaluates to the original boolean value, because True, False, None, and __debug__ are special-cased so their lexical access is unaffected by changes to the builtins module, unlike Ellipsis and NotImplemented.

_daily.dev helps developers who want to understand these edge cases before they trust builtin overrides._

## Community take

How the wider developer community reacted, aggregated from 2 discussions and 13 comments across lobsters, hackernews (as of 2026-09-14).

**TL;DR:** Discussion centers on explaining the historical reasons behind Python's constant quirks—mostly technical clarification and history rather than praise or criticism of the post itself.

**Sentiment:** 10% positive · 75% mixed · 15% skeptical

**The pushback**

- One commenter dismissed the piece as low-effort ('slop language').

**By community**

- lobsters (mixed): Mostly a deep technical thread digging into CPython history and bytecode behavior behind the quirks, with a mix of clarifications, corrections, and one dismissive remark.
- hackernews (mixed): No comment content was provided, so no discernible take could be formed.

**Hottest debate:** Whether booleans are 'really' integers under the hood versus just behaving like integers due to defined arithmetic methods.

**Highlights**

> `builtins.True` and friends are a fossil of pre-3.0 history. `True` and `False` started out as ordinary, reassignable builtins under [PEP 285], landing first in 2.2.1 as a bugfix-release backport and then [formalized properly in 2.3][0]. [`None` got its reassignment blocked in 2.4.][1] `True` and `False` stayed assignable through the rest of the 2.x line and only became real keywords in 3.0. The builtins slot is just what's left of that old namespace, kept around mostly so `getattr`/`dir`/`vars` introspection still works on them. It also explains why `setattr(builtins, 'True', 67)` doesn't touch the literal: the keyword compiles to a constant baked into the bytecode at compile time, separate from whatever's sitting in the builtins namespace. [PEP 285]: https://peps.python.org/pep-0285/ [0]: https://docs.python.org/2.3/whatsnew/section-bool.html [1]: https://docs.python.org/2.4/whatsnew/node12.html#SECTION0001200000000000000000
> — [hongminhee on lobsters · 2 points, 1 comments](https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda#c_qkdwoi)

> > an interesting side effect of this is that expressions like x.True raise a SyntaxError. i'm curious as to what the rationale was for this decision (if there was one). A lot of things in Python can be explained by just how the language "design" works. The interpreter at one point did not have bools, they were just aliases to 1 and 0 which is why they are still integers today. However at one point someone on the way to Python 3 benchmarked the thing and realized that issuing `LOAD_GLOBAL` for these common constants is wasteful and they were reserved and compiled into bytecode instructions. `Ellipsis` gained a whole token in Python 3 which is `...`. Previously that was only possible in certain subscript positions. So it's similar, just not by its name. `NotImplemented` is rare enough that there is really no point in making it a keyword. It's just a marker constant for comparisons.
> — [mitsuhiko on lobsters · 1 points, 1 comments](https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda#c_dy7onl)

> I quibble: that doesn’t require that booleans *be* integers; a suitable `__add__` method will do. And you probably want a `def __int__(self): return 1 if self is True else 0` so that `int(True)` works. And before long you’ll have defined all other numerical methods, since this isn’t JavaScript where `(true + true) ** (true + true + true) == 8` without booleans being integers because it’s eager to convert types. And… once you’ve defined `__add__`, `__mul__`, `__div__`, *&c*… well, duck typing doctrine will say it’s an integer after all. Ah well.
> — [chrismorgan on lobsters · 3 points](https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda#c_kbr74b)

> Besides all the observable behaviour, like addition being defined or `1 / False` raising ZeroDivisionError`. You can look at the code to see that boolean's are defined as 0 and 1 here: https://github.com/python/cpython/blob/9f8780022058e629dada82221c384b67c5687b2e/Objects/boolobject.c#L215-L226 You can also see that the the base type (tp_base) of PyBool_Type is defined as long (PyLong_Type)
> — [PuercoPop on lobsters · 1 points](https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda#c_y1izcq)

**Source threads**

- [lobsters](https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda) · 66 points · 13 comments
- [hackernews](https://news.ycombinator.com/item?id=49250370) · 1 points · 0 comments

## Similar posts on daily.dev

- [Python Constants: Improve Your Code's Maintainability Quiz – Real Python](https://daily.dev/posts/python-constants-improve-your-code-s-maintainability-quiz-real-python-cbbc1blnd) · Real Python · 1 upvotes · 0 comments
- [Can I create a constant in Python? No, but I can get close.](https://daily.dev/posts/can-i-create-a-constant-in-python-no-but-i-can-get-close--o5uxiiwk6) · Medium · 0 upvotes · 0 comments
- [python string literals are kinda funny](https://daily.dev/posts/python-string-literals-are-kinda-funny-yqzriyck0) · Lobsters · 0 upvotes · 0 comments
- [PyCoder’s Weekly](https://daily.dev/posts/pycoder-s-weekly-fhxycryw1) · Planet Python · 0 upvotes · 0 comments

---

Tags: [#python](https://daily.dev/tags/python), [#cpython](https://daily.dev/tags/cpython)

[View this post on daily.dev](https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"python's pre-declared constants are kinda weird","url":"https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x"},"datePublished":"2026-08-13T08:53:12.243Z","dateModified":"2026-09-14T06:42:35.506Z","description":"An exploration of Python's six pre-declared constants (True, False, None, __debug__, Ellipsis, NotImplemented) reveals inconsistent behavior under the hood....","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/c0dcbee94b601fa988a6a37bae129cea?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/c0dcbee94b601fa988a6a37bae129cea?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Lobsters","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Lobsters","logo":"https://media.daily.dev/image/upload/s--tl8v_Fku--/f_auto,t_logo/v1698841318/logos/lobste.jpg","url":"https://daily.dev/sources/lobsters"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"python,cpython","timeRequired":"PT4M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Lobsters","item":"https://daily.dev/sources/lobsters"},{"@type":"ListItem","position":3,"name":"python's pre-declared constants are kinda weird"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/python-s-pre-declared-constants-are-kinda-weird-xrpblgg4x#faq","mainEntity":[{"@type":"Question","name":"why can't I assign a value to __debug__ in Python?","acceptedAnswer":{"@type":"Answer","text":"__debug__ is the only identifier in Python that cannot be assigned to; attempting `__debug__ = 67` raises a SyntaxError rather than working normally or raising a NameError. This is a hardcoded special case in the language, distinct from True, False, and None, which are actual lexical keyword tokens rather than identifiers at all. daily.dev surfaces quirks like this for developers digging into how Python's parser really works."}},{"@type":"Question","name":"can Ellipsis and NotImplemented be overwritten as global variables in Python?","acceptedAnswer":{"@type":"Answer","text":"Yes, unlike True, False, and None, Ellipsis and NotImplemented are ordinary builtins rather than protected constants, so assigning `NotImplemented = 67` or using setattr on the builtins module successfully changes their value when referenced by name. However, the literal `...` token still always evaluates to the original Ellipsis object regardless of such reassignment. track subtle language behaviors like this on daily.dev before they surprise you in production code."}},{"@type":"Question","name":"does setattr on the builtins module change the value of True or False in Python?","acceptedAnswer":{"@type":"Answer","text":"No. Calling setattr(builtins, 'True', 67) changes what getattr(builtins, 'True') returns, but the lexical token True still evaluates to the original boolean value, because True, False, None, and __debug__ are special-cased so their lexical access is unaffected by changes to the builtins module, unlike Ellipsis and NotImplemented. daily.dev helps developers who want to understand these edge cases before they trust builtin overrides."}}]}
```

