Skip to main content

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+.

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)

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 (?=...) / (?!...), character classes [...], counted repeats {m,n}, shorthands \d \w \s (and \D \W \S), word boundaries \b \B, and anchors ^ $ \A \Z. Flags: IGNORECASE, MULTILINE, DOTALL, VERBOSE (also inline (?imsx)). API mirrors re: compile, match, fullmatch, search, findall, finditer, sub, split, escape, with Match.group / groups / span / groupdict.

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

License

MIT

Metadata

Release files for PythonIota 1.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for PythonIota 1.7.0
File Size Uploaded Provenance Actions
pythoniota-1.7.0.tar.gz 45.1 kB Without provenance Details for pythoniota-1.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for PythonIota 1.7.0
File Interpreter ABI Platform Actions
pythoniota-1.7.0-py3-none-any.whl Python 3 none any Details for pythoniota-1.7.0-py3-none-any.whl

Total release size: 80.2 kB

Release files / pythoniota-1.7.0.tar.gz

Download URL pythoniota-1.7.0.tar.gz
Size 45.1 kB
Tags Source
SHA-256 checksum
How to use checksums
547caacf698f9b5732a28586a37426b0530a3e07773d354ba6c41a7f7e7014b6
BLAKE2b-256 checksum
How to use checksums
320c85150ba06ce45231ef7c422f14da76ae68cc2854e60ad7a8830237f6187c
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.0-py3-none-any.whl

Download URL pythoniota-1.7.0-py3-none-any.whl
Size 35.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f96858093bac3242542abb43036b56d365c1c02f57b898defe32b335c9e0faf9
BLAKE2b-256 checksum
How to use checksums
139f2d2329495939ff2e4171f9efee8c58ab323620d6dfb84ccd00f652395b78
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.0

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page