Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

canonical-range

A Python implementation of a canonical range (where step != 0, length >= 0, stop == start + length * step) with enhanced functionality compared to Python's built-in range.

Features

  • Canonical Representation: Always stores ranges in a consistent mathematical form ( step != 0, length >= 0, stop == start + length * step)
  • Full Sequence Protocol: Implements all Sequence methods (__getitem__, __len__, etc.)
  • Type Safety: Comprehensive type hints throughout
  • Memory Efficient: Uses __slots__ for reduced memory footprint

Installation

pip install canonical-range

Basic Usage

from canonical_range import CanonicalRange


def start_stop_step(canonical_range):
    return canonical_range.start, canonical_range.stop, canonical_range.step


# Range contains the elements [10, 13, 16, 19]
# Canonicalized to CanonicalRange(10, 22, 3)
r1 = CanonicalRange(10, 20, 3)
assert start_stop_step(r1) == (10, 22, 3)
assert r1.length == 4

# Range contains the elements [10, 13, 16, 19, 22]
# Canonicalized to CanonicalRange(10, 25, 3)
r2 = CanonicalRange(10, 23, 3)
assert start_stop_step(r2) == (10, 25, 3)
assert r2.length == 5

# Range contains the elements [20, 17, 14, 11]
# Canonicalized to CanonicalRange(20, 8, -3)
r3 = CanonicalRange(20, 10, -3)
assert start_stop_step(r3) == (20, 8, -3)
assert r3.length == 4

# Extending ranges
assert repr(CanonicalRange(1, 5, 1).extend(2)) == '(CanonicalRange(1, 7, 1), CanonicalRange(5, 7, 1))'
assert repr(
    CanonicalRange(1, 5, 1).extend(0)) == '(CanonicalRange(1, 5, 1), CanonicalRange(5, 5, 1))'  # Empty extension

# Range is empty
assert not CanonicalRange(5, 5, 1)
assert not CanonicalRange(5, 5, -1)
assert not CanonicalRange(5, 4, 1)
assert not CanonicalRange(5, 6, -1)

# Indexing and slicing
r4 = CanonicalRange(0, 10, 1)

# Empty slice
assert r4[1:1] == CanonicalRange(1, 1, 1)
assert not r4[1:1]

assert r4[1:0] == CanonicalRange(1, 1, 1)
assert not r4[1:0]

# Range contains the elements [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert r4[0] == r4[-10] == 0
assert r4[-1] == r4[9] == 9
assert all(i in r4 for i in range(10))
assert -1 not in r4
assert 10 not in r4

# Sliced range contains the elements [2, 3, 4]
assert start_stop_step(r4[2:5]) == (2, 5, 1)

# Sliced range contains the elements [2, 4]
assert start_stop_step(r4[2:5:2]) == (2, 6, 2)

# Sliced range contains the elements [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
assert start_stop_step(r4[::-1]) == start_stop_step(reversed(r4)) == (9, -1, -1)
assert 9 in r4[::-1]
assert 0 in r4[::-1]
assert -1 not in r4[::-1]

# Sliced range contains the elements [5, 3]
assert start_stop_step(r4[5:2:-2]) == (5, 1, -2)

# For ranges,
# If step > 0,
# If start < -length, it gets clamped up to 0
# If start > length, it gets clamped down to length
# If stop < -length, it gets clamped up to 0
# If stop > length, it gets clamped down to length
# We then do canonicalization on top of this
# Examples:
# range(10)[-14:-16:2] -> range(0, 0, 2)
# range(10)[-14:-12:2] -> range(0, 0, 2)
# range(10)[-14:6:4] -> range(0, 6, 4) -> range(0, 8, 4)
# range(10)[-14:28:21] -> range(0, 10, 21) -> range(0, 21, 21)
# range(10)[14:-14:2] -> range(10, 0, 2) -> range(10, 10, 2)
# range(10)[14:2:2] -> range(10, 2, 2) -> range(10, 10, 2)
# range(10)[14:12:2] -> range(10, 10, 2)
# range(10)[14:16:2] -> range(10, 10, 2)
assert start_stop_step(r4[-14:-16:2]) == (0, 0, 2)
assert start_stop_step(r4[-14:-12:2]) == (0, 0, 2)
assert start_stop_step(r4[-14:6:4]) == (0, 8, 4)
assert start_stop_step(r4[-14:28:21]) == (0, 21, 21)
assert start_stop_step(r4[14:-14:2]) == start_stop_step(r4[14:2:2]) == start_stop_step(r4[14:12:2]) == start_stop_step(r4[14:16:2]) == (10, 10, 2)

# If step < 0,
# If start < -length, it gets clamped up to -1
# If start > length - 1, it gets clamped down to length - 1
# If stop < -length, it gets clamped up to -1
# If stop > length - 1, it gets clamped down to length - 1
# We then do canonicalization on top of this
# Examples:
# range(10)[-14:-16:-2] -> range(-1, -1, -2)
# range(10)[-14:-12:-2] -> range(-1, -1, -2)
# range(10)[-14:6:-2] -> range(-1, 6, -2) -> range(-1, -1, -2)
# range(10)[-14:6:-4] -> range(-1, 6, -4) -> range(-1, -1, -4)
# range(10)[-14:28:-21] -> range(-1, 9, -21) -> range(-1, -1, -21)
# range(10)[14:-14:-2] -> range(9, -1, -2)
# range(10)[14:2:-2] -> range(9, 2, -2) -> range(9, 1, -2)
# range(10)[14:12:-2] -> range(9, 9, -2)
# range(10)[14:16:-2] -> range(9, 9, -2)
assert start_stop_step(r4[-14:-16:-2]) == start_stop_step(r4[-14:-12:-2]) == start_stop_step(r4[-14:6:-2]) == (-1, -1, -2)
assert start_stop_step(r4[-14:6:-4]) == (-1, -1, -4)
assert start_stop_step(r4[-14:28:-21]) == (-1, -1, -21)
assert start_stop_step(r4[14:-14:-2]) == (9, -1, -2)
assert start_stop_step(r4[14:2:-2]) == (9, 1, -2)
assert start_stop_step(r4[14:12:-2]) == start_stop_step(r4[14:16:-2]) == (9, 9, -2)

Contributing

Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.

License

This project is licensed under the MIT License.

Release files for canonical-range 0.1.0a2

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

Source distribution (sdist)

Source distribution for canonical-range 0.1.0a2
File Size Uploaded
canonical_range-0.1.0a2.tar.gz 5.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for canonical-range 0.1.0a2
File Interpreter ABI Platform
canonical_range-0.1.0a2-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 11.6 kB

Release files / canonical_range-0.1.0a2.tar.gz

Download URL canonical_range-0.1.0a2.tar.gz
Size 5.7 kB
Tags Source
SHA-256 checksum
How to use checksums
bbe4b3126cffc24eaaced33574a5e3d166c7d5132fe441182712f55d3a3252ca
BLAKE2b-256 checksum
How to use checksums
528a03e2e737907324163f96bf90161d29677d69456a1a0b1fbe3d1912823e23
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.2

Release files / canonical_range-0.1.0a2-py2.py3-none-any.whl

Download URL canonical_range-0.1.0a2-py2.py3-none-any.whl
Size 5.9 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
dc75b71aa8cc87ae882af0db0986939f7be583c6eba7c7a4aaa9ff69c836e234
BLAKE2b-256 checksum
How to use checksums
93404c96ee5870980eb31d301331ec64cd5858a329064180a9c1b7064c57489c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.2

Release history Release notifications | RSS feed

This release

0.1.0a2 This release

2 release files

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