Same Python syntax. Zero dynamic typing.
nakprok (นาคปรก) is a subset of Python that enforces static type annotations
everywhere. It's not a new language, but a stricter runtime for the Python you
already know.
Why the name? Nakprok is a depiction of the Buddha sheltered by the multi-headed serpent Nāga in Thai Buddhist art. Just as the Naga protected the Buddha, this project protects your code from untyped or mistyped logic.
- Explicit is Better than Implicit: If a value's type can't be seen in the code, it shouldn't be there.
- Zero Runtime Overhead: Validation happens at the AST level before execution. Once validated, it runs as standard CPython.
- Ecosystem Compatibility: Works with all existing Python libraries and tools.
- No New Syntax: Uses standard Python 3.10+ type hinting syntax.
nakprok is not a replacement for mypy. They tackle type safety from
different angles and work well together:
| mypy | nakprok | |
|---|---|---|
| When | Static analysis (separate step) | AST validation before execution |
| What | Deep type inference & checking | Enforces explicit type declarations |
| Scope | All valid Python | Strict subset (blocks lambda, untyped unpacking, etc.) |
| Result | Advisory report (can be ignored) | Hard block — code won't run |
Think of it this way:
- nakprok ensures every value is explicitly typed at the source level.
- mypy then verifies those types are semantically correct (e.g., protocol
compliance, generic constraints,
intwherefloatis expected).
You can (and should) run both:
nakprok check . # Enforces explicit typing
mypy . # Validates type correctnessnakprok currently enforces the following rules inside functions (and classes):
- All parameters must be typed:
def add(a: int, b: int)is required. - Return types are mandatory:
-> Noneor-> intmust be specified. - Exceptions:
selfandclsparameters in methods are exempt. - Variadic arguments:
*args: strand**kwargs: Anymust also have annotations.
-
Strict Local Typing: Every local variable must be declared with a type.
x: int = 10(Annotated assignment)x: intfollowed byx = 10(Declaration then assignment)
-
Re-assignment: Once a variable is typed in the current scope, it can be re-assigned without repeating the annotation.
x: int = 10 x = 20 # Valid
-
For Loops: Loop variables must be previously declared.
i: int for i in range(10): ...
-
With Statements: Context manager variables must be previously declared.
f: TextIO with open("file.txt") as f: ...
-
Match Case: Variable captures via
asinmatchpatterns must be pre-declared with a type annotation:val: int match data: case int() as val: print(val)
Star patterns (
[*rest]) follow the same rule:rest: list[int] match items: case [*rest]: print(rest)
Type-only matches without binding (
case int():) are always valid.
- Immutability: UPPERCASE variables are treated as constants and are immutable everywhere. Once assigned a value, any attempt to re-assign or shadow them will result in an error.
- Exemptions: UPPERCASE constants at the module level are exempt from strict
typing by convention (e.g.,
MAX_SIZE = 100).
- Lambdas: Blocked because Python provides no syntax for annotating their parameters or return types.
- Untyped Unpacking:
x, y = (1, 2)is blocked unlessxandywere previously declared.
The project aims to become a complete "strict mode" for Python. Future plans include:
- Module-Level Enforcement: Extend strict typing to all module-level variables (not just UPPERCASE constants).
- Type-Safe Imports: Validate that imported names are used consistently
with their types (integration with
mypyorpyrightstubs). - Strict Class Attributes: Enforce that all class attributes are declared in the class body with types.
- Decorator Validation: Ensure decorators preserve type information and are themselves strictly typed.
- Linter Integration: A dedicated VS Code / PyCharm extension to show
nakprokerrors in real-time.
For users (from PyPI):
pip install nakprokFor developers (from a cloned repo):
pip install -e .# Check and run (default)
nakprok file.py
# Check types only (file or directory)
nakprok check file.py
nakprok check src/
# Explicit run
nakprok run file.pydef factorial(n: int) -> int:
if n <= 1:
return 1
return n * factorial(n - 1)
def main() -> None:
result: int = factorial(5)
print(f"5! = {result}")
if __name__ == "__main__":
main()$ nakprok factorial.py
5! = 120We maintain a comprehensive test suite to ensure all rules are correctly enforced.
pytest tests/ -v