PythonIota
A zero-dependency Python toolkit of from-scratch building blocks — Go-style
iota enumerations, lazy sequence generators, and a linear-time regex engine.
pip install PythonIota
Requires Python 3.10+.
1.7.1 fixes
- Full matching now considers complete alternatives instead of rejecting a valid match after selecting a shorter branch.
- Ordered enum members now hash consistently with equal numeric values and equal members of other enums, preserving dictionary and set lookups.
What's inside
- Enums — Go-style
iota, bit flags, string enums (pythoniota.IotaEnum, ...) - Sequences — lazy, chainable generators (
pythoniota.iota) - Regex — a from-scratch NFA (Pike VM) engine, no catastrophic backtracking (
pythoniota.regex) - CLI — a from-scratch
argparse-style argument parser (pythoniota.cli)
Enums
Go-style iota
iota starts at 0 and auto-increments each time it is read. Literal assignments do not consume it.
from pythoniota import IotaEnum
class Color(IotaEnum):
Red = iota # 0
Green = iota # 1
Blue = iota # 2
Color.Green # 1
Color.names() # ['Red', 'Green', 'Blue']
Color.values() # [0, 1, 2]
list(Color) # [('Red', 0), ('Green', 1), ('Blue', 2)]
0 in Color # True (by value)
'Red' in Color # True (by name)
Color.from_value(2) # 'Blue'
iota works inside expressions — the value is the current counter:
class Perm(IotaEnum):
Read = 1 << iota # 1
Write = 1 << iota # 2
Exec = 1 << iota # 4
All = Read | Write | Exec # 7
class Size(IotaEnum):
KB = 1 << (iota + 10) # 1024
MB = 1 << (iota + 10) # 2048
GB = 1 << (iota + 10) # 4096
Skipping values & custom start/step
class Errno(IotaEnum):
EPERM = iota # 0
skip(2) # skip 1, 2
EBADF = iota # 3
class Port(IotaEnum):
_iota_start_ = 8000
_iota_step_ = 10
HTTP = iota # 8000
HTTPS = iota # 8010
_ = iota also skips a single value (Go's blank identifier).
Immutability, aliases, serialization
Color.Red = 99 # AttributeError: cannot modify enum member
Color.alias('R', 'Red') # Color.R == 0
Color.to_dict() # {'Red': 0, 'Green': 1, 'Blue': 2}
Color.to_json() # '{"Red": 0, "Green": 1, "Blue": 2}'
Color.from_json(s) # -> dict of members
Color.has('Red') # True
Color.get('Nope', -1) # -1
@unique — reject duplicate values
from pythoniota import unique
@unique
class Status(IotaEnum):
Active = iota
Inactive = iota
# raises ValueError if two members share a value
Ordered enums
Add _ordered_ = True to make members comparable by declaration order:
class Priority(IotaEnum):
_ordered_ = True
Low = iota
Medium = iota
High = iota
Priority.Low < Priority.High # True
Bit flags
from pythoniota import IotaBitFlags, FlagScope
class Access(IotaBitFlags):
Read = 1 << iota # 1
Write = 1 << iota # 2
Exec = 1 << iota # 4
flags = Access.Read | Access.Write
flags.has(Access.Read) # True
flags.has_all(Access.Read, Access.Exec) # False
flags.has_any(Access.Read, Access.Exec) # True
list(flags.decompose()) # [BitFlag(1), BitFlag(2)]
with FlagScope() as scope:
scope.grant(Access.Read, Access.Write)
scope.revoke(Access.Write)
scope.has(Access.Read) # True
String enums
Members resolve to their own name, or a custom format:
from pythoniota import IotaStringEnum
class Color(IotaStringEnum):
Red = iota # 'Red'
Blue = iota # 'Blue'
class Code(IotaStringEnum):
_format_ = "{name}_{index:03d}"
OK = iota # 'OK_000'
Error = iota # 'Error_001'
@iota_enum decorator
Turn a plain class into an enum:
from pythoniota import iota_enum
@iota_enum
class Color:
Red = 0
Green = 1
Blue = 2
@iota_enum(ordered=True)
class Priority:
Low = 0
High = 1
match / case
Enum members match by value:
match color:
case Color.Red: ...
case Color.Blue: ...
case _: ...
Sequences
iota(...) is a lazy, composable sequence. Construction mirrors range, plus an optional map:
from pythoniota import iota
iota() # 0, 1, 2, ... (infinite)
iota(5) # 0, 1, 2, 3, 4
iota(2, 10, 2) # 2, 4, 6, 8
iota(5, map=lambda i: i*i) # 0, 1, 4, 9, 16
s = iota(10)
len(s) # 10 (O(1))
s[7] # 7 (O(1) arithmetic indexing)
s[2:5] # [2, 3, 4]
list(reversed(s)) # 9, 8, ... 0 (lazy)
5 in s # True (O(1))
s.take(3) # [0, 1, 2]
Operators
iota(3) + iota(3, 6) # concat: 0,1,2,3,4,5
iota(3) * 2 # repeat: 0,1,2,0,1,2
iota(3) | iota(3, 6) # interleave: 0,3,1,4,2,5
iota(3) @ iota(3, 6) # zip: (0,3),(1,4),(2,5)
Lazy combinators
iota(10).filter(lambda x: x % 2 == 0) # 0,2,4,6,8
iota().map(lambda x: x*x) # 0,1,4,9,...
iota().takewhile(lambda x: x < 5) # 0,1,2,3,4
iota(10).dropwhile(lambda x: x < 7) # 7,8,9
iota(4).pairwise() # (0,1),(1,2),(2,3)
iota(5).window(3) # (0,1,2),(1,2,3),(2,3,4)
iota(7).chunk(3) # [0,1,2],[3,4,5],[6]
iota(6).map(lambda x: x % 3).distinct() # 0,1,2
seq.flatten() # flatten one level
iota().enumerate() .accumulate() .zip(other)
Terminal operations
Reductions on plain arithmetic sequences are O(1); infinite sequences raise where they would not terminate.
iota(1, 101).sum() # 5050 (closed-form, O(1))
iota(3, 10).min() # 3
iota(3, 10).max() # 9
iota(0, 10, 2).count() # 5
iota(5).last() # 4
iota(10).nth(3) # 3
iota().first() # 0
iota().find(lambda x: x > 100) # 101
iota(1, 5).all(lambda x: x > 0) # True
iota(5).any(lambda x: x == 3) # True
iota(3).reduce(lambda a, b: a + b) # 3
iota(3).to_list() # [0, 1, 2]
iota(3).to_tuple() # (0, 1, 2)
iota(3).to_set() # {0, 1, 2}
iota(3).to_dict(value=lambda x: x*x) # {0: 0, 1: 1, 2: 4}
Async iteration
async for x in iota(5):
...
Recipes
from pythoniota.recipes import (
fibonacci, lucas, factorial, catalan, harmonic,
triangle, powers, geometric, primes, primes_sieve,
pascal_row, collatz, repeat, cycle,
)
fibonacci(10).to_list() # 0,1,1,2,3,5,8,13,21,34
lucas(7).to_list() # 2,1,3,4,7,11,18
factorial(6).to_list() # 1,1,2,6,24,120
catalan(6).to_list() # 1,1,2,5,14,42
harmonic(4).to_list() # 1.0, 1.5, 1.833..., 2.083...
triangle(5).to_list() # 0,1,3,6,10
powers(2, 8).to_list() # 1,2,4,...,128
geometric(3, 2, 4).to_list() # 3,6,12,24
primes(5).to_list() # 2,3,5,7,11 (first n primes)
primes_sieve(20).to_list()# 2,3,5,7,11,13,17,19 (< limit, Eratosthenes)
pascal_row(4).to_list() # 1,4,6,4,1
collatz(6).to_list() # 6,3,10,5,16,8,4,2,1
repeat('x', 3).to_list() # ['x','x','x']
cycle([1,2], 5).to_list() # 1,2,1,2,1
Recipes without a count argument produce infinite sequences: fibonacci().take(20), primes().takewhile(lambda p: p < 100).
Safe expression evaluation
safe_eval evaluates arithmetic expressions over a whitelisted AST (no eval, no names/calls beyond provided variables):
from pythoniota import safe_eval
safe_eval("2 ** 10") # 1024
safe_eval("a * b + 1", {"a": 3, "b": 4}) # 13
Regex
A from-scratch regular-expression engine. Unlike Python's backtracking re, it
compiles the pattern to a tiny bytecode and runs it with a Pike VM (a
thread-list NFA simulation), so matching is linear in len(text) × len(program)
— pathological patterns like (a+)+$ finish instantly instead of hanging.
from pythoniota import regex
m = regex.match(r"(\d{4})-(\d{2})-(\d{2})", "2026-08-02")
m.groups() # ('2026', '08', '02')
regex.findall(r"\w+", "hello world") # ['hello', 'world']
regex.sub(r"\s+", "_", "a b c") # 'a_b_c'
regex.split(r"[,;]\s*", "a, b; c,d") # ['a', 'b', 'c', 'd']
named = regex.search(r"(?P<user>\w+)@(?P<host>\w+)", "user@host")
named.groupdict() # {'user': 'user', 'host': 'host'}
Supported: literals, ., * + ? and their lazy forms *? +? ??, alternation
|, groups (...) / non-capturing (?:...) / named (?P<name>...), lookahead
(?=...) / (?!...), fixed-width lookbehind (?<=...) / (?<!...), conditionals
(?(id/name)yes|no), character classes [...], counted repeats {m,n},
shorthands \d \w \s (and \D \W \S), word boundaries \b \B, anchors
^ $ \A \Z, and hex escapes \xHH / \uHHHH. Flags: IGNORECASE, MULTILINE,
DOTALL, VERBOSE (also inline (?imsx)). API mirrors re: compile, match,
fullmatch, search, findall, finditer, sub, subn, split, escape,
with Match.group / groups / span / groupdict / expand.
regex.findall(r"[a-z]+", "AbCdEf", regex.IGNORECASE) # ['AbCdEf']
regex.findall(r"\d+(?=px)", "10px 20em 30px") # ['10', '30'] (lookahead)
Backreferences (\1) are intentionally not supported: they can't be matched
by a finite automaton in linear time — that's the price of the no-backtracking
guarantee (the same trade-off Go's regexp and RE2 make).
# no ReDoS: this returns in microseconds, where re takes ~13s at length 28
regex.search(r"(a+)+$", "a" * 40 + "!") # None, instantly
CLI
A faithful subset of the standard library's argparse, built from scratch:
from pythoniota import cli
p = cli.ArgumentParser(prog="greet", description="say hello")
p.add_argument("name")
p.add_argument("-n", "--times", type=int, default=1)
p.add_argument("-v", "--verbose", action="store_true")
args = p.parse_args(["world", "--times", "3", "-v"])
args.name, args.times, args.verbose # ('world', 3, True)
Supported:
- positional & optional arguments,
nargs(N/?/*/+) type/choices/default/required- actions:
store,store_true,store_false,store_const,append,count --opt=value, clustered short flags (-abc), the--end-of-options marker- an auto-generated
-h/--help, and sub-commands viaadd_subparsers
Bad input prints a usage line and raises SystemExit(2) — the same contract as
argparse, so it drops into scripts and tests unchanged. parse_known_args
returns (namespace, extras) instead of erroring on unknown tokens.
Sub-commands
p = cli.ArgumentParser(prog="git")
sub = p.add_subparsers(dest="cmd")
add = sub.add_parser("add", help="stage files")
add.add_argument("path")
add.add_argument("-f", "--force", action="store_true")
rm = sub.add_parser("rm", help="remove files")
rm.add_argument("path")
args = p.parse_args(["add", "README.md", "-f"])
args.cmd, args.path, args.force # ('add', 'README.md', True)
License
MIT
Metadata
Release files for PythonIota 1.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pythoniota-1.7.1.tar.gz | 55.0 kB | Without provenance | Details for pythoniota-1.7.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pythoniota-1.7.1-py3-none-any.whl | Python 3 | none | any | Details for pythoniota-1.7.1-py3-none-any.whl |
Total release size: 91.2 kB
Release files / pythoniota-1.7.1.tar.gz
| Download URL | pythoniota-1.7.1.tar.gz |
|---|---|
| Size | 55.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
beef53dda46507851975703c0510cd9152db1a8a6f0fe46d386b8f2453e1f33f
|
|
BLAKE2b-256 checksum How to use checksums |
46f9a18bfa247e58013592ee376a71a62a33afa80f098824765eb46a348d234c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|
Release files / pythoniota-1.7.1-py3-none-any.whl
| Download URL | pythoniota-1.7.1-py3-none-any.whl |
|---|---|
| Size | 36.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cbadb40de93d382a02340ee44e9c27d154fbba3ad7950b26a040446c89c8c161
|
|
BLAKE2b-256 checksum How to use checksums |
a6502282e762bd92bcf5b32a1fcfbed605af018cd381b799990bfdc8ec5e2705
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.9
|
Security for PythonIota 1.7.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.