added magic methods

This commit is contained in:
Dominik Krenn 2025-11-05 14:27:58 +01:00
parent 60568933e6
commit f6eee30f17
2 changed files with 62 additions and 1 deletions

View File

@ -43,6 +43,12 @@ b = StreamNumber(literal="1337")
result = mul(a, b)
print("".join(result.stream()))
# same helpers are available via Python operators
total = a + b # calls mathstream.add under the hood
ratio = total / 2 # literal coercion is automatic
with StreamNumber(literal="10") as temp:
product = temp * ratio
```
Available operations:
@ -50,6 +56,8 @@ Available operations:
- Introspection & helpers: `is_even`, `is_odd`, `free_stream`, `active_streams`, `tracked_files`
- Lifecycle control: `collect_garbage`, `set_manual_free_only`, `manual_free_only_enabled`
- Environment helpers: `clear_logs`, `StreamNumber.write_stage`, `engine.LOG_DIR`
- Python sugar: `StreamNumber` implements `+`, `-`, `*`, `/`, `%`, `**`, and their reflected counterparts. Raw `int`, `str`, or `pathlib.Path` operands are coerced automatically.
- Context manager support: `with StreamNumber(...) as sn:` ensures `.free()` is called at exit.
## How It Works
@ -73,6 +81,7 @@ Available operations:
### Common Pitfalls & Recoveries
- **Accidentally freed files** Automatic finalizers may delete staged outputs while you still hold the path elsewhere. Fix: call `set_manual_free_only(True)` at the start of long-lived workflows, or pass `delete_file=False` to `free_stream` when you need to keep the digits around manually.
- **Operator coercion surprises** Arithmetic operators turn `int`, `str`, or `Path` operands into streamed numbers. If a string happens to be a *file path* instead of a literal, the actual file will be wrapped. Fix: be explicit (`StreamNumber(literal="...")`) when in doubt.
- **Literal churn** Recreating the same `StreamNumber(literal="123")` millions of times hammers the filesystem. Fix: stash the first instance, or cache the `.path` and rely on `StreamNumber(existing_path)` in hot loops.
- **GC too aggressive** Running `collect_garbage(0)` after every operation removes recently written files. Fix: raise the threshold (e.g., `collect_garbage(1000)`) or run GC only after youve freed all references.
- **Chunk mismatch** Some editors save files with BOMs or commas. `_normalize_stream` will raise `ValueError("Non-digit characters found...")`. Fix: sanitise input files (only ASCII digits with optional leading sign).

View File

@ -2,7 +2,8 @@ import hashlib
import weakref
from collections import Counter
from pathlib import Path
from typing import Dict, Optional, Union
from typing import Dict, Optional, Union, Any
from .engine import add, sub, mul, div, mod, pow
from .utils import (
register_log_file,
@ -117,6 +118,41 @@ class StreamNumber:
def __exit__(self, exc_type, exc, tb):
self.free()
def __add__(self, other):
return add(self, _coerce_operand(other))
def __sub__(self, other):
return sub(self, _coerce_operand(other))
def __mul__(self, other):
return mul(self, _coerce_operand(other))
def __truediv__(self, other):
return div(self, _coerce_operand(other))
def __mod__(self, other):
return mod(self, _coerce_operand(other))
def __pow__(self, other):
return pow(self, _coerce_operand(other))
def __radd__(self, other):
return add(_coerce_operand(other), self)
def __rsub__(self, other):
return sub(_coerce_operand(other), self)
def __rmul__(self, other):
return mul(_coerce_operand(other), self)
def __rtruediv__(self, other):
return div(_coerce_operand(other), self)
def __rmod__(self, other):
return mod(_coerce_operand(other), self)
def __rpow__(self, other):
return pow(_coerce_operand(other), self)
_ACTIVE_COUNTER: Counter[str] = Counter()
@ -163,3 +199,19 @@ def set_manual_free_only(enabled: bool) -> None:
def manual_free_only_enabled() -> bool:
"""Return the current manual-free-only toggle."""
return _MANUAL_FREE_ONLY
def _coerce_operand(value: Any) -> StreamNumber:
"""Convert supported operand types into a StreamNumber."""
if isinstance(value, StreamNumber):
return value
if isinstance(value, (int,)):
return StreamNumber(literal=str(value))
if isinstance(value, Path):
return StreamNumber(value)
if isinstance(value, str):
candidate = Path(value)
if candidate.exists():
return StreamNumber(candidate)
return StreamNumber(literal=value)
raise TypeError(f"Unsupported operand type for StreamNumber: {type(value)!r}")