A playground fork of CPython where speculative language ideas get real implementations.
Quick start · What's new · Try it · How it works · Caveats
Note
This is not the official Python distribution. It tracks upstream
python/cpython main and layers experimental
syntax and runtime features on top. Maintained by
@grandimam — follow along on X for progress and
write-ups. The upstream README is preserved as README.rst.
⚡ Quick start
Zero install — open in Codespaces
One click gives you a cloud VS Code with the interpreter already compiled. When the terminal opens, just run:
./python -c 'print(None?.upper(), "hi"?.upper())' # None HI
Codespaces runs on your GitHub account's free quota (120 core-hours/month on personal accounts — no paid plan required). The first build takes a few minutes; the interpreter is ready when the welcome message appears.
Build from source
Takes about five minutes on macOS / Linux:
git clone https://github.com/grandimam/cpython.git cd cpython ./configure --enable-optimizations make -j"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" ./python -c 'print("hello"?.upper(), None?.upper())' # HELLO None
On macOS the built binary is
./python.exe(case-insensitive filesystem); on Linux it's./python.
Prefer a throwaway, side-by-side install so your system Python is untouched:
./configure --prefix="$HOME/.local/cpython-experimental" make -j8 && make altinstall "$HOME/.local/cpython-experimental/bin/python3.16" --version # Python 3.16.0a0+grandimam.0.1
Build prerequisites
- macOS:
xcode-select --installandbrew install openssl xz gdbm - Debian / Ubuntu:
sudo apt install build-essential pkg-config libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev libffi-dev liblzma-dev - Fedora:
sudo dnf builddep python3 - Windows:
PCbuild\build.bat -c Release(seePCbuild/readme.txt)
Full details live in the upstream Developer's Guide.
✨ What's new in this fork
?. — None-aware attribute access
Stop writing x.y if x is not None else None chains. The new ?. operator
short-circuits the entire attribute / subscript / call chain to None the
moment it hits a None:
class User: name: str | None class Response: customer: User | None # Lowercase name or None name = response?.customer?.name.lower()
| Expression | response is None |
customer is None |
both set |
|---|---|---|---|
response?.customer?.name.lower() |
None |
None |
"alice" |
response.customer.name.lower() |
AttributeError |
AttributeError |
"alice" |
Semantics at a glance
- Only
Noneshort-circuits —""?.upper()is"",0?.realis0. - Short-circuiting covers the whole chain: in
x?.f(expensive()),expensive()is never called whenx is None. - It stops at the end of the chain:
x?.a + 1still raisesTypeErrorforx = None. - Load-only:
x?.a = 1,del x?.aandx?.a += 1are syntax errors. - Fully integrated: new
QUESTIONDOTtoken,ast.NoneAwareAttributenode,ast.unparseround-trips,disshows the short-circuit, feature-version gated (ast.parse(src, feature_version=(3, 15))rejects it).
🧪 Try it
>>> import ast, dis >>> ast.dump(ast.parse("a?.b.c", mode="eval").body) "Attribute(value=NoneAwareAttribute(value=Name(id='a'), attr='b'), attr='c')" >>> def f(r): return r?.customer?.name.lower() >>> dis.dis(f) 1 RESUME 0 LOAD_FAST 0 (r) COPY 1 POP_JUMP_IF_NONE 40 (to L1) NOT_TAKEN LOAD_ATTR 0 (customer) COPY 1 POP_JUMP_IF_NONE 26 (to L1) NOT_TAKEN LOAD_ATTR 2 (name) LOAD_ATTR 4 (lower) PUSH_NULL CALL 0 L1: RETURN_VALUE
Run the feature's test-suite:
./python -m test test_grammar test_ast test_tokenize test_unparse test_syntax🔬 How it works
Each feature is implemented end-to-end, the same way a real CPython change would be:
| Layer | Files | What changed for ?. |
|---|---|---|
| Tokenizer | Grammar/Tokens, Parser/token.c |
New two-character QUESTIONDOT token |
| Grammar | Grammar/python.gram, Parser/parser.c |
primary: primary '?.' NAME; excluded from assignment targets |
| AST | Parser/Python.asdl, Python/Python-ast.c |
NoneAwareAttribute(expr value, identifier attr) |
| Validation | Python/ast.c, Python/symtable.c |
Load-only checks, scope analysis |
| Codegen | Python/codegen.c |
Chain-aware compilation with a shared POP_JUMP_IF_NONE label |
| Stdlib | Lib/_ast_unparse.py, Lib/token.py |
Unparsing and token names |
| Docs/Tests | Doc/reference/expressions.rst, Lib/test/test_*.py |
Language reference, What's New, regression tests |
Regenerate derived files after touching the grammar or ASDL:
make regen-token regen-ast regen-pegen
🗺️ Roadmap
-
?.None-aware attribute access -
?[]None-aware subscription -
??None-coalescing operator -
??=None-coalescing augmented assignment
Have an idea? Open an issue or ping @grandimam.
⚠️ Caveats
- Experimental. Syntax, AST shapes and bytecode may change or disappear without notice.
- Not for production. Code written against this fork will not run on official CPython.
- Tracks
main. Expect rebases; pin a commit if you depend on it. - Upstream policy. This fork follows CPython's AI tools policy and coding conventions so that features could, in principle, be proposed upstream.
📄 License
Copyright © 2001 Python Software Foundation. All rights reserved.
Distributed under the PSF License. See README.rst
for the full upstream copyright and license information.
Built with ❤️ by @grandimam on top of the amazing work of the CPython core team.
