sain
sain is a set of minimal abstraction that brings Rust's ecosystem to Python. It offers a few of the core Rust types like Vec<T>
and Result<T, E>
and more.
1# BSD 3-Clause License 2# 3# Copyright (c) 2022-Present, nxtlo 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, this 10# list of conditions and the following disclaimer. 11# 12# * Redistributions in binary form must reproduce the above copyright notice, 13# this list of conditions and the following disclaimer in the documentation 14# and/or other materials provided with the distribution. 15# 16# * Neither the name of the copyright holder nor the names of its 17# contributors may be used to endorse or promote products derived from 18# this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31"""sain is a set of minimal abstraction that brings Rust's ecosystem to Python. It offers a few of the core Rust types like `Vec<T>` and `Result<T, E>` and more.""" 32 33from __future__ import annotations 34 35__all__ = ( 36 # cfg.py 37 "cfg", 38 "cfg_attr", 39 # default.py 40 "default", 41 "Default", 42 # option.py 43 "option", 44 "Some", 45 "Option", 46 "NOTHING", 47 # iter.py 48 "Iter", 49 "Iterator", 50 "iter", 51 # macros.py 52 "macros", 53 "todo", 54 "deprecated", 55 "unimplemented", 56 "doc", 57 # futures.py 58 "futures", 59 # result.py 60 "result", 61 "Ok", 62 "Err", 63 "Result", 64 # collections 65 "collections", 66 "vec", 67 "Vec", 68 # error.py 69 "error", 70 "Error", 71 # boxed.py 72 "boxed", 73 "Box", 74 # sync 75 "sync", 76 "MaybeUninit", 77 # convert 78 "convert", 79 "From", 80 "TryFrom", 81 "Into", 82 "TryInto", 83) 84 85from . import boxed 86from . import collections 87from . import convert 88from . import default 89from . import error 90from . import futures 91from . import iter 92from . import macros 93from . import option 94from . import result 95from . import sync 96from .boxed import Box 97from .cfg import cfg 98from .cfg import cfg_attr 99from .collections import Vec 100from .collections import vec 101from .convert import From 102from .convert import Into 103from .convert import TryFrom 104from .convert import TryInto 105from .default import Default 106from .error import Error 107from .iter import Iter 108from .iter import Iterator 109from .macros import deprecated 110from .macros import doc 111from .macros import todo 112from .macros import unimplemented 113from .maybe_uninit import MaybeUninit 114from .option import NOTHING 115from .option import Option 116from .option import Some 117from .result import Err 118from .result import Ok 119from .result import Result 120 121__version__: str = "1.1.0" 122__url__: str = "https://github.com/nxtlo/sain" 123__author__: str = "nxtlo" 124__about__: str = ( 125 "Sain is a dependency-free library that implements some of the Rust core" 126 "types which abstracts over common patterns in Rust for Python." 127) 128__license__: str = "BSD 3-Clause License"
172def cfg( 173 target_os: System | None = None, 174 python_version: tuple[int, int, int] | None = None, 175 target_arch: Arch | None = None, 176 impl: Python | None = None, 177) -> bool: 178 """A function that will run the code only if all predicate attributes returns `True`. 179 180 The difference between this function and `cfg_attr` is that this function will not raise an exception. 181 Instead it will return `False` if any of the attributes fails. 182 183 Example 184 ------- 185 ```py 186 import sain 187 188 if cfg(target_os="windows"): 189 print("Windows") 190 elif cfg(target_os="linux", target_arch="arm64"): 191 print("Linux") 192 else: 193 print("Something else") 194 ``` 195 196 Parameters 197 ---------- 198 target_os : `str | None` 199 The targeted operating system thats required for the object to be ran. 200 python_version : `tuple[int, int, int] | None` 201 The targeted Python version thats required for the object to be ran. Format must be `(3, 9, 5)`` 202 target_arch : `str | None` 203 The CPU targeted architecture thats required for the object to be ran. 204 impl : `str | None` 205 The Python implementation thats required for the object to be ran. 206 207 Returns 208 ------- 209 `bool` 210 The condition that was checked. 211 """ 212 checker = _AttrCheck( 213 lambda: None, 214 no_raise=True, 215 target_os=target_os, 216 python_version=python_version, 217 target_arch=target_arch, 218 impl=impl, 219 ) 220 return checker.internal_check()
A function that will run the code only if all predicate attributes returns True
.
The difference between this function and cfg_attr
is that this function will not raise an exception.
Instead it will return False
if any of the attributes fails.
Example
import sain
if cfg(target_os="windows"):
print("Windows")
elif cfg(target_os="linux", target_arch="arm64"):
print("Linux")
else:
print("Something else")
Parameters
- target_os (
str | None
): The targeted operating system thats required for the object to be ran. - python_version (
tuple[int, int, int] | None
): The targeted Python version thats required for the object to be ran. Format must be `(3, 9, 5)`` - target_arch (
str | None
): The CPU targeted architecture thats required for the object to be ran. - impl (
str | None
): The Python implementation thats required for the object to be ran.
Returns
bool
: The condition that was checked.
99def cfg_attr( 100 *, 101 target_os: System | None = None, 102 python_version: tuple[int, int, int] | None = None, 103 target_arch: Arch | None = None, 104 impl: Python | None = None, 105) -> collections.Callable[[F], F]: 106 """Conditional runtime object configuration based on passed arguments. 107 108 If the decorated object gets called and one of the attributes returns `False`, 109 `RuntimeError` will be raised and the object will not run. 110 111 Example 112 ------- 113 ```py 114 import sain 115 116 @cfg_attr(target_os="windows") 117 def windows_only(): 118 # Do stuff with Windows's API. 119 ... 120 121 # Mut be PyPy Python implementation or `RuntimeError` will be raised 122 # when creating the instance. 123 @cfg_attr(impl="PyPy") 124 class Zoo: 125 @sain.cfg_attr(target_os="linux") 126 def bark(self) -> None: 127 windows_only() # RuntimeError("Windows OS only!) 128 129 # An instance will not be created if raised. 130 zoo = Zoo() 131 # RuntimeError("class Zoo requires PyPy implementation") 132 zoo.bark() 133 # Whats zoo?? 134 ``` 135 136 Parameters 137 ---------- 138 target_os : `str | None` 139 The targeted operating system thats required for the object. 140 python_version : `tuple[int, int, int] | None` 141 The targeted Python version thats required for the object. Format must be `(3, 9, 5)`. 142 target_arch : `str | None` 143 The CPU targeted architecture thats required for the object. 144 impl : `str | None` 145 The Python implementation thats required for the object. 146 147 Raises 148 ------ 149 `RuntimeError` 150 This fails if any of the attributes returns `False`. 151 `ValueError` 152 If the passed Python implementation is unknown. 153 """ 154 155 def decorator(callback: F) -> F: 156 @functools.wraps(callback) 157 def wrapper(*args: typing.Any, **kwargs: typing.Any) -> F: 158 checker = _AttrCheck( 159 callback, 160 target_os=target_os, 161 python_version=python_version, 162 target_arch=target_arch, 163 impl=impl, 164 ) 165 return checker(*args, **kwargs) 166 167 return typing.cast(F, wrapper) 168 169 return decorator
Conditional runtime object configuration based on passed arguments.
If the decorated object gets called and one of the attributes returns False
,
RuntimeError
will be raised and the object will not run.
Example
import sain
@cfg_attr(target_os="windows")
def windows_only():
# Do stuff with Windows's API.
...
# Mut be PyPy Python implementation or `RuntimeError` will be raised
# when creating the instance.
@cfg_attr(impl="PyPy")
class Zoo:
@sain.cfg_attr(target_os="linux")
def bark(self) -> None:
windows_only() # RuntimeError("Windows OS only!)
# An instance will not be created if raised.
zoo = Zoo()
# RuntimeError("class Zoo requires PyPy implementation")
zoo.bark()
# Whats zoo??
Parameters
- target_os (
str | None
): The targeted operating system thats required for the object. - python_version (
tuple[int, int, int] | None
): The targeted Python version thats required for the object. Format must be(3, 9, 5)
. - target_arch (
str | None
): The CPU targeted architecture thats required for the object. - impl (
str | None
): The Python implementation thats required for the object.
Raises
RuntimeError
: This fails if any of the attributes returnsFalse
.ValueError
: If the passed Python implementation is unknown.
57@typing.runtime_checkable 58class Default(typing.Protocol[_T_co]): 59 """An interface for an object that has a default value. 60 61 Example 62 ------- 63 ```py 64 from sain import Default 65 66 class Cache(Default[dict[str, Any]]): 67 68 @staticmethod 69 def default() -> dict[str, Any]: 70 return {} 71 72 cache = Cache.default() 73 print(cache) 74 assert isinstance(cache, Default) 75 # {} 76 ``` 77 """ 78 79 __slots__ = () 80 81 @staticmethod 82 def default() -> _T_co: 83 """Return the default value of the object.""" 84 raise NotImplementedError
An interface for an object that has a default value.
Example
from sain import Default
class Cache(Default[dict[str, Any]]):
@staticmethod
def default() -> dict[str, Any]:
return {}
cache = Cache.default()
print(cache)
assert isinstance(cache, Default)
# {}
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
55@typing.final 56class Some(typing.Generic[T], _default.Default["Option[None]"]): 57 """The `Option` type. An object that might be `T` or `None`. 58 59 It is a drop-in replacement for `typing.Optional[T]`, But has proper methods to handle the contained value. 60 61 Example 62 ------- 63 ```py 64 value = Some("Hello") 65 print(value) 66 # Some("Hello") 67 68 # This will unwrap the contained value as long as 69 # it is not `None` otherwise this will raise an error. 70 print(value.unwrap()) 71 # "Hello" 72 73 none_value = Some(None) 74 while none_value.unwrap(): 75 # Never unreachable! 76 77 # Solving it with `unwrap_or` method to unwrap the value or return a default value. 78 print(none_value.unwrap_or(10)) 79 # 10 80 ``` 81 """ 82 83 __slots__ = ("_value", "__default") 84 __match_args__ = ("_value",) 85 86 def __init__(self, value: T | None, /) -> None: 87 self._value = value 88 89 @staticmethod 90 def default() -> Option[None]: 91 """Default value for `Some`. Returns `None` wrapped in `Some`. 92 93 Example 94 ------- 95 ```py 96 assert Some.default() == NOTHING 97 ``` 98 """ 99 return NOTHING 100 101 # *- Reading the value -* 102 103 def into_inner(self) -> T | None: 104 """Consume `Self`, returning the wrapped value as `T | None`. 105 106 Examples 107 -------- 108 ```py 109 opt = Some('char') 110 x = opt.into_inner() 111 assert x is not None 112 113 opt = Some(None) 114 assert opt.into_inner() is None 115 ``` 116 """ 117 return self._value 118 119 def unwrap(self) -> T: 120 """Unwrap the inner value either returning if its not `None` or raising a `RuntimeError`. 121 122 It's usually not recommended to use this method in production code, since it raises. 123 124 Example 125 ------- 126 ```py 127 value = Some(5) 128 print(value.unwrap()) 129 # 5 130 131 value = Some(None) 132 print(value.unwrap()) 133 # RuntimeError 134 ``` 135 136 Raises 137 ------ 138 `RuntimeError` 139 If the inner value is `None`. 140 """ 141 if self._value is None: 142 raise RuntimeError( 143 f"Called `Option.unwrap()` on {type(self._value).__name__}." 144 ) from None 145 146 return self._value 147 148 def unwrap_or(self, default: T, /) -> T: 149 """Unwrap the inner value either returning if its not `None` or returning `default`. 150 151 Example 152 ------- 153 ```py 154 value = Some(5) 155 print(value.unwrap_or(10)) 156 # 5 157 158 # Type hint is required here. 159 value: Option[int] = Some(None) 160 print(value.unwrap_or(10)) 161 # 10 162 ``` 163 """ 164 if self._value is None: 165 return default 166 167 return self._value 168 169 def unwrap_or_else(self, f: FnOnce[T], /) -> T: 170 """Unwrap the inner value either returning if its not `None` or calling `f` to get a default value. 171 172 Example 173 ------- 174 ```py 175 value = Some(5) 176 print(value.unwrap_or_else(lambda: 10)) 177 # 5 178 179 value: Option[bool] = Some(None) 180 print(value.unwrap_or_else(lambda: True)) 181 # True 182 ``` 183 """ 184 if self._value is None: 185 return f() 186 187 return self._value 188 189 @macros.unsafe 190 def unwrap_unchecked(self) -> T: 191 """Returns the contained Some value without checking that the value is not None. 192 193 Example 194 ------- 195 ```py 196 v: Option[float] = Some(1.2) 197 v.unwrap_unchecked() # 1.2 198 199 v: Option[float] = Some(None) 200 print(v.unwrap_unchecked()) # Undefined Behavior 201 ``` 202 """ 203 #! SAFETY: The caller guarantees that the value is not None. 204 return self._value # type: ignore 205 206 def expect(self, message: str, /) -> T: 207 """Returns the contained value if it is not `None` otherwise raises a `RuntimeError`. 208 209 Example 210 ------- 211 ```py 212 value = Some("Hello") 213 214 print(value.expect("Value is None")) 215 # "Hello" 216 217 value: Option[str] = Some(None) 218 print(value.expect("Value is None")) 219 # RuntimeError("Value is None") 220 ``` 221 """ 222 if self._value is None: 223 raise RuntimeError(message) 224 225 return self._value 226 227 # *- Functional operations -* 228 def map(self, f: Fn[T, U], /) -> Some[U]: 229 """Map the inner value to another type. Returning `Some(None)` if `T` is `None`. 230 231 Example 232 ------- 233 ```py 234 value = Some(5.0) 235 236 print(value.map(lambda x: x * 2.0)) 237 # Some(10.0) 238 239 value: Option[bool] = Some(None) 240 print(value) 241 # Some(None) 242 ``` 243 """ 244 if self._value is None: 245 return nothing_unchecked() 246 247 return Some(f(self._value)) 248 249 def map_or(self, default: U, f: Fn[T, U], /) -> U: 250 """Map the inner value to another type or return `default` if its `None`. 251 252 Example 253 ------- 254 ```py 255 value: Option[float] = Some(5.0) 256 257 # map to int. 258 print(value.map_or(0, int)) 259 # 6 260 261 value: Option[float] = Some(None) 262 print(value.map_or(0, int) 263 # 0 264 ``` 265 """ 266 if self._value is None: 267 return default 268 269 return f(self._value) 270 271 def map_or_else(self, default: FnOnce[U], f: Fn[T, U], /) -> U: 272 """Map the inner value to another type, or return `default()` if its `None`. 273 274 Example 275 ------- 276 ```py 277 def default() -> int: 278 return sys.getsizeof(object()) 279 280 value: Option[float] = Some(5.0) 281 282 # map to int. 283 print(value.map_or_else(default, int)) 284 # 6 285 286 value: Option[float] = Some(None) 287 print(value.map_or_else(default, int) 288 # 28 <- size of object() 289 ``` 290 """ 291 if self._value is None: 292 return default() 293 294 return f(self._value) 295 296 def filter(self, predicate: Fn[T, bool]) -> Some[T]: 297 """Returns `Some(None)` if the contained value is `None`, 298 299 otherwise calls the predicate and returns `Some(T)` if the predicate returns `True`. 300 301 Example 302 ------- 303 ```py 304 value = Some([1, 2, 3]) 305 306 print(value.filter(lambda x: 1 in x)) 307 # Some([1, 2, 3]) 308 309 value: Option[int] = Some([1, 2, 3]) # or Some(None) 310 print(value.filter(lambda x: 1 not in x)) 311 # None 312 ``` 313 """ 314 if (value := self._value) is not None: 315 if predicate(value): 316 return Some(value) 317 318 return nothing_unchecked() 319 320 # *- Inner operations *- 321 322 def take(self) -> Option[T]: 323 """Take the value from `Self`, Setting it to `None`. 324 325 Example 326 ------- 327 ```py 328 original = Some("Hi") 329 new = original.take() 330 331 print(original, new) 332 # None, Some("Hi") 333 ``` 334 """ 335 if self._value is None: 336 return nothing_unchecked() 337 338 val = self._value 339 self._value = None 340 return Some(val) 341 342 def take_if(self, predicate: collections.Callable[[T], bool]) -> Option[T]: 343 """Take the value from `Self`, Setting it to `None` only if predicate returns `True`. 344 345 Example 346 ------- 347 ```py 348 def validate(email: str) -> bool: 349 # you can obviously validate this better. 350 return email.find('@') == 1 351 352 original = Some("flex@gg.com") 353 valid = original.take_if(validate) 354 assert is_allowed.is_some() and original.is_none() 355 356 original = Some("mail.example.com") 357 invalid = original.take_if(validate) 358 assert invalid.is_none() and original.is_some() 359 ``` 360 """ 361 if self.map_or(False, predicate): 362 return self.take() 363 364 return nothing_unchecked() 365 366 def replace(self, value: T) -> Some[T]: 367 """Replace the contained value with another value. 368 369 Example 370 ------- 371 ```py 372 value: Option[str] = Some(None) 373 value = value.replace("Hello") 374 # Some("Hello") 375 ``` 376 """ 377 self._value = value 378 return self 379 380 def and_ok(self, optb: Some[T]) -> Some[T]: 381 """Returns `Some(None)` if either contained value is `None`, 382 383 Otherwise return `optb`. 384 385 Example 386 ------- 387 ```py 388 x: Option[int] = Some(None) 389 y: Option[str] = Some("bye") 390 assert x.and_ok(y).is_none() 391 392 x: Option[int] = Some(10) 393 y: Option[str] = Some("bye") 394 assert value.and_ok(y) == Some("bye") 395 ``` 396 """ 397 if self._value is None or optb.is_none(): 398 return nothing_unchecked() 399 400 return optb 401 402 def and_then(self, f: Fn[T, Some[T]]) -> Some[T]: 403 """Returns `Some(None)` if the contained value is `None`, otherwise call `f()` 404 on `T` and return `Option[T]` if it's value not `None`. 405 406 Example 407 ------- 408 ```py 409 value = Some(5) 410 print(value.and_then(lambda x: Some(x * 2))) 411 # Some(10) 412 413 value: Option[int] = Some(10) 414 print(value.and_then(lambda x: Some(None))) 415 # Some(None) 416 ``` 417 """ 418 if self._value is None: 419 return nothing_unchecked() 420 421 return f(self._value) 422 423 # *- Builder methods *- 424 425 def iter(self) -> _iter.Iterator[T]: 426 """Returns an iterator over the contained value. 427 428 Example 429 ------- 430 ```py 431 from sain import Some 432 value = Some("gg") 433 value.next() == Some("gg") 434 435 value: Option[int] = Some(None) 436 value.next().is_none() 437 ``` 438 """ 439 if self._value is None: 440 #! SAFETY: We know the value is None here. 441 return _iter.empty() 442 443 return _iter.once(self._value) 444 445 def as_ref(self) -> Some[cell.Cell[T]]: 446 """Returns immutable `Some[sain.cell.Cell[T]]` if the contained value is not `None`, 447 448 Otherwise returns `Some(None)`. 449 450 Example 451 ------- 452 ```py 453 value = Some(5).as_ref().unwrap() 454 value.object = 0 # FrozenError! 455 456 owned = value.object 457 print(owned) # 5 458 459 # Create a copy of object. 460 clone = value.copy() 461 clone = 0 # Thats fine. 462 print(clone == owned) # False, 0 != 5 463 464 # None object. 465 value: Option[int] = Some(None) 466 print(value.as_ref()) 467 # Some(None) 468 ``` 469 470 Raises 471 ------ 472 `dataclasses.FrozenInstanceError` 473 When attempting to modify the contained value. Use `sain.AsRef.copy()` method to create a copy. 474 475 Or just use `.as_mut()` if you're dealing with mutable objects. 476 """ 477 if self._value is not None: 478 return Some(cell.Cell(self._value)) 479 480 # SAFETY: self._value is None. 481 return NOTHING # pyright: ignore 482 483 def as_mut(self) -> Some[cell.RefCell[T]]: 484 """Returns mutable `Some[sain.cell.RefCell[T]]` if the contained value is not `None`, 485 486 Otherwise returns `Some(None)`. 487 488 Example 489 ------- 490 ```py 491 value = Some(5).as_mut().unwrap() 492 value.object = 0 493 print(value) # Some(RefCell(0)) 494 495 # None object. 496 value: Option[int] = Some(None) 497 print(value.as_mut()) 498 # Some(None) 499 ``` 500 """ 501 if self._value is not None: 502 return Some(cell.RefCell(self._value)) 503 504 # SAFETY: self._value is None. 505 return NOTHING # pyright: ignore 506 507 # *- Boolean checks *- 508 509 def is_some(self) -> bool: 510 """Returns `True` if the contained value is not `None`, otherwise returns `False`. 511 512 Example 513 ------- 514 ```py 515 value = Some(5) 516 print(value.is_some()) 517 # True 518 519 value: Option[int] = Some(None) 520 print(value.is_some()) 521 # False 522 ``` 523 """ 524 return self._value is not None 525 526 def is_some_and(self, predicate: Fn[T, bool]) -> bool: 527 """Returns `True` if the contained value is not `None` and 528 the predicate returns `True`, otherwise returns `False`. 529 530 Example 531 ------- 532 ```py 533 value = Some(5) 534 print(value.is_some_and(lambda x: x > 3)) 535 # True 536 537 value: Option[int] = Some(None) 538 print(value.is_some_and(lambda x: x > 3)) 539 # False 540 ``` 541 """ 542 return self._value is not None and predicate(self._value) 543 544 def is_none(self) -> bool: 545 """Returns `True` if the contained value is `None`, otherwise returns `False`. 546 547 Example 548 ------- 549 ```py 550 value = Some(5) 551 print(value.is_none()) 552 # False 553 554 value: Option[int] = Some(None) 555 print(value.is_none()) 556 # True 557 ``` 558 """ 559 return self._value is None 560 561 def __repr__(self) -> str: 562 if self._value is None: 563 return "None" 564 return f"Some({self._value!r})" 565 566 __str__ = __repr__ 567 568 def __invert__(self) -> T: 569 return self.unwrap() 570 571 def __or__(self, other: T) -> T: 572 return self.unwrap_or(other) 573 574 def __bool__(self) -> bool: 575 return self.is_some() 576 577 def __eq__(self, other: object) -> bool: 578 if not isinstance(other, Some): 579 return NotImplemented 580 581 return self._value == other._value # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] 582 583 def __ne__(self, other: object) -> bool: 584 return not self.__eq__(other) 585 586 def __hash__(self) -> int: 587 return hash(self._value)
The Option
type. An object that might be T
or None
.
It is a drop-in replacement for typing.Optional[T]
, But has proper methods to handle the contained value.
Example
value = Some("Hello")
print(value)
# Some("Hello")
# This will unwrap the contained value as long as
# it is not `None` otherwise this will raise an error.
print(value.unwrap())
# "Hello"
none_value = Some(None)
while none_value.unwrap():
# Never unreachable!
# Solving it with `unwrap_or` method to unwrap the value or return a default value.
print(none_value.unwrap_or(10))
# 10
103 def into_inner(self) -> T | None: 104 """Consume `Self`, returning the wrapped value as `T | None`. 105 106 Examples 107 -------- 108 ```py 109 opt = Some('char') 110 x = opt.into_inner() 111 assert x is not None 112 113 opt = Some(None) 114 assert opt.into_inner() is None 115 ``` 116 """ 117 return self._value
Consume Self
, returning the wrapped value as T | None
.
Examples
opt = Some('char')
x = opt.into_inner()
assert x is not None
opt = Some(None)
assert opt.into_inner() is None
119 def unwrap(self) -> T: 120 """Unwrap the inner value either returning if its not `None` or raising a `RuntimeError`. 121 122 It's usually not recommended to use this method in production code, since it raises. 123 124 Example 125 ------- 126 ```py 127 value = Some(5) 128 print(value.unwrap()) 129 # 5 130 131 value = Some(None) 132 print(value.unwrap()) 133 # RuntimeError 134 ``` 135 136 Raises 137 ------ 138 `RuntimeError` 139 If the inner value is `None`. 140 """ 141 if self._value is None: 142 raise RuntimeError( 143 f"Called `Option.unwrap()` on {type(self._value).__name__}." 144 ) from None 145 146 return self._value
Unwrap the inner value either returning if its not None
or raising a RuntimeError
.
It's usually not recommended to use this method in production code, since it raises.
Example
value = Some(5)
print(value.unwrap())
# 5
value = Some(None)
print(value.unwrap())
# RuntimeError
Raises
RuntimeError
: If the inner value isNone
.
148 def unwrap_or(self, default: T, /) -> T: 149 """Unwrap the inner value either returning if its not `None` or returning `default`. 150 151 Example 152 ------- 153 ```py 154 value = Some(5) 155 print(value.unwrap_or(10)) 156 # 5 157 158 # Type hint is required here. 159 value: Option[int] = Some(None) 160 print(value.unwrap_or(10)) 161 # 10 162 ``` 163 """ 164 if self._value is None: 165 return default 166 167 return self._value
Unwrap the inner value either returning if its not None
or returning default
.
Example
value = Some(5)
print(value.unwrap_or(10))
# 5
# Type hint is required here.
value: Option[int] = Some(None)
print(value.unwrap_or(10))
# 10
169 def unwrap_or_else(self, f: FnOnce[T], /) -> T: 170 """Unwrap the inner value either returning if its not `None` or calling `f` to get a default value. 171 172 Example 173 ------- 174 ```py 175 value = Some(5) 176 print(value.unwrap_or_else(lambda: 10)) 177 # 5 178 179 value: Option[bool] = Some(None) 180 print(value.unwrap_or_else(lambda: True)) 181 # True 182 ``` 183 """ 184 if self._value is None: 185 return f() 186 187 return self._value
Unwrap the inner value either returning if its not None
or calling f
to get a default value.
Example
value = Some(5)
print(value.unwrap_or_else(lambda: 10))
# 5
value: Option[bool] = Some(None)
print(value.unwrap_or_else(lambda: True))
# True
189 @macros.unsafe 190 def unwrap_unchecked(self) -> T: 191 """Returns the contained Some value without checking that the value is not None. 192 193 Example 194 ------- 195 ```py 196 v: Option[float] = Some(1.2) 197 v.unwrap_unchecked() # 1.2 198 199 v: Option[float] = Some(None) 200 print(v.unwrap_unchecked()) # Undefined Behavior 201 ``` 202 """ 203 #! SAFETY: The caller guarantees that the value is not None. 204 return self._value # type: ignore
Returns the contained Some value without checking that the value is not None.
Example
v: Option[float] = Some(1.2)
v.unwrap_unchecked() # 1.2
v: Option[float] = Some(None)
print(v.unwrap_unchecked()) # Undefined Behavior
Safety ⚠️
Calling this method on None
is considered undefined behavior.
206 def expect(self, message: str, /) -> T: 207 """Returns the contained value if it is not `None` otherwise raises a `RuntimeError`. 208 209 Example 210 ------- 211 ```py 212 value = Some("Hello") 213 214 print(value.expect("Value is None")) 215 # "Hello" 216 217 value: Option[str] = Some(None) 218 print(value.expect("Value is None")) 219 # RuntimeError("Value is None") 220 ``` 221 """ 222 if self._value is None: 223 raise RuntimeError(message) 224 225 return self._value
Returns the contained value if it is not None
otherwise raises a RuntimeError
.
Example
value = Some("Hello")
print(value.expect("Value is None"))
# "Hello"
value: Option[str] = Some(None)
print(value.expect("Value is None"))
# RuntimeError("Value is None")
228 def map(self, f: Fn[T, U], /) -> Some[U]: 229 """Map the inner value to another type. Returning `Some(None)` if `T` is `None`. 230 231 Example 232 ------- 233 ```py 234 value = Some(5.0) 235 236 print(value.map(lambda x: x * 2.0)) 237 # Some(10.0) 238 239 value: Option[bool] = Some(None) 240 print(value) 241 # Some(None) 242 ``` 243 """ 244 if self._value is None: 245 return nothing_unchecked() 246 247 return Some(f(self._value))
Map the inner value to another type. Returning Some(None)
if T
is None
.
Example
value = Some(5.0)
print(value.map(lambda x: x * 2.0))
# Some(10.0)
value: Option[bool] = Some(None)
print(value)
# Some(None)
249 def map_or(self, default: U, f: Fn[T, U], /) -> U: 250 """Map the inner value to another type or return `default` if its `None`. 251 252 Example 253 ------- 254 ```py 255 value: Option[float] = Some(5.0) 256 257 # map to int. 258 print(value.map_or(0, int)) 259 # 6 260 261 value: Option[float] = Some(None) 262 print(value.map_or(0, int) 263 # 0 264 ``` 265 """ 266 if self._value is None: 267 return default 268 269 return f(self._value)
Map the inner value to another type or return default
if its None
.
Example
value: Option[float] = Some(5.0)
# map to int.
print(value.map_or(0, int))
# 6
value: Option[float] = Some(None)
print(value.map_or(0, int)
# 0
271 def map_or_else(self, default: FnOnce[U], f: Fn[T, U], /) -> U: 272 """Map the inner value to another type, or return `default()` if its `None`. 273 274 Example 275 ------- 276 ```py 277 def default() -> int: 278 return sys.getsizeof(object()) 279 280 value: Option[float] = Some(5.0) 281 282 # map to int. 283 print(value.map_or_else(default, int)) 284 # 6 285 286 value: Option[float] = Some(None) 287 print(value.map_or_else(default, int) 288 # 28 <- size of object() 289 ``` 290 """ 291 if self._value is None: 292 return default() 293 294 return f(self._value)
Map the inner value to another type, or return default()
if its None
.
Example
def default() -> int:
return sys.getsizeof(object())
value: Option[float] = Some(5.0)
# map to int.
print(value.map_or_else(default, int))
# 6
value: Option[float] = Some(None)
print(value.map_or_else(default, int)
# 28 <- size of object()
296 def filter(self, predicate: Fn[T, bool]) -> Some[T]: 297 """Returns `Some(None)` if the contained value is `None`, 298 299 otherwise calls the predicate and returns `Some(T)` if the predicate returns `True`. 300 301 Example 302 ------- 303 ```py 304 value = Some([1, 2, 3]) 305 306 print(value.filter(lambda x: 1 in x)) 307 # Some([1, 2, 3]) 308 309 value: Option[int] = Some([1, 2, 3]) # or Some(None) 310 print(value.filter(lambda x: 1 not in x)) 311 # None 312 ``` 313 """ 314 if (value := self._value) is not None: 315 if predicate(value): 316 return Some(value) 317 318 return nothing_unchecked()
Returns Some(None)
if the contained value is None
,
otherwise calls the predicate and returns Some(T)
if the predicate returns True
.
Example
value = Some([1, 2, 3])
print(value.filter(lambda x: 1 in x))
# Some([1, 2, 3])
value: Option[int] = Some([1, 2, 3]) # or Some(None)
print(value.filter(lambda x: 1 not in x))
# None
322 def take(self) -> Option[T]: 323 """Take the value from `Self`, Setting it to `None`. 324 325 Example 326 ------- 327 ```py 328 original = Some("Hi") 329 new = original.take() 330 331 print(original, new) 332 # None, Some("Hi") 333 ``` 334 """ 335 if self._value is None: 336 return nothing_unchecked() 337 338 val = self._value 339 self._value = None 340 return Some(val)
Take the value from Self
, Setting it to None
.
Example
original = Some("Hi")
new = original.take()
print(original, new)
# None, Some("Hi")
342 def take_if(self, predicate: collections.Callable[[T], bool]) -> Option[T]: 343 """Take the value from `Self`, Setting it to `None` only if predicate returns `True`. 344 345 Example 346 ------- 347 ```py 348 def validate(email: str) -> bool: 349 # you can obviously validate this better. 350 return email.find('@') == 1 351 352 original = Some("flex@gg.com") 353 valid = original.take_if(validate) 354 assert is_allowed.is_some() and original.is_none() 355 356 original = Some("mail.example.com") 357 invalid = original.take_if(validate) 358 assert invalid.is_none() and original.is_some() 359 ``` 360 """ 361 if self.map_or(False, predicate): 362 return self.take() 363 364 return nothing_unchecked()
Take the value from Self
, Setting it to None
only if predicate returns True
.
Example
def validate(email: str) -> bool:
# you can obviously validate this better.
return email.find('@') == 1
original = Some("flex@gg.com")
valid = original.take_if(validate)
assert is_allowed.is_some() and original.is_none()
original = Some("mail.example.com")
invalid = original.take_if(validate)
assert invalid.is_none() and original.is_some()
366 def replace(self, value: T) -> Some[T]: 367 """Replace the contained value with another value. 368 369 Example 370 ------- 371 ```py 372 value: Option[str] = Some(None) 373 value = value.replace("Hello") 374 # Some("Hello") 375 ``` 376 """ 377 self._value = value 378 return self
Replace the contained value with another value.
Example
value: Option[str] = Some(None)
value = value.replace("Hello")
# Some("Hello")
380 def and_ok(self, optb: Some[T]) -> Some[T]: 381 """Returns `Some(None)` if either contained value is `None`, 382 383 Otherwise return `optb`. 384 385 Example 386 ------- 387 ```py 388 x: Option[int] = Some(None) 389 y: Option[str] = Some("bye") 390 assert x.and_ok(y).is_none() 391 392 x: Option[int] = Some(10) 393 y: Option[str] = Some("bye") 394 assert value.and_ok(y) == Some("bye") 395 ``` 396 """ 397 if self._value is None or optb.is_none(): 398 return nothing_unchecked() 399 400 return optb
Returns Some(None)
if either contained value is None
,
Otherwise return optb
.
Example
x: Option[int] = Some(None)
y: Option[str] = Some("bye")
assert x.and_ok(y).is_none()
x: Option[int] = Some(10)
y: Option[str] = Some("bye")
assert value.and_ok(y) == Some("bye")
402 def and_then(self, f: Fn[T, Some[T]]) -> Some[T]: 403 """Returns `Some(None)` if the contained value is `None`, otherwise call `f()` 404 on `T` and return `Option[T]` if it's value not `None`. 405 406 Example 407 ------- 408 ```py 409 value = Some(5) 410 print(value.and_then(lambda x: Some(x * 2))) 411 # Some(10) 412 413 value: Option[int] = Some(10) 414 print(value.and_then(lambda x: Some(None))) 415 # Some(None) 416 ``` 417 """ 418 if self._value is None: 419 return nothing_unchecked() 420 421 return f(self._value)
Returns Some(None)
if the contained value is None
, otherwise call f()
on T
and return Option[T]
if it's value not None
.
Example
value = Some(5)
print(value.and_then(lambda x: Some(x * 2)))
# Some(10)
value: Option[int] = Some(10)
print(value.and_then(lambda x: Some(None)))
# Some(None)
425 def iter(self) -> _iter.Iterator[T]: 426 """Returns an iterator over the contained value. 427 428 Example 429 ------- 430 ```py 431 from sain import Some 432 value = Some("gg") 433 value.next() == Some("gg") 434 435 value: Option[int] = Some(None) 436 value.next().is_none() 437 ``` 438 """ 439 if self._value is None: 440 #! SAFETY: We know the value is None here. 441 return _iter.empty() 442 443 return _iter.once(self._value)
Returns an iterator over the contained value.
Example
from sain import Some
value = Some("gg")
value.next() == Some("gg")
value: Option[int] = Some(None)
value.next().is_none()
445 def as_ref(self) -> Some[cell.Cell[T]]: 446 """Returns immutable `Some[sain.cell.Cell[T]]` if the contained value is not `None`, 447 448 Otherwise returns `Some(None)`. 449 450 Example 451 ------- 452 ```py 453 value = Some(5).as_ref().unwrap() 454 value.object = 0 # FrozenError! 455 456 owned = value.object 457 print(owned) # 5 458 459 # Create a copy of object. 460 clone = value.copy() 461 clone = 0 # Thats fine. 462 print(clone == owned) # False, 0 != 5 463 464 # None object. 465 value: Option[int] = Some(None) 466 print(value.as_ref()) 467 # Some(None) 468 ``` 469 470 Raises 471 ------ 472 `dataclasses.FrozenInstanceError` 473 When attempting to modify the contained value. Use `sain.AsRef.copy()` method to create a copy. 474 475 Or just use `.as_mut()` if you're dealing with mutable objects. 476 """ 477 if self._value is not None: 478 return Some(cell.Cell(self._value)) 479 480 # SAFETY: self._value is None. 481 return NOTHING # pyright: ignore
Returns immutable Some[sain.cell.Cell[T]]
if the contained value is not None
,
Otherwise returns Some(None)
.
Example
value = Some(5).as_ref().unwrap()
value.object = 0 # FrozenError!
owned = value.object
print(owned) # 5
# Create a copy of object.
clone = value.copy()
clone = 0 # Thats fine.
print(clone == owned) # False, 0 != 5
# None object.
value: Option[int] = Some(None)
print(value.as_ref())
# Some(None)
Raises
dataclasses.FrozenInstanceError
: When attempting to modify the contained value. Usesain.AsRef.copy()
method to create a copy.
Or just use .as_mut()
if you're dealing with mutable objects.
483 def as_mut(self) -> Some[cell.RefCell[T]]: 484 """Returns mutable `Some[sain.cell.RefCell[T]]` if the contained value is not `None`, 485 486 Otherwise returns `Some(None)`. 487 488 Example 489 ------- 490 ```py 491 value = Some(5).as_mut().unwrap() 492 value.object = 0 493 print(value) # Some(RefCell(0)) 494 495 # None object. 496 value: Option[int] = Some(None) 497 print(value.as_mut()) 498 # Some(None) 499 ``` 500 """ 501 if self._value is not None: 502 return Some(cell.RefCell(self._value)) 503 504 # SAFETY: self._value is None. 505 return NOTHING # pyright: ignore
Returns mutable Some[sain.cell.RefCell[T]]
if the contained value is not None
,
Otherwise returns Some(None)
.
Example
value = Some(5).as_mut().unwrap()
value.object = 0
print(value) # Some(RefCell(0))
# None object.
value: Option[int] = Some(None)
print(value.as_mut())
# Some(None)
509 def is_some(self) -> bool: 510 """Returns `True` if the contained value is not `None`, otherwise returns `False`. 511 512 Example 513 ------- 514 ```py 515 value = Some(5) 516 print(value.is_some()) 517 # True 518 519 value: Option[int] = Some(None) 520 print(value.is_some()) 521 # False 522 ``` 523 """ 524 return self._value is not None
Returns True
if the contained value is not None
, otherwise returns False
.
Example
value = Some(5)
print(value.is_some())
# True
value: Option[int] = Some(None)
print(value.is_some())
# False
526 def is_some_and(self, predicate: Fn[T, bool]) -> bool: 527 """Returns `True` if the contained value is not `None` and 528 the predicate returns `True`, otherwise returns `False`. 529 530 Example 531 ------- 532 ```py 533 value = Some(5) 534 print(value.is_some_and(lambda x: x > 3)) 535 # True 536 537 value: Option[int] = Some(None) 538 print(value.is_some_and(lambda x: x > 3)) 539 # False 540 ``` 541 """ 542 return self._value is not None and predicate(self._value)
Returns True
if the contained value is not None
and
the predicate returns True
, otherwise returns False
.
Example
value = Some(5)
print(value.is_some_and(lambda x: x > 3))
# True
value: Option[int] = Some(None)
print(value.is_some_and(lambda x: x > 3))
# False
544 def is_none(self) -> bool: 545 """Returns `True` if the contained value is `None`, otherwise returns `False`. 546 547 Example 548 ------- 549 ```py 550 value = Some(5) 551 print(value.is_none()) 552 # False 553 554 value: Option[int] = Some(None) 555 print(value.is_none()) 556 # True 557 ``` 558 """ 559 return self._value is None
Returns True
if the contained value is None
, otherwise returns False
.
Example
value = Some(5)
print(value.is_none())
# False
value: Option[int] = Some(None)
print(value.is_none())
# True
716@typing.final 717@diagnostic 718class Iter(Iterator[Item]): 719 """a lazy iterator that has its items ready in-memory. 720 721 This is similar to Rust `std::slice::Iter<T>` item which iterables can build 722 from this via `.iter()` method. 723 724 Example 725 ------- 726 ```py 727 iterator = Iter([1, 2, 3]) 728 729 # Limit the results to 2. 730 for item in iterator.take(2): 731 print(item) 732 # 1 733 # 2 734 735 # Filter the results. 736 for item in iterator.filter(lambda item: item > 1): 737 print(item) 738 # 2 739 # 3 740 # 3 741 742 # Indexing is supported. 743 print(iterator[0]) 744 # 1 745 ``` 746 747 Parameters 748 ---------- 749 items: `Iterable[Item]` 750 The items to iterate over. This can be anything that implements `__iter__` and `__next__`. 751 """ 752 753 __slots__ = ("_it",) 754 755 def __init__(self, iterable: collections.Iterable[Item]) -> None: 756 self._it = iter(iterable) 757 758 def clone(self) -> typing.Self: 759 """Return a copy of this iterator. 760 761 ```py 762 it = Iterator([1, 2, 3]) 763 764 for i in it.clone(): 765 ... 766 767 # The actual iterator hasn't been exhausted. 768 assert it.count() == 3 769 ``` 770 """ 771 return self.__class__(copy.copy(self._it)) 772 773 def __next__(self) -> Item: 774 try: 775 return next(self._it) 776 except StopIteration: 777 unreachable() 778 779 def __getitem__(self, index: int) -> Option[Item]: 780 try: 781 return self.skip(index).first() 782 except IndexError: 783 unreachable() 784 785 def __contains__(self, item: Item) -> bool: 786 return item in self._it
a lazy iterator that has its items ready in-memory.
This is similar to Rust std::slice::Iter<T>
item which iterables can build
from this via sain.iter()
method.
Example
iterator = Iter([1, 2, 3])
# Limit the results to 2.
for item in iterator.take(2):
print(item)
# 1
# 2
# Filter the results.
for item in iterator.filter(lambda item: item > 1):
print(item)
# 2
# 3
# 3
# Indexing is supported.
print(iterator[0])
# 1
Parameters
- items (
Iterable[Item]
): The items to iterate over. This can be anything that implements__iter__
and__next__
.
758 def clone(self) -> typing.Self: 759 """Return a copy of this iterator. 760 761 ```py 762 it = Iterator([1, 2, 3]) 763 764 for i in it.clone(): 765 ... 766 767 # The actual iterator hasn't been exhausted. 768 assert it.count() == 3 769 ``` 770 """ 771 return self.__class__(copy.copy(self._it))
Return a copy of this iterator.
it = Iterator([1, 2, 3])
for i in it.clone():
...
# The actual iterator hasn't been exhausted.
assert it.count() == 3
97class Iterator( 98 typing.Generic[Item], 99 abc.ABC, 100 _default.Default[Item], 101): 102 """An abstract interface for dealing with iterators. 103 104 This is exactly the same trait as `core::iter::Iterator` trait from Rust. 105 106 This is the main interface that any type can implement by basically inheriting from it. 107 The method `__next__` is the only method that needs to be implemented, You get all the other methods for free. 108 109 If you want to use a ready iterator for general purposes, Use `Iter`. This interface is only for implementors 110 and type hints. 111 112 Example 113 ------- 114 ```py 115 @dataclass 116 class Message: 117 content: str 118 119 class MessageIterator(sain.Iterator[Message]): 120 def __init__(self, id: int = random.randint(0, 100)) -> None: 121 self._session: requests.Session | None = None 122 self.id = id 123 124 def __next__(self) -> Message: 125 if self._session is None: 126 self._session = requests.session() 127 128 try: 129 with self._session: 130 response = self._session.get(f"https://dummyjson.com/products/{self.id}").json() 131 finally: 132 self._session = None 133 134 return Message(response["description"]) 135 136 it = MessageIterator() 137 # Lazily fetch the first 5 messages from the API. 138 for msg in it.take(5): 139 print(msg) 140 ``` 141 """ 142 143 @abc.abstractmethod 144 def __next__(self) -> Item: 145 raise NotImplementedError 146 147 ################### 148 # const functions # 149 ################### 150 151 @staticmethod 152 @typing.final 153 def default() -> Empty[Item]: 154 """Return the default iterator for this type. It returns an empty iterator. 155 156 Example 157 ------- 158 ```py 159 it: Iterator[int] = Iter.default() 160 assert t.next().is_none() 161 ``` 162 """ 163 return Empty() 164 165 @typing.overload 166 @typing.final 167 def collect(self) -> collections.Sequence[Item]: ... 168 169 @typing.overload 170 @typing.final 171 def collect( 172 self, *, cast: collections.Callable[[Item], OtherItem] 173 ) -> collections.Sequence[OtherItem]: ... 174 175 @typing.final 176 def collect( 177 self, *, cast: collections.Callable[[Item], OtherItem] | None = None 178 ) -> collections.Sequence[Item] | collections.Sequence[OtherItem]: 179 """Collects all items in the iterator into an immutable sequence. 180 181 Example 182 ------- 183 ```py 184 iterator = Iter(range(3)) 185 iterator.collect() 186 # (0, 1, 2, 3) 187 iterator.collect(cast=str) # Map each element and collect it. 188 # ('0', '1', '2', '3') 189 ``` 190 191 Parameters 192 ---------- 193 cast: `T | None` 194 An optional type to cast the items into. 195 If not provided the items will be returned as it's original type. 196 """ 197 if cast is not None: 198 return tuple(cast(i) for i in self) 199 200 return tuple(_ for _ in self) 201 202 @typing.final 203 def to_vec(self) -> vec.Vec[Item]: 204 """Convert this iterator into `Vec[T]`. 205 206 Example 207 ------- 208 ```py 209 it = sain.iter.once(0) 210 vc = it.to_vec() 211 212 assert to_vec == [0] 213 ``` 214 """ 215 return vec.Vec(_ for _ in self) 216 217 @typing.final 218 def sink(self) -> None: 219 """Consume all elements from this iterator, flushing it into the sink. 220 221 Example 222 ------- 223 ```py 224 it = Iter((1, 2, 3)) 225 it.sink() 226 assert it.next().is_none() 227 ``` 228 """ 229 for _ in self: 230 pass 231 232 @typing.final 233 def raw_parts(self) -> collections.Generator[Item, None, None]: 234 """Decompose all elements from this iterator, yielding it one by one 235 as a normal generator. 236 237 This mainly used for objects that needs to satisfy its exact type. 238 239 ```py 240 it = Iter("cba") 241 sort = sorted(it.raw_parts()) 242 243 assert it.count() == 0 244 assert sort == ["a", "b", "c"] 245 ``` 246 """ 247 for item in self: 248 yield item 249 250 ################## 251 # default impl's # 252 ################## 253 254 def next(self) -> Option[Item]: 255 """Advance the iterator, Returning the next item, `Some(None)` if all items yielded. 256 257 Example 258 ------- 259 ```py 260 iterator = Iter(["1", "2"]) 261 assert iterator.next() == Some("1") 262 assert iterator.next() == Some("2") 263 assert iterator.next().is_none() 264 ``` 265 """ 266 try: 267 return _option.Some(self.__next__()) 268 except StopIteration: 269 return _option.nothing_unchecked() 270 271 def cloned(self) -> Cloned[Item]: 272 """Creates an iterator which shallow copies its elements by reference. 273 274 If you need a copy of the actual iterator and not the elements. 275 use `Iter.clone()` 276 277 .. note:: 278 This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html) 279 on each item that is being yielded. 280 281 Example 282 ------- 283 ```py 284 @dataclass 285 class User: 286 users_ids: list[int] = [] 287 288 # An iterator which elements points to the same user. 289 user = User() 290 it = Iter((user, user)) 291 292 for u in it.cloned(): 293 u.user_ids.append(1) 294 295 # We iterated over the same user pointer twice and appended "1" 296 # since `copy` returns a shallow copy of nested structures. 297 assert len(user.user_ids) == 2 298 ``` 299 """ 300 return Cloned(self) 301 302 def copied(self) -> Copied[Item]: 303 """Creates an iterator which copies all of its elements by value. 304 305 If you only need a copy of the item reference, Use `.cloned()` instead. 306 307 .. note:: 308 This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html) 309 on each item that is being yielded. 310 311 Example 312 ------- 313 ```py 314 @dataclass 315 class User: 316 users_ids: list[int] = [] 317 318 # An iterator which elements points to the same user. 319 user = User() 320 it = Iter((user, user)) 321 322 for u in it.copied(): 323 # A new list is created for each item. 324 u.user_ids.append(1) 325 326 # The actual list is untouched since we consumed a deep copy of it. 327 assert len(user.user_ids) == 0 328 ``` 329 """ 330 return Copied(self) 331 332 def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]: 333 """Maps each item in the iterator to another type. 334 335 Example 336 ------- 337 ```py 338 iterator = Iter(["1", "2", "3"]).map(int) 339 340 for item in iterator: 341 assert isinstance(item, int) 342 ``` 343 344 Parameters 345 ---------- 346 predicate: `Callable[[Item], OtherItem]` 347 The function to map each item in the iterator to the other type. 348 """ 349 return Map(self, fn) 350 351 def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]: 352 """Filters the iterator to only yield items that match the predicate. 353 354 Example 355 ------- 356 ```py 357 places = Iter(['London', 'Paris', 'Los Angeles']) 358 for place in places.filter(lambda place: place.startswith('L')): 359 print(place) 360 361 # London 362 # Los Angeles 363 ``` 364 """ 365 return Filter(self, predicate) 366 367 def take(self, count: int) -> Take[Item]: 368 """Take the first number of items until the number of items 369 are yielded or the end of the iterator is exhausted. 370 371 Example 372 ------- 373 ```py 374 iterator = Iter(['c', 'x', 'y']) 375 376 for x in iterator.take(2): 377 assert x in ('c', 'x') 378 379 # <Iter(['c', 'x'])> 380 ``` 381 """ 382 return Take(self, count) 383 384 def skip(self, count: int) -> Skip[Item]: 385 """Skips the first number of items in the iterator. 386 387 Example 388 ------- 389 ```py 390 iterator = Iter((1, 2, 3, 4)) 391 for i in iterator.skip(2): 392 print(i) 393 394 # 3 395 # 4 396 ``` 397 """ 398 return Skip(self, count) 399 400 def enumerate(self, *, start: int = 0) -> Enumerate[Item]: 401 """Create a new iterator that yields a tuple of the index and item. 402 403 Example 404 ------- 405 ```py 406 iterator = Iter([1, 2, 3]) 407 for index, item in iterator.enumerate(): 408 print(index, item) 409 410 # 0 1 411 # 1 2 412 # 2 3 413 ``` 414 """ 415 return Enumerate(self, start) 416 417 def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]: 418 """yields items from the iterator while predicate returns `True`. 419 420 The rest of the items are discarded as soon as the predicate returns `False` 421 422 Example 423 ------- 424 ```py 425 iterator = Iter(['a', 'ab', 'xd', 'ba']) 426 for x in iterator.take_while(lambda x: 'a' in x): 427 print(x) 428 429 # a 430 # ab 431 ``` 432 433 Parameters 434 ---------- 435 predicate: `collections.Callable[[Item], bool]` 436 The function to predicate each item in the iterator. 437 """ 438 return TakeWhile(self, f) 439 440 def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]: 441 """Yields items from the iterator while predicate returns `False`. 442 443 Example 444 ------- 445 ```py 446 iterator = Iter(['a', 'ab', 'xd', 'ba']) 447 for x in iterator.drop_while(lambda x: 'a' in x): 448 print(x) 449 450 # xd 451 # ba 452 ``` 453 454 Parameters 455 ---------- 456 predicate: `collections.Callable[[Item], bool]` 457 The function to predicate each item in the iterator. 458 """ 459 return DropWhile(self, f) 460 461 def all(self, predicate: collections.Callable[[Item], bool]) -> bool: 462 """Return `True` if all items in the iterator match the predicate. 463 464 Example 465 ------- 466 ```py 467 iterator = Iter([1, 2, 3]) 468 while iterator.all(lambda item: isinstance(item, int)): 469 print("Still all integers") 470 continue 471 # Still all integers 472 ``` 473 474 Parameters 475 ---------- 476 predicate: `collections.Callable[[Item], bool]` 477 The function to test each item in the iterator. 478 """ 479 return all(predicate(item) for item in self) 480 481 def any(self, predicate: collections.Callable[[Item], bool]) -> bool: 482 """`True` if any items in the iterator match the predicate. 483 484 Example 485 ------- 486 ```py 487 iterator = Iter([1, 2, 3]) 488 if iterator.any(lambda item: isinstance(item, int)): 489 print("At least one item is an int.") 490 # At least one item is an int. 491 ``` 492 493 Parameters 494 ---------- 495 predicate: `collections.Callable[[Item], bool]` 496 The function to test each item in the iterator. 497 """ 498 return any(predicate(item) for item in self) 499 500 def zip( 501 self, other: collections.Iterable[OtherItem] 502 ) -> Iterator[tuple[Item, OtherItem]]: 503 """Zips the iterator with another iterable. 504 505 Example 506 ------- 507 ```py 508 iterator = Iter([1, 2, 3]) 509 for item, other_item in iterator.zip([4, 5, 6]): 510 assert item == other_item 511 <Iter([(1, 4), (2, 5), (3, 6)])> 512 ``` 513 514 Parameters 515 ---------- 516 other: `Iter[OtherItem]` 517 The iterable to zip with. 518 519 Returns 520 ------- 521 `Iter[tuple[Item, OtherItem]]` 522 The zipped iterator. 523 524 """ 525 return Iter(zip(self.raw_parts(), other)) 526 527 def sort( 528 self, 529 *, 530 key: collections.Callable[[Item], _typeshed.SupportsRichComparison], 531 reverse: bool = False, 532 ) -> Iterator[Item]: 533 """Sorts the iterator. 534 535 Example 536 ------- 537 ```py 538 iterator = Iter([3, 1, 6, 7]) 539 for item in iterator.sort(key=lambda item: item < 3): 540 print(item) 541 # 1 542 # 3 543 # 6 544 # 7 545 ``` 546 547 Parameters 548 ---------- 549 key: `collections.Callable[[Item], Any]` 550 The function to sort by. 551 reverse: `bool` 552 Whether to reverse the sort. 553 554 """ 555 return Iter(sorted(self.raw_parts(), key=key, reverse=reverse)) 556 557 def reversed(self) -> Iterator[Item]: 558 """Returns a new iterator that yields the items in the iterator in reverse order. 559 560 Example 561 ------- 562 ```py 563 iterator = Iter([3, 1, 6, 7]) 564 for item in iterator.reversed(): 565 print(item) 566 # 7 567 # 6 568 # 1 569 # 3 570 ``` 571 """ 572 # NOTE: In order to reverse the iterator we need to 573 # first collect it into some collection. 574 return Iter(reversed(self.collect())) 575 576 def union(self, other: collections.Iterable[Item]) -> Iterator[Item]: 577 """Returns a new iterator that yields all items from both iterators. 578 579 Example 580 ------- 581 ```py 582 iterator = Iter([1, 2, 3]) 583 other = [4, 5, 6] 584 585 for item in iterator.union(other): 586 print(item) 587 # 1 588 # 2 589 # 3 590 # 4 591 # 5 592 # 6 593 ``` 594 595 Parameters 596 ---------- 597 other: `Iter[Item]` 598 The iterable to union with. 599 """ 600 return Iter(itertools.chain(self.raw_parts(), other)) 601 602 def first(self) -> Option[Item]: 603 """Returns the first item in the iterator. 604 605 Example 606 ------- 607 ```py 608 iterator = Iter([3, 1, 6, 7]) 609 iterator.first().is_some_and(lambda x: x == 3) 610 ``` 611 """ 612 return self.take(1).next() 613 614 def last(self) -> Option[Item]: 615 """Returns the last item in the iterator. 616 617 Example 618 ------- 619 ```py 620 iterator = Iter([3, 1, 6, 7]) 621 iterator.last().is_some_and(lambda x: x == 7) 622 ``` 623 """ 624 return self.reversed().first() 625 626 def count(self) -> int: 627 """Return the count of elements in memory this iterator has. 628 629 Example 630 ------- 631 ```py 632 it = Iter(range(3)) 633 assert it.count() == 3 634 ``` 635 """ 636 count = 0 637 for _ in self: 638 count += 1 639 640 return count 641 642 def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None: 643 """Calls `func` on each item in the iterator. 644 645 Example 646 ------- 647 ```py 648 iterator = Iter([1, 2, 3]) 649 iterator.for_each(lambda item: print(item)) 650 # 1 651 # 2 652 # 3 653 ``` 654 655 Parameters 656 ---------- 657 func: `collections.Callable[[Item], typing.Any]` 658 The function to call on each item in the iterator. 659 """ 660 for item in self: 661 func(item) 662 663 async def async_for_each( 664 self, 665 func: collections.Callable[ 666 [Item], collections.Coroutine[None, typing.Any, OtherItem] 667 ], 668 ) -> _result.Result[collections.Sequence[OtherItem], futures.SpawnError]: 669 """Calls the async function on each item in the iterator concurrently. 670 671 Example 672 ------- 673 ```py 674 async def create_user(username: str) -> None: 675 async with aiohttp.request("POST", f'.../{username}') as r: 676 return await r.json() 677 678 async def main(): 679 users = sain.into_iter(["danny", "legalia"]) 680 results = await users.async_for_each(lambda username: create_user(username)) 681 for k, v in results.unwrap().items(): 682 ... 683 ``` 684 685 Parameters 686 ---------- 687 func: `collections.Callable[[Item], Coroutine[None, Any, Any]]` 688 The async function to call on each item in the iterator. 689 """ 690 return await futures.spawn(*(func(item) for item in self)) 691 692 def __reversed__(self) -> Iterator[Item]: 693 return self.reversed() 694 695 def __setitem__(self, _: None, __: None) -> typing.NoReturn: 696 raise NotImplementedError( 697 f"{type(self).__name__} doesn't support item assignment." 698 ) from None 699 700 def __repr__(self) -> str: 701 return "<Iterator>" 702 703 def __copy__(self) -> Cloned[Item]: 704 return self.cloned() 705 706 def __deepcopy__(self) -> Copied[Item]: 707 return self.copied() 708 709 def __len__(self) -> int: 710 return self.count() 711 712 def __iter__(self) -> Iterator[Item]: 713 return self
An abstract interface for dealing with iterators.
This is exactly the same trait as core::iter::Iterator
trait from Rust.
This is the main interface that any type can implement by basically inheriting from it.
The method __next__
is the only method that needs to be implemented, You get all the other methods for free.
If you want to use a ready iterator for general purposes, Use Iter
. This interface is only for implementors
and type hints.
Example
@dataclass
class Message:
content: str
class MessageIterator(sain.Iterator[Message]):
def __init__(self, id: int = random.randint(0, 100)) -> None:
self._session: requests.Session | None = None
self.id = id
def __next__(self) -> Message:
if self._session is None:
self._session = requests.session()
try:
with self._session:
response = self._session.get(f"https://dummyjson.com/products/{self.id}").json()
finally:
self._session = None
return Message(response["description"])
it = MessageIterator()
# Lazily fetch the first 5 messages from the API.
for msg in it.take(5):
print(msg)
151 @staticmethod 152 @typing.final 153 def default() -> Empty[Item]: 154 """Return the default iterator for this type. It returns an empty iterator. 155 156 Example 157 ------- 158 ```py 159 it: Iterator[int] = Iter.default() 160 assert t.next().is_none() 161 ``` 162 """ 163 return Empty()
Return the default iterator for this type. It returns an empty iterator.
Example
it: Iterator[int] = Iter.default()
assert t.next().is_none()
175 @typing.final 176 def collect( 177 self, *, cast: collections.Callable[[Item], OtherItem] | None = None 178 ) -> collections.Sequence[Item] | collections.Sequence[OtherItem]: 179 """Collects all items in the iterator into an immutable sequence. 180 181 Example 182 ------- 183 ```py 184 iterator = Iter(range(3)) 185 iterator.collect() 186 # (0, 1, 2, 3) 187 iterator.collect(cast=str) # Map each element and collect it. 188 # ('0', '1', '2', '3') 189 ``` 190 191 Parameters 192 ---------- 193 cast: `T | None` 194 An optional type to cast the items into. 195 If not provided the items will be returned as it's original type. 196 """ 197 if cast is not None: 198 return tuple(cast(i) for i in self) 199 200 return tuple(_ for _ in self)
Collects all items in the iterator into an immutable sequence.
Example
iterator = Iter(range(3))
iterator.collect()
# (0, 1, 2, 3)
iterator.collect(cast=str) # Map each element and collect it.
# ('0', '1', '2', '3')
Parameters
- cast (
T | None
): An optional type to cast the items into. If not provided the items will be returned as it's original type.
202 @typing.final 203 def to_vec(self) -> vec.Vec[Item]: 204 """Convert this iterator into `Vec[T]`. 205 206 Example 207 ------- 208 ```py 209 it = sain.iter.once(0) 210 vc = it.to_vec() 211 212 assert to_vec == [0] 213 ``` 214 """ 215 return vec.Vec(_ for _ in self)
Convert this iterator into Vec[T]
.
Example
it = sain.iter.once(0)
vc = it.to_vec()
assert to_vec == [0]
217 @typing.final 218 def sink(self) -> None: 219 """Consume all elements from this iterator, flushing it into the sink. 220 221 Example 222 ------- 223 ```py 224 it = Iter((1, 2, 3)) 225 it.sink() 226 assert it.next().is_none() 227 ``` 228 """ 229 for _ in self: 230 pass
Consume all elements from this iterator, flushing it into the sink.
Example
it = Iter((1, 2, 3))
it.sink()
assert it.next().is_none()
232 @typing.final 233 def raw_parts(self) -> collections.Generator[Item, None, None]: 234 """Decompose all elements from this iterator, yielding it one by one 235 as a normal generator. 236 237 This mainly used for objects that needs to satisfy its exact type. 238 239 ```py 240 it = Iter("cba") 241 sort = sorted(it.raw_parts()) 242 243 assert it.count() == 0 244 assert sort == ["a", "b", "c"] 245 ``` 246 """ 247 for item in self: 248 yield item
Decompose all elements from this iterator, yielding it one by one as a normal generator.
This mainly used for objects that needs to satisfy its exact type.
it = Iter("cba")
sort = sorted(it.raw_parts())
assert it.count() == 0
assert sort == ["a", "b", "c"]
254 def next(self) -> Option[Item]: 255 """Advance the iterator, Returning the next item, `Some(None)` if all items yielded. 256 257 Example 258 ------- 259 ```py 260 iterator = Iter(["1", "2"]) 261 assert iterator.next() == Some("1") 262 assert iterator.next() == Some("2") 263 assert iterator.next().is_none() 264 ``` 265 """ 266 try: 267 return _option.Some(self.__next__()) 268 except StopIteration: 269 return _option.nothing_unchecked()
Advance the iterator, Returning the next item, Some(None)
if all items yielded.
Example
iterator = Iter(["1", "2"])
assert iterator.next() == Some("1")
assert iterator.next() == Some("2")
assert iterator.next().is_none()
271 def cloned(self) -> Cloned[Item]: 272 """Creates an iterator which shallow copies its elements by reference. 273 274 If you need a copy of the actual iterator and not the elements. 275 use `Iter.clone()` 276 277 .. note:: 278 This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html) 279 on each item that is being yielded. 280 281 Example 282 ------- 283 ```py 284 @dataclass 285 class User: 286 users_ids: list[int] = [] 287 288 # An iterator which elements points to the same user. 289 user = User() 290 it = Iter((user, user)) 291 292 for u in it.cloned(): 293 u.user_ids.append(1) 294 295 # We iterated over the same user pointer twice and appended "1" 296 # since `copy` returns a shallow copy of nested structures. 297 assert len(user.user_ids) == 2 298 ``` 299 """ 300 return Cloned(self)
Creates an iterator which shallow copies its elements by reference.
If you need a copy of the actual iterator and not the elements.
use Iter.clone()
This method calls copy.copy()
on each item that is being yielded.
Example
@dataclass
class User:
users_ids: list[int] = []
# An iterator which elements points to the same user.
user = User()
it = Iter((user, user))
for u in it.cloned():
u.user_ids.append(1)
# We iterated over the same user pointer twice and appended "1"
# since `copy` returns a shallow copy of nested structures.
assert len(user.user_ids) == 2
302 def copied(self) -> Copied[Item]: 303 """Creates an iterator which copies all of its elements by value. 304 305 If you only need a copy of the item reference, Use `.cloned()` instead. 306 307 .. note:: 308 This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html) 309 on each item that is being yielded. 310 311 Example 312 ------- 313 ```py 314 @dataclass 315 class User: 316 users_ids: list[int] = [] 317 318 # An iterator which elements points to the same user. 319 user = User() 320 it = Iter((user, user)) 321 322 for u in it.copied(): 323 # A new list is created for each item. 324 u.user_ids.append(1) 325 326 # The actual list is untouched since we consumed a deep copy of it. 327 assert len(user.user_ids) == 0 328 ``` 329 """ 330 return Copied(self)
Creates an iterator which copies all of its elements by value.
If you only need a copy of the item reference, Use .cloned()
instead.
This method simply calls copy.deepcopy()
on each item that is being yielded.
Example
@dataclass
class User:
users_ids: list[int] = []
# An iterator which elements points to the same user.
user = User()
it = Iter((user, user))
for u in it.copied():
# A new list is created for each item.
u.user_ids.append(1)
# The actual list is untouched since we consumed a deep copy of it.
assert len(user.user_ids) == 0
332 def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]: 333 """Maps each item in the iterator to another type. 334 335 Example 336 ------- 337 ```py 338 iterator = Iter(["1", "2", "3"]).map(int) 339 340 for item in iterator: 341 assert isinstance(item, int) 342 ``` 343 344 Parameters 345 ---------- 346 predicate: `Callable[[Item], OtherItem]` 347 The function to map each item in the iterator to the other type. 348 """ 349 return Map(self, fn)
Maps each item in the iterator to another type.
Example
iterator = Iter(["1", "2", "3"]).map(int)
for item in iterator:
assert isinstance(item, int)
Parameters
- predicate (
Callable[[Item], OtherItem]
): The function to map each item in the iterator to the other type.
351 def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]: 352 """Filters the iterator to only yield items that match the predicate. 353 354 Example 355 ------- 356 ```py 357 places = Iter(['London', 'Paris', 'Los Angeles']) 358 for place in places.filter(lambda place: place.startswith('L')): 359 print(place) 360 361 # London 362 # Los Angeles 363 ``` 364 """ 365 return Filter(self, predicate)
Filters the iterator to only yield items that match the predicate.
Example
places = Iter(['London', 'Paris', 'Los Angeles'])
for place in places.filter(lambda place: place.startswith('L')):
print(place)
# London
# Los Angeles
367 def take(self, count: int) -> Take[Item]: 368 """Take the first number of items until the number of items 369 are yielded or the end of the iterator is exhausted. 370 371 Example 372 ------- 373 ```py 374 iterator = Iter(['c', 'x', 'y']) 375 376 for x in iterator.take(2): 377 assert x in ('c', 'x') 378 379 # <Iter(['c', 'x'])> 380 ``` 381 """ 382 return Take(self, count)
Take the first number of items until the number of items are yielded or the end of the iterator is exhausted.
Example
iterator = Iter(['c', 'x', 'y'])
for x in iterator.take(2):
assert x in ('c', 'x')
# <Iter(['c', 'x'])>
384 def skip(self, count: int) -> Skip[Item]: 385 """Skips the first number of items in the iterator. 386 387 Example 388 ------- 389 ```py 390 iterator = Iter((1, 2, 3, 4)) 391 for i in iterator.skip(2): 392 print(i) 393 394 # 3 395 # 4 396 ``` 397 """ 398 return Skip(self, count)
Skips the first number of items in the iterator.
Example
iterator = Iter((1, 2, 3, 4))
for i in iterator.skip(2):
print(i)
# 3
# 4
400 def enumerate(self, *, start: int = 0) -> Enumerate[Item]: 401 """Create a new iterator that yields a tuple of the index and item. 402 403 Example 404 ------- 405 ```py 406 iterator = Iter([1, 2, 3]) 407 for index, item in iterator.enumerate(): 408 print(index, item) 409 410 # 0 1 411 # 1 2 412 # 2 3 413 ``` 414 """ 415 return Enumerate(self, start)
Create a new iterator that yields a tuple of the index and item.
Example
iterator = Iter([1, 2, 3])
for index, item in iterator.enumerate():
print(index, item)
# 0 1
# 1 2
# 2 3
417 def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]: 418 """yields items from the iterator while predicate returns `True`. 419 420 The rest of the items are discarded as soon as the predicate returns `False` 421 422 Example 423 ------- 424 ```py 425 iterator = Iter(['a', 'ab', 'xd', 'ba']) 426 for x in iterator.take_while(lambda x: 'a' in x): 427 print(x) 428 429 # a 430 # ab 431 ``` 432 433 Parameters 434 ---------- 435 predicate: `collections.Callable[[Item], bool]` 436 The function to predicate each item in the iterator. 437 """ 438 return TakeWhile(self, f)
yields items from the iterator while predicate returns True
.
The rest of the items are discarded as soon as the predicate returns False
Example
iterator = Iter(['a', 'ab', 'xd', 'ba'])
for x in iterator.take_while(lambda x: 'a' in x):
print(x)
# a
# ab
Parameters
- predicate (
collections.Callable[[Item], bool]
): The function to predicate each item in the iterator.
440 def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]: 441 """Yields items from the iterator while predicate returns `False`. 442 443 Example 444 ------- 445 ```py 446 iterator = Iter(['a', 'ab', 'xd', 'ba']) 447 for x in iterator.drop_while(lambda x: 'a' in x): 448 print(x) 449 450 # xd 451 # ba 452 ``` 453 454 Parameters 455 ---------- 456 predicate: `collections.Callable[[Item], bool]` 457 The function to predicate each item in the iterator. 458 """ 459 return DropWhile(self, f)
Yields items from the iterator while predicate returns False
.
Example
iterator = Iter(['a', 'ab', 'xd', 'ba'])
for x in iterator.drop_while(lambda x: 'a' in x):
print(x)
# xd
# ba
Parameters
- predicate (
collections.Callable[[Item], bool]
): The function to predicate each item in the iterator.
461 def all(self, predicate: collections.Callable[[Item], bool]) -> bool: 462 """Return `True` if all items in the iterator match the predicate. 463 464 Example 465 ------- 466 ```py 467 iterator = Iter([1, 2, 3]) 468 while iterator.all(lambda item: isinstance(item, int)): 469 print("Still all integers") 470 continue 471 # Still all integers 472 ``` 473 474 Parameters 475 ---------- 476 predicate: `collections.Callable[[Item], bool]` 477 The function to test each item in the iterator. 478 """ 479 return all(predicate(item) for item in self)
Return True
if all items in the iterator match the predicate.
Example
iterator = Iter([1, 2, 3])
while iterator.all(lambda item: isinstance(item, int)):
print("Still all integers")
continue
# Still all integers
Parameters
- predicate (
collections.Callable[[Item], bool]
): The function to test each item in the iterator.
481 def any(self, predicate: collections.Callable[[Item], bool]) -> bool: 482 """`True` if any items in the iterator match the predicate. 483 484 Example 485 ------- 486 ```py 487 iterator = Iter([1, 2, 3]) 488 if iterator.any(lambda item: isinstance(item, int)): 489 print("At least one item is an int.") 490 # At least one item is an int. 491 ``` 492 493 Parameters 494 ---------- 495 predicate: `collections.Callable[[Item], bool]` 496 The function to test each item in the iterator. 497 """ 498 return any(predicate(item) for item in self)
True
if any items in the iterator match the predicate.
Example
iterator = Iter([1, 2, 3])
if iterator.any(lambda item: isinstance(item, int)):
print("At least one item is an int.")
# At least one item is an int.
Parameters
- predicate (
collections.Callable[[Item], bool]
): The function to test each item in the iterator.
500 def zip( 501 self, other: collections.Iterable[OtherItem] 502 ) -> Iterator[tuple[Item, OtherItem]]: 503 """Zips the iterator with another iterable. 504 505 Example 506 ------- 507 ```py 508 iterator = Iter([1, 2, 3]) 509 for item, other_item in iterator.zip([4, 5, 6]): 510 assert item == other_item 511 <Iter([(1, 4), (2, 5), (3, 6)])> 512 ``` 513 514 Parameters 515 ---------- 516 other: `Iter[OtherItem]` 517 The iterable to zip with. 518 519 Returns 520 ------- 521 `Iter[tuple[Item, OtherItem]]` 522 The zipped iterator. 523 524 """ 525 return Iter(zip(self.raw_parts(), other))
Zips the iterator with another iterable.
Example
iterator = Iter([1, 2, 3])
for item, other_item in iterator.zip([4, 5, 6]):
assert item == other_item
<Iter([(1, 4), (2, 5), (3, 6)])>
Parameters
- other (
Iter[OtherItem]
): The iterable to zip with.
Returns
Iter[tuple[Item, OtherItem]]
: The zipped iterator.
527 def sort( 528 self, 529 *, 530 key: collections.Callable[[Item], _typeshed.SupportsRichComparison], 531 reverse: bool = False, 532 ) -> Iterator[Item]: 533 """Sorts the iterator. 534 535 Example 536 ------- 537 ```py 538 iterator = Iter([3, 1, 6, 7]) 539 for item in iterator.sort(key=lambda item: item < 3): 540 print(item) 541 # 1 542 # 3 543 # 6 544 # 7 545 ``` 546 547 Parameters 548 ---------- 549 key: `collections.Callable[[Item], Any]` 550 The function to sort by. 551 reverse: `bool` 552 Whether to reverse the sort. 553 554 """ 555 return Iter(sorted(self.raw_parts(), key=key, reverse=reverse))
Sorts the iterator.
Example
iterator = Iter([3, 1, 6, 7])
for item in iterator.sort(key=lambda item: item < 3):
print(item)
# 1
# 3
# 6
# 7
Parameters
- key (
collections.Callable[[Item], Any]
): The function to sort by. - reverse (
bool
): Whether to reverse the sort.
557 def reversed(self) -> Iterator[Item]: 558 """Returns a new iterator that yields the items in the iterator in reverse order. 559 560 Example 561 ------- 562 ```py 563 iterator = Iter([3, 1, 6, 7]) 564 for item in iterator.reversed(): 565 print(item) 566 # 7 567 # 6 568 # 1 569 # 3 570 ``` 571 """ 572 # NOTE: In order to reverse the iterator we need to 573 # first collect it into some collection. 574 return Iter(reversed(self.collect()))
Returns a new iterator that yields the items in the iterator in reverse order.
Example
iterator = Iter([3, 1, 6, 7])
for item in iterator.reversed():
print(item)
# 7
# 6
# 1
# 3
576 def union(self, other: collections.Iterable[Item]) -> Iterator[Item]: 577 """Returns a new iterator that yields all items from both iterators. 578 579 Example 580 ------- 581 ```py 582 iterator = Iter([1, 2, 3]) 583 other = [4, 5, 6] 584 585 for item in iterator.union(other): 586 print(item) 587 # 1 588 # 2 589 # 3 590 # 4 591 # 5 592 # 6 593 ``` 594 595 Parameters 596 ---------- 597 other: `Iter[Item]` 598 The iterable to union with. 599 """ 600 return Iter(itertools.chain(self.raw_parts(), other))
Returns a new iterator that yields all items from both iterators.
Example
iterator = Iter([1, 2, 3])
other = [4, 5, 6]
for item in iterator.union(other):
print(item)
# 1
# 2
# 3
# 4
# 5
# 6
Parameters
- other (
Iter[Item]
): The iterable to union with.
602 def first(self) -> Option[Item]: 603 """Returns the first item in the iterator. 604 605 Example 606 ------- 607 ```py 608 iterator = Iter([3, 1, 6, 7]) 609 iterator.first().is_some_and(lambda x: x == 3) 610 ``` 611 """ 612 return self.take(1).next()
Returns the first item in the iterator.
Example
iterator = Iter([3, 1, 6, 7])
iterator.first().is_some_and(lambda x: x == 3)
614 def last(self) -> Option[Item]: 615 """Returns the last item in the iterator. 616 617 Example 618 ------- 619 ```py 620 iterator = Iter([3, 1, 6, 7]) 621 iterator.last().is_some_and(lambda x: x == 7) 622 ``` 623 """ 624 return self.reversed().first()
Returns the last item in the iterator.
Example
iterator = Iter([3, 1, 6, 7])
iterator.last().is_some_and(lambda x: x == 7)
626 def count(self) -> int: 627 """Return the count of elements in memory this iterator has. 628 629 Example 630 ------- 631 ```py 632 it = Iter(range(3)) 633 assert it.count() == 3 634 ``` 635 """ 636 count = 0 637 for _ in self: 638 count += 1 639 640 return count
Return the count of elements in memory this iterator has.
Example
it = Iter(range(3))
assert it.count() == 3
642 def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None: 643 """Calls `func` on each item in the iterator. 644 645 Example 646 ------- 647 ```py 648 iterator = Iter([1, 2, 3]) 649 iterator.for_each(lambda item: print(item)) 650 # 1 651 # 2 652 # 3 653 ``` 654 655 Parameters 656 ---------- 657 func: `collections.Callable[[Item], typing.Any]` 658 The function to call on each item in the iterator. 659 """ 660 for item in self: 661 func(item)
Calls func
on each item in the iterator.
Example
iterator = Iter([1, 2, 3])
iterator.for_each(lambda item: print(item))
# 1
# 2
# 3
Parameters
- func (
collections.Callable[[Item], typing.Any]
): The function to call on each item in the iterator.
663 async def async_for_each( 664 self, 665 func: collections.Callable[ 666 [Item], collections.Coroutine[None, typing.Any, OtherItem] 667 ], 668 ) -> _result.Result[collections.Sequence[OtherItem], futures.SpawnError]: 669 """Calls the async function on each item in the iterator concurrently. 670 671 Example 672 ------- 673 ```py 674 async def create_user(username: str) -> None: 675 async with aiohttp.request("POST", f'.../{username}') as r: 676 return await r.json() 677 678 async def main(): 679 users = sain.into_iter(["danny", "legalia"]) 680 results = await users.async_for_each(lambda username: create_user(username)) 681 for k, v in results.unwrap().items(): 682 ... 683 ``` 684 685 Parameters 686 ---------- 687 func: `collections.Callable[[Item], Coroutine[None, Any, Any]]` 688 The async function to call on each item in the iterator. 689 """ 690 return await futures.spawn(*(func(item) for item in self))
Calls the async function on each item in the iterator concurrently.
Example
async def create_user(username: str) -> None:
async with aiohttp.request("POST", f'.../{username}') as r:
return await r.json()
async def main():
users = sain.into_iter(["danny", "legalia"])
results = await users.async_for_each(lambda username: create_user(username))
for k, v in results.unwrap().items():
...
Parameters
- func (
collections.Callable[[Item], Coroutine[None, Any, Any]]
): The async function to call on each item in the iterator.
231def todo(message: typing.LiteralString | None = None) -> typing.NoReturn: 232 """A place holder that indicates unfinished code. 233 234 Example 235 ------- 236 ```py 237 from sain import todo 238 239 def from_json(payload: dict[str, int]) -> int: 240 # Calling this function will raise `Error`. 241 todo() 242 ``` 243 244 Parameters 245 ---------- 246 message : `str | None` 247 Multiple optional arguments to pass if the error was raised. 248 """ 249 raise Error(f"not yet implemented: {message}" if message else "not yet implemented")
A place holder that indicates unfinished code.
Example
from sain import todo
def from_json(payload: dict[str, int]) -> int:
# Calling this function will raise `Error`.
todo()
Parameters
- message (
str | None
): Multiple optional arguments to pass if the error was raised.
152def deprecated( 153 *, 154 since: typing.LiteralString | None = None, 155 removed_in: typing.LiteralString | None = None, 156 use_instead: typing.LiteralString | None = None, 157 hint: typing.LiteralString | None = None, 158) -> collections.Callable[ 159 [collections.Callable[P, U]], 160 collections.Callable[P, U], 161]: 162 """A decorator that marks a function as deprecated. 163 164 An attempt to call the object that's marked will cause a runtime warn. 165 166 Example 167 ------- 168 ```py 169 from sain import deprecated 170 171 @deprecated( 172 since = "1.0.0", 173 removed_in ="3.0.0", 174 use_instead = "UserImpl()", 175 hint = "Hint for ux." 176 ) 177 class User: 178 ... 179 180 user = User() # This will cause a warning at runtime. 181 ``` 182 183 Parameters 184 ---------- 185 since : `str` 186 The version that the function was deprecated. 187 removed_in : `str | None` 188 If provided, It will log when will the object will be removed in. 189 use_instead : `str | None` 190 If provided, This should be the alternative object name that should be used instead. 191 hint: `str` 192 An optional hint for the user. 193 """ 194 195 def _create_message(obj: typing.Any) -> str: 196 msg = f"{_obj_type(obj)} `{obj.__module__}.{obj.__name__}` is deprecated." 197 198 if since is not None: 199 msg += f" since {since}." 200 201 if removed_in: 202 msg += f" Scheduled for removal in `{removed_in}`." 203 204 if use_instead is not None: 205 msg += f" Use `{use_instead}` instead." 206 207 if hint: 208 msg += f" Hint: {hint}" 209 return msg 210 211 def decorator(func: collections.Callable[P, U]) -> collections.Callable[P, U]: 212 message = _create_message(func) 213 214 @functools.wraps(func) 215 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 216 _warn(message) 217 return func(*args, **kwargs) 218 219 # idk why pyright doesn't know the type of wrapper. 220 m = f"\n# Warning ⚠️\n{message}." 221 if wrapper.__doc__: 222 # append this message to an existing document. 223 wrapper.__doc__ = inspect.cleandoc(wrapper.__doc__) + f"{m}" 224 else: 225 wrapper.__doc__ = m 226 return wrapper 227 228 return decorator
A decorator that marks a function as deprecated.
An attempt to call the object that's marked will cause a runtime warn.
Example
from sain import deprecated
@deprecated(
since = "1.0.0",
removed_in ="3.0.0",
use_instead = "UserImpl()",
hint = "Hint for ux."
)
class User:
...
user = User() # This will cause a warning at runtime.
Parameters
- since (
str
): The version that the function was deprecated. - removed_in (
str | None
): If provided, It will log when will the object will be removed in. - use_instead (
str | None
): If provided, This should be the alternative object name that should be used instead. - hint (
str
): An optional hint for the user.
252def unimplemented( 253 *, 254 message: typing.LiteralString | None = None, 255 available_in: typing.LiteralString | None = None, 256) -> collections.Callable[ 257 [collections.Callable[P, U]], 258 collections.Callable[P, U], 259]: 260 """A decorator that marks an object as unimplemented. 261 262 An attempt to call the object that's marked will cause a runtime warn. 263 264 Example 265 ------- 266 ```py 267 from sain import unimplemented 268 269 @unimplemented("User object is not implemented yet.") 270 class User: 271 ... 272 ``` 273 274 Parameters 275 ---------- 276 message : `str | None` 277 An optional message to be displayed when the function is called. Otherwise default message will be used. 278 available_in : `str | None` 279 If provided, This will be shown as what release this object be implemented. 280 """ 281 282 def _create_message(obj: typing.Any) -> str: 283 msg = ( 284 message 285 or f"{_obj_type(obj)} `{obj.__module__}.{obj.__name__}` is not yet implemented." 286 ) # noqa: W503 287 288 if available_in: 289 msg += f" Available in `{available_in}`." 290 return msg 291 292 def decorator(obj: collections.Callable[P, U]) -> collections.Callable[P, U]: 293 message = _create_message(obj) 294 295 @functools.wraps(obj) 296 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 297 _warn(message) 298 return obj(*args, **kwargs) 299 300 # idk why pyright doesn't know the type of wrapper. 301 m = f"\n# Warning ⚠️\n{message}." 302 if wrapper.__doc__: 303 # append this message to an existing document. 304 wrapper.__doc__ = inspect.cleandoc(wrapper.__doc__) + f"{m}" 305 else: 306 wrapper.__doc__ = m 307 return wrapper 308 309 return decorator
A decorator that marks an object as unimplemented.
An attempt to call the object that's marked will cause a runtime warn.
Example
from sain import unimplemented
@unimplemented("User object is not implemented yet.")
class User:
...
Parameters
- message (
str | None
): An optional message to be displayed when the function is called. Otherwise default message will be used. - available_in (
str | None
): If provided, This will be shown as what release this object be implemented.
312def doc( 313 path: Read, 314) -> collections.Callable[ 315 [collections.Callable[P, U]], 316 collections.Callable[P, U], 317]: 318 """Set `path` to be the object's documentation. 319 320 Example 321 ------- 322 ```py 323 from sain import doc 324 from pathlib import Path 325 326 @doc(Path("../README.md")) 327 class User: 328 329 @doc("bool.html") 330 def bool_docs() -> None: 331 ... 332 ``` 333 334 Parameters 335 ---------- 336 path: `type[int] | type[str] | type[bytes] | type[PathLike[str]] | type[PathLike[bytes]]` 337 The path to read the content from. 338 """ 339 340 def decorator(f: collections.Callable[P, U]) -> collections.Callable[P, U]: 341 with open(path, "r") as file: 342 f.__doc__ = file.read() 343 344 @functools.wraps(f) 345 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 346 return f(*args, **kwargs) 347 348 return wrapper 349 350 return decorator
Set path
to be the object's documentation.
Example
from sain import doc
from pathlib import Path
@doc(Path("../README.md"))
class User:
@doc("bool.html")
def bool_docs() -> None:
...
Parameters
- path (
type[int] | type[str] | type[bytes] | type[PathLike[str]] | type[PathLike[bytes]]
): The path to read the content from.
127@typing.final 128@dataclasses.dataclass(slots=True, frozen=True, repr=False) 129class Ok(typing.Generic[T]): 130 """Contains the success value of `Result[T, ...]`.""" 131 132 _inner: T 133 134 ############################### 135 # * Boolean operations. * # 136 ############################### 137 138 def is_ok(self) -> typing.Literal[True]: 139 """Returns `True` if the contained value is `Ok` and `False` if it an `Err`. 140 141 Example 142 ------- 143 ```py 144 value: Result[str, None] = Ok("value") 145 assert value.is_ok() == True 146 ``` 147 """ 148 return True 149 150 def is_ok_and(self, f: F[T, bool]) -> bool: 151 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 152 153 Example 154 ------- 155 ```py 156 value: Result[str, None] = Ok("value") 157 158 assert value.is_ok_and(lambda inner: inner == "value") 159 # True 160 ``` 161 """ 162 return self.is_ok() and f(self._inner) 163 164 # These are never truthy in an `Ok` instance. 165 def is_err(self) -> typing.Literal[False]: 166 """Returns `True` if the contained value is `Err`. 167 168 Example 169 ------- 170 ```py 171 value: Result[str, None] = Ok("value") 172 173 assert value.is_err() == False 174 ``` 175 """ 176 return False 177 178 def is_err_and(self, f: F[T, bool]) -> typing.Literal[False]: 179 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 180 181 Example 182 ------- 183 ```py 184 value: Result[str, None] = Ok("value") 185 186 assert value.is_err_and(lambda inner: inner == "value") 187 # False 188 ``` 189 """ 190 return False 191 192 ################### 193 # * Extractors. * # 194 ################### 195 196 def expect(self, message: str, /) -> T: 197 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 198 if it was `Err` with `message` passed to it. 199 200 Example 201 ------- 202 ```py 203 ok: Result[str, None] = Ok("owo") 204 ok.expect("err") # owo 205 206 err: Result[str, None] = Err(None) 207 err.expect("err") # RuntimeError("err") 208 ``` 209 """ 210 return self._inner 211 212 def unwrap(self) -> T: 213 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 214 215 Example 216 ------- 217 ```py 218 ok: Result[str, None] = Ok("owo") 219 ok.unwrap() # owo 220 221 err: Result[str, None] = Err(None) 222 err.unwrap() # RuntimeError 223 ``` 224 """ 225 return self._inner 226 227 def unwrap_or(self, default: T, /) -> T: 228 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 229 230 Example 231 ------- 232 ```py 233 ok: Result[str, None] = Ok("OwO") 234 ok.unwrap_or("uwu") # OwO 235 236 err: Result[str, None] = Err(None) 237 err.unwrap_or("uwu") # uwu 238 ``` 239 """ 240 return self._inner 241 242 def unwrap_or_else(self, f: F[E, T]) -> T: 243 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 244 245 Example 246 ------- 247 ```py 248 ok: Result[int, str] = Ok(4) 249 ok.unwrap_or_else(lambda e: 0) # 4 250 251 err: Result[int, str] = Err("word") 252 err.unwrap_or_else(lambda e: len(e)) # 4 253 ``` 254 """ 255 return self._inner 256 257 def unwrap_err(self) -> typing.NoReturn: 258 """Return the contained `Err` value, Raising if it was `Ok`. 259 260 Example 261 ------- 262 ```py 263 ok: Result[str, None] = Ok("buh") 264 ok.unwrap_err() # RuntimeError 265 266 err: Result[str, None] = Err(None) 267 err.unwrap_err() == None 268 # True 269 ``` 270 """ 271 raise RuntimeError(f"Called `unwrap_err` on an `Ok` variant: {self._inner!r}") 272 273 ############################ 274 # * Conversion adapters. * # 275 ############################ 276 277 def ok(self) -> Option[T]: 278 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 279 280 Example 281 ------- 282 ```py 283 value: Result[str, None] = Ok("buh") 284 value.ok().is_some() # True 285 286 value: Result[str, int] = Err(0) 287 value.ok().is_none() # True 288 ``` 289 """ 290 return _option.Some(self._inner) 291 292 def err(self) -> Option[None]: 293 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 294 295 Example 296 ------- 297 ```py 298 value: Result[str, None] = Ok("buh") 299 value.err().is_none() # True 300 301 value: Result[str, int] = Err(0) 302 value.err().is_some() # True 303 ``` 304 """ 305 return _option.NOTHING 306 307 def inspect(self, f: F[T, typing.Any]) -> None: 308 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 309 310 Example 311 ------- 312 ```py 313 def sink(value: str) -> None: 314 # do something with value 315 print("Called " + value) 316 317 x: Result[str, None] = Ok("ok") 318 x.inspect(sink) # "Called ok" 319 320 x: Result[str, str] = Err("err") 321 x.inspect(sink) # None 322 ``` 323 """ 324 f(self._inner) 325 326 def inspect_err(self, f: F[E, typing.Any]) -> None: 327 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 328 329 Example 330 ------- 331 ```py 332 def sink(value: str) -> None: 333 # do something with value 334 print("Called " + value) 335 336 x: Result[str, None] = Ok("ok") 337 x.inspect_err(sink) # None 338 339 x: Result[str, str] = Err("err") 340 x.inspect_err(sink) # Called err 341 ``` 342 """ 343 344 def map(self, f: F[T, U], /) -> Ok[U]: 345 """Map `Ok[T]` to `Ok[U]` by applying a function to `T`, Leaving `Err` untouched. 346 347 Example 348 ------- 349 ```py 350 ok: Result[str, int] = Ok("1") 351 ok.map(lambda c: int(c) + 1) # Ok(2) 352 353 err: Result[str, int] = Err(0) 354 err.map(str.upper) # Err(0) 355 ``` 356 """ 357 return Ok(f(self._inner)) 358 359 def map_or(self, f: F[T, U], default: U, /) -> U: 360 """Returns the provided default value if `Err`, 361 362 Otherwise extracts the `Ok` value and maps it to `f()` 363 364 Example 365 ------- 366 ```py 367 x: Result[str, str] = Ok("foo") 368 assert x.map_or(lambda c: len(c), 42) == 3 369 370 x: Result[str, str] = Err("bar") 371 assert x.map_or(lambda c: len(c), 42) == 42 372 ``` 373 """ 374 return f(self._inner) 375 376 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 377 """Returns the provided default value from a function if `Err`, 378 379 Otherwise extracts the `Ok` value and maps it to `f()` 380 381 Example 382 ------- 383 ```py 384 x: Result[str, str] = Ok("four") 385 assert x.map_or_else( 386 lambda c: 2 * len(c), 387 lambda err: len(err) 388 ) == 8 389 390 x: Result[str, str] = Err("bar") 391 assert x.map_or_else( 392 lambda c: 2 * len(c), 393 lambda err: len(err) 394 ) == 3 395 ``` 396 """ 397 return f(self._inner) 398 399 def map_err(self, f: F[E, U], /) -> Self: 400 """Maps a `Result[T, E]` to `Result[T, U]`, leaving `OK[T]` untouched. 401 402 Example 403 ------- 404 ```py 405 x: Result[str, int] = Ok("blue") 406 x.map_err(lambda err: err + 1) # Ok("blue") 407 408 x: Result[str, int] = Err(5) 409 x.map_err(float) # Err(5.0) 410 ``` 411 """ 412 return self 413 414 ############################## 415 # * Iterator constructors. * # 416 ############################## 417 418 def iter(self) -> _iter.Iterator[T]: 419 """Return an iterator over the contained value. 420 421 If it was `Ok[T]` then it will return `Iter[T]`, otherwise it will return `Iter[Never]`. 422 423 Example 424 ------- 425 ```py 426 c: Result[str, int] = Ok("blue") 427 c.iter().next() == Some("blue") 428 429 c: Result[str, int] = Err(0) 430 c.iter().next() == Some(None) 431 ``` 432 """ 433 return self.__iter__() 434 435 def __iter__(self) -> _iter.Iterator[T]: 436 return _iter.once(self._inner) 437 438 ################# 439 # * Overloads * # 440 ################# 441 442 def __repr__(self) -> str: 443 return f"Ok({self._inner})" 444 445 def __or__(self, other: T) -> T: 446 return self._inner 447 448 def __invert__(self) -> T: 449 return self._inner
Contains the success value of Result[T, ...]
.
150 def is_ok_and(self, f: F[T, bool]) -> bool: 151 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 152 153 Example 154 ------- 155 ```py 156 value: Result[str, None] = Ok("value") 157 158 assert value.is_ok_and(lambda inner: inner == "value") 159 # True 160 ``` 161 """ 162 return self.is_ok() and f(self._inner)
Returns True
if the contained value is Ok
and f()
returns True..
Example
value: Result[str, None] = Ok("value")
assert value.is_ok_and(lambda inner: inner == "value")
# True
165 def is_err(self) -> typing.Literal[False]: 166 """Returns `True` if the contained value is `Err`. 167 168 Example 169 ------- 170 ```py 171 value: Result[str, None] = Ok("value") 172 173 assert value.is_err() == False 174 ``` 175 """ 176 return False
Returns True
if the contained value is Err
.
Example
value: Result[str, None] = Ok("value")
assert value.is_err() == False
178 def is_err_and(self, f: F[T, bool]) -> typing.Literal[False]: 179 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 180 181 Example 182 ------- 183 ```py 184 value: Result[str, None] = Ok("value") 185 186 assert value.is_err_and(lambda inner: inner == "value") 187 # False 188 ``` 189 """ 190 return False
Returns True
if the contained value is Ok
and f()
returns True..
Example
value: Result[str, None] = Ok("value")
assert value.is_err_and(lambda inner: inner == "value")
# False
196 def expect(self, message: str, /) -> T: 197 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 198 if it was `Err` with `message` passed to it. 199 200 Example 201 ------- 202 ```py 203 ok: Result[str, None] = Ok("owo") 204 ok.expect("err") # owo 205 206 err: Result[str, None] = Err(None) 207 err.expect("err") # RuntimeError("err") 208 ``` 209 """ 210 return self._inner
212 def unwrap(self) -> T: 213 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 214 215 Example 216 ------- 217 ```py 218 ok: Result[str, None] = Ok("owo") 219 ok.unwrap() # owo 220 221 err: Result[str, None] = Err(None) 222 err.unwrap() # RuntimeError 223 ``` 224 """ 225 return self._inner
227 def unwrap_or(self, default: T, /) -> T: 228 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 229 230 Example 231 ------- 232 ```py 233 ok: Result[str, None] = Ok("OwO") 234 ok.unwrap_or("uwu") # OwO 235 236 err: Result[str, None] = Err(None) 237 err.unwrap_or("uwu") # uwu 238 ``` 239 """ 240 return self._inner
242 def unwrap_or_else(self, f: F[E, T]) -> T: 243 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 244 245 Example 246 ------- 247 ```py 248 ok: Result[int, str] = Ok(4) 249 ok.unwrap_or_else(lambda e: 0) # 4 250 251 err: Result[int, str] = Err("word") 252 err.unwrap_or_else(lambda e: len(e)) # 4 253 ``` 254 """ 255 return self._inner
257 def unwrap_err(self) -> typing.NoReturn: 258 """Return the contained `Err` value, Raising if it was `Ok`. 259 260 Example 261 ------- 262 ```py 263 ok: Result[str, None] = Ok("buh") 264 ok.unwrap_err() # RuntimeError 265 266 err: Result[str, None] = Err(None) 267 err.unwrap_err() == None 268 # True 269 ``` 270 """ 271 raise RuntimeError(f"Called `unwrap_err` on an `Ok` variant: {self._inner!r}")
277 def ok(self) -> Option[T]: 278 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 279 280 Example 281 ------- 282 ```py 283 value: Result[str, None] = Ok("buh") 284 value.ok().is_some() # True 285 286 value: Result[str, int] = Err(0) 287 value.ok().is_none() # True 288 ``` 289 """ 290 return _option.Some(self._inner)
292 def err(self) -> Option[None]: 293 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 294 295 Example 296 ------- 297 ```py 298 value: Result[str, None] = Ok("buh") 299 value.err().is_none() # True 300 301 value: Result[str, int] = Err(0) 302 value.err().is_some() # True 303 ``` 304 """ 305 return _option.NOTHING
307 def inspect(self, f: F[T, typing.Any]) -> None: 308 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 309 310 Example 311 ------- 312 ```py 313 def sink(value: str) -> None: 314 # do something with value 315 print("Called " + value) 316 317 x: Result[str, None] = Ok("ok") 318 x.inspect(sink) # "Called ok" 319 320 x: Result[str, str] = Err("err") 321 x.inspect(sink) # None 322 ``` 323 """ 324 f(self._inner)
326 def inspect_err(self, f: F[E, typing.Any]) -> None: 327 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 328 329 Example 330 ------- 331 ```py 332 def sink(value: str) -> None: 333 # do something with value 334 print("Called " + value) 335 336 x: Result[str, None] = Ok("ok") 337 x.inspect_err(sink) # None 338 339 x: Result[str, str] = Err("err") 340 x.inspect_err(sink) # Called err 341 ``` 342 """
344 def map(self, f: F[T, U], /) -> Ok[U]: 345 """Map `Ok[T]` to `Ok[U]` by applying a function to `T`, Leaving `Err` untouched. 346 347 Example 348 ------- 349 ```py 350 ok: Result[str, int] = Ok("1") 351 ok.map(lambda c: int(c) + 1) # Ok(2) 352 353 err: Result[str, int] = Err(0) 354 err.map(str.upper) # Err(0) 355 ``` 356 """ 357 return Ok(f(self._inner))
Map Ok[T]
to Ok[U]
by applying a function to T
, Leaving Err
untouched.
Example
ok: Result[str, int] = Ok("1")
ok.map(lambda c: int(c) + 1) # Ok(2)
err: Result[str, int] = Err(0)
err.map(str.upper) # Err(0)
359 def map_or(self, f: F[T, U], default: U, /) -> U: 360 """Returns the provided default value if `Err`, 361 362 Otherwise extracts the `Ok` value and maps it to `f()` 363 364 Example 365 ------- 366 ```py 367 x: Result[str, str] = Ok("foo") 368 assert x.map_or(lambda c: len(c), 42) == 3 369 370 x: Result[str, str] = Err("bar") 371 assert x.map_or(lambda c: len(c), 42) == 42 372 ``` 373 """ 374 return f(self._inner)
376 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 377 """Returns the provided default value from a function if `Err`, 378 379 Otherwise extracts the `Ok` value and maps it to `f()` 380 381 Example 382 ------- 383 ```py 384 x: Result[str, str] = Ok("four") 385 assert x.map_or_else( 386 lambda c: 2 * len(c), 387 lambda err: len(err) 388 ) == 8 389 390 x: Result[str, str] = Err("bar") 391 assert x.map_or_else( 392 lambda c: 2 * len(c), 393 lambda err: len(err) 394 ) == 3 395 ``` 396 """ 397 return f(self._inner)
Returns the provided default value from a function if Err
,
Otherwise extracts the Ok
value and maps it to f()
Example
x: Result[str, str] = Ok("four")
assert x.map_or_else(
lambda c: 2 * len(c),
lambda err: len(err)
) == 8
x: Result[str, str] = Err("bar")
assert x.map_or_else(
lambda c: 2 * len(c),
lambda err: len(err)
) == 3
399 def map_err(self, f: F[E, U], /) -> Self: 400 """Maps a `Result[T, E]` to `Result[T, U]`, leaving `OK[T]` untouched. 401 402 Example 403 ------- 404 ```py 405 x: Result[str, int] = Ok("blue") 406 x.map_err(lambda err: err + 1) # Ok("blue") 407 408 x: Result[str, int] = Err(5) 409 x.map_err(float) # Err(5.0) 410 ``` 411 """ 412 return self
Maps a Result[T, E]
to Result[T, U]
, leaving OK[T]
untouched.
Example
x: Result[str, int] = Ok("blue")
x.map_err(lambda err: err + 1) # Ok("blue")
x: Result[str, int] = Err(5)
x.map_err(float) # Err(5.0)
418 def iter(self) -> _iter.Iterator[T]: 419 """Return an iterator over the contained value. 420 421 If it was `Ok[T]` then it will return `Iter[T]`, otherwise it will return `Iter[Never]`. 422 423 Example 424 ------- 425 ```py 426 c: Result[str, int] = Ok("blue") 427 c.iter().next() == Some("blue") 428 429 c: Result[str, int] = Err(0) 430 c.iter().next() == Some(None) 431 ``` 432 """ 433 return self.__iter__()
Return an iterator over the contained value.
If it was Ok[T]
then it will return Iter[T]
, otherwise it will return Iter[Never]
.
Example
c: Result[str, int] = Ok("blue")
c.iter().next() == Some("blue")
c: Result[str, int] = Err(0)
c.iter().next() == Some(None)
452@typing.final 453@dataclasses.dataclass(slots=True, frozen=True, repr=False) 454class Err(typing.Generic[E]): 455 """Contains the error value of `Result[..., E]`.""" 456 457 _inner: E 458 459 ################################ 460 # * Boolean operations. * # 461 ################################ 462 463 def is_ok(self) -> typing.Literal[False]: 464 """Returns `True` if the contained value is `Ok` and `False` if it an `Err`. 465 466 Example 467 ------- 468 ```py 469 value: Result[str, None] = Err(None) 470 471 assert value.is_ok() == False 472 ``` 473 """ 474 return False 475 476 def is_ok_and(self, f: F[E, bool]) -> typing.Literal[False]: 477 """Returns `True` if the contained value is `Ok` and `f()` returns True. 478 479 Example 480 ------- 481 ```py 482 value: Result[str, None] = Err(None) 483 484 assert value.is_ok_and(lambda inner: inner == "value") 485 # False 486 ``` 487 """ 488 return False 489 490 # These are never truthy in an `Ok` instance. 491 def is_err(self) -> typing.Literal[True]: 492 """Returns `True` if the contained value is `Err`. 493 494 Example 495 ------- 496 ```py 497 value: Result[str, None] = Err(None) 498 499 assert value.is_err() == True 500 ``` 501 """ 502 return True 503 504 def is_err_and(self, f: F[E, bool]) -> bool: 505 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 506 507 Example 508 ------- 509 ```py 510 value: Result[str, None] = Err(None) 511 512 assert value.is_err_and(lambda err: err is None) 513 # True 514 ``` 515 """ 516 return self.is_err() and f(self._inner) 517 518 ################### 519 # * Extractors. * # 520 ################### 521 522 def expect(self, msg: str) -> typing.NoReturn: 523 raise RuntimeError(msg) from None 524 525 def expect_err(self) -> E: 526 return self._inner 527 528 def unwrap(self) -> typing.NoReturn: 529 raise RuntimeError( 530 f"Called `unwrap()` on an `Err` variant: {self._inner!r}" 531 ) from None 532 533 def unwrap_or(self, __default: T, /) -> T: 534 return __default 535 536 def unwrap_or_else(self, f: F[E, T]) -> T: 537 return f(self._inner) 538 539 def unwrap_err(self) -> E: 540 return self._inner 541 542 ############################ 543 # * Conversion adapters. * # 544 ############################ 545 546 def inspect(self, f: F[T, typing.Any]) -> None: 547 return None 548 549 def inspect_err(self, f: F[E, typing.Any]) -> None: 550 f(self._inner) 551 552 def ok(self) -> Option[None]: 553 return _option.NOTHING 554 555 def err(self) -> Option[E]: 556 return _option.Some(self._inner) 557 558 def map(self, f: F[E, U]) -> Self: 559 return self 560 561 def map_or(self, f: F[E, U], default: U, /) -> U: 562 return default 563 564 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 565 return default(self._inner) 566 567 def map_err(self, f: F[E, U]) -> Err[U]: 568 return Err(f(self._inner)) 569 570 ############################## 571 # * Iterator constructors. * # 572 ############################## 573 574 def iter(self) -> _iter.Iterator[Never]: 575 return self.__iter__() 576 577 def __iter__(self) -> _iter.Iterator[Never]: 578 return _iter.empty() 579 580 ################# 581 # * Overloads * # 582 ################# 583 584 def __repr__(self) -> str: 585 return f"Err({self._inner})" 586 587 def __or__(self, other: T) -> T: 588 return other 589 590 def __invert__(self) -> typing.NoReturn: 591 self.unwrap()
Contains the error value of Result[..., E]
.
476 def is_ok_and(self, f: F[E, bool]) -> typing.Literal[False]: 477 """Returns `True` if the contained value is `Ok` and `f()` returns True. 478 479 Example 480 ------- 481 ```py 482 value: Result[str, None] = Err(None) 483 484 assert value.is_ok_and(lambda inner: inner == "value") 485 # False 486 ``` 487 """ 488 return False
Returns True
if the contained value is Ok
and f()
returns True.
Example
value: Result[str, None] = Err(None)
assert value.is_ok_and(lambda inner: inner == "value")
# False
491 def is_err(self) -> typing.Literal[True]: 492 """Returns `True` if the contained value is `Err`. 493 494 Example 495 ------- 496 ```py 497 value: Result[str, None] = Err(None) 498 499 assert value.is_err() == True 500 ``` 501 """ 502 return True
Returns True
if the contained value is Err
.
Example
value: Result[str, None] = Err(None)
assert value.is_err() == True
504 def is_err_and(self, f: F[E, bool]) -> bool: 505 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 506 507 Example 508 ------- 509 ```py 510 value: Result[str, None] = Err(None) 511 512 assert value.is_err_and(lambda err: err is None) 513 # True 514 ``` 515 """ 516 return self.is_err() and f(self._inner)
Returns True
if the contained value is Ok
and f()
returns True..
Example
value: Result[str, None] = Err(None)
assert value.is_err_and(lambda err: err is None)
# True
70@typing.final 71class Vec(typing.Generic[T]): 72 """A contiguous growable alternative to builtin `list` with extra functionalities. 73 74 The layout of `Vec` is almost the same as `list`. 75 76 When initializing a vec, it will not build the underlying list until the first element gets pushed. 77 Which saves a little bit of memory. 78 79 Example 80 ------- 81 ```py 82 names = Vec() 83 names.push('foo') 84 names.push('bar') 85 86 print(names) # ['foo', 'bar'] 87 assert names.len() == 2 88 ``` 89 90 Constructing 91 ------------ 92 * `Vec()`: Create an unallocated vec, Which means the underlying list will be `None` until you start pushing into it 93 * `Vec(other_list)`: Create a vec which points to `other_list` 94 * `Vec((1, 2, 3))`: Create a vec with `[1, 2, 3]` pre-allocated 95 * `Vec.with_capacity(5)`: Create a vec that can hold up to 5 elements 96 * `from_args(1, 2, 3)`: Create a vec from arguments. This is not a classmethod 97 98 Iterating over `Vec` 99 ------------------- 100 There're two ways to iterate over a `Vec`. The first is to normally use `for` loop. 101 102 ```py 103 for i in names: 104 print(i) 105 106 # foo 107 # bar 108 ``` 109 110 The second is to use `Vec.iter`, which yields all items in this `Vec` from start to end. 111 Then the iterator gets exhausted as usual, See `sain.Iterator`. 112 113 ```py 114 iterator = names.iter() 115 for name in iterator.map(str.upper): 116 print(name) 117 118 # FOO 119 # BAR 120 121 # No more items, The actual vec is left unchanged. 122 assert iterator.next().is_none() 123 ``` 124 125 ## Comparison operators 126 Comparing different collections with `Vec` have a cost. Depending on what you're comparing it wit. 127 128 Any iterable that is not a `list` or `Vec` that is used to compare with will get copied into a `list`, 129 So be careful what you compare a `Vec` with. 130 131 ```py 132 vec = Vec((1,2,3)) 133 # zero-cost 134 vec == [1, 2, 3] 135 # Copies {1, 2, 3} -> [1, 2, 3] which can cost. 136 vec == {1, 2, 3} 137 ``` 138 139 Zero-Copy 140 --------- 141 A vec that gets initialized from a `list` will *point* to it and doesn't copy it. 142 So any element that gets appended to the collection will also get pushed into the vec. 143 144 ```py 145 cells: list[str] = [] 146 vec = Vec(cells) # This DOES NOT copy the `cells`. 147 148 cells.append("foo") 149 vec[0] == "foo" # True 150 ``` 151 152 The opposite of the above is to initialize the vec from either 153 an iterable or args, or copy the list. 154 155 ```py 156 # Creates a new vec and extend it with the elements. 157 from_args = vec.from_args("foo", "bar") 158 159 # inlined from another iterable. 160 from_iter = Vec(["foo", "bar"]) 161 162 # Copy the list into a vec. 163 vec = Vec(cells[:]) 164 cells.append("bar") 165 166 vec[2] # IndexError: "bar" doesn't exist in vec. 167 ``` 168 """ 169 170 __slots__ = ("_ptr", "_capacity") 171 172 @typing.overload 173 def __init__(self) -> None: ... 174 175 @typing.overload 176 def __init__(self, iterable: collections.Iterable[T]) -> None: ... 177 178 def __init__(self, iterable: collections.Iterable[T] | None = None) -> None: 179 # We won't allocate to build the list here. 180 # Instead, On first push or fist indexed set 181 # we allocate if it was None. 182 if isinstance(iterable, list): 183 # Calling `list()` on another list will copy it, So instead we just point to it. 184 self._ptr = iterable 185 elif isinstance(iterable, Vec): 186 self._ptr = iterable._ptr 187 # any other iterable that ain't a list needs to get copied into a new list. 188 else: 189 self._ptr: list[T] | None = list(iterable) if iterable else None 190 191 self._capacity: int | None = None 192 193 @classmethod 194 def with_capacity(cls, capacity: int) -> Self: 195 """Create a new `Vec` with at least the specified capacity. 196 This vec will be able to hold `capacity` elements without pushing further. 197 198 Check out `Vec.push_within_capacity` as well. 199 200 Example 201 ------- 202 ```py 203 vec = Vec.with_capacity(3) 204 assert vec.len() == 0 and vec.capacity() >= 3 205 206 vec.push(1) 207 vec.push(2) 208 vec.push(3) 209 print(vec.len()) # 3 210 211 # This won't push. 212 vec.push(4) 213 ``` 214 """ 215 v = cls() 216 v._capacity = capacity 217 return v 218 219 def as_ref(self) -> collections.Collection[T]: 220 """Return an immutable view over this vector elements. 221 222 Example 223 ------- 224 ```py 225 vec = Vec((1,2,3)) 226 227 assert vec.as_ref() == (1,2,3) 228 ``` 229 """ 230 return tuple(self) 231 232 def len(self) -> int: 233 """Return the number of elements in this vector. 234 235 Example 236 ------- 237 ```py 238 vec = Vec((1,2,3)) 239 240 assert vec.len() == 3 241 ``` 242 """ 243 return self.__len__() 244 245 def capacity(self) -> int: 246 """Return the capacity of this vector if set. 0 if not . 247 248 Example 249 ------- 250 ```py 251 vec_with_cap = Vec.with_capacity(3) 252 assert vec_with_cap.capacity().unwrap() == 3 253 254 vec = Vec([1, 2, 3]) 255 assert vec.capacity().is_none() 256 ``` 257 """ 258 return 0 if self._capacity is None else self._capacity 259 260 def iter(self) -> _iter.Iterator[T]: 261 """Return an iterator over this vector elements. 262 263 Example 264 ------- 265 ```py 266 vec = Vec((1,2,3)) 267 iterator = vec.iter() 268 269 # Map each element to a str 270 for element in iterator.map(str): 271 print(element) 272 ``` 273 """ 274 return _iter.Iter(self) 275 276 def is_empty(self) -> bool: 277 """Returns true if the vector contains no elements.""" 278 return not self._ptr 279 280 def split_off(self, at: int) -> Vec[T]: 281 """Split the vector off at the specified position. 282 283 if this vec is empty, `self` is returned unchanged. 284 285 Example 286 ------- 287 ```py 288 vec = Vec((1, 2, 3)) 289 split = vec.split_off(1) 290 291 print(split, vec) # [1], [2, 3] 292 ``` 293 294 Raises 295 ------ 296 `RuntimeError` 297 This method will raise if `at` > `len(self)` 298 """ 299 if at > self.len(): 300 raise RuntimeError( 301 f"Index `at` ({at}) should be <= than len of vector ({self.len()}) " 302 ) from None 303 304 # Either the list is empty or uninit. 305 if not self._ptr: 306 return self 307 308 split = self[at : self.len()] 309 self._ptr = self._ptr[0:at] 310 return split 311 312 def split_first(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 313 """Split the first and rest elements of the vector, If empty, `Some[None]` is returned. 314 315 Example 316 ------- 317 ```py 318 vec = Vec([1, 2, 3]) 319 split = vec.split_first() 320 assert split == Some((1, [2, 3])) 321 322 vec: Vec[int] = Vec() 323 split = vec.split_first() 324 assert split == Some(None) 325 ``` 326 """ 327 if not self._ptr: 328 return _option.nothing_unchecked() 329 330 first, *rest = self._ptr 331 return _option.Some((first, rest)) 332 333 def first(self) -> _option.Option[T]: 334 """Get the first element in this vec, returning `Some[None]` if there's none. 335 336 Example 337 ------- 338 ```py 339 vec = Vec((1,2,3)) 340 first = vec.first() 341 assert ~first == 1 342 ``` 343 """ 344 return self.get(0) 345 346 def truncate(self, size: int) -> None: 347 """Shortens the vec, keeping the first `size` elements and dropping the rest. 348 349 Example 350 ------- 351 ```py 352 vec = Vec([1,2,3]) 353 vec.truncate(1) 354 assert vec == [1] 355 ``` 356 """ 357 if not self._ptr: 358 return 359 360 self._ptr = self._ptr[:size] 361 362 def retain(self, f: collections.Callable[[T], bool]) -> None: 363 """Remove elements from this vec while `f()` returns `True`. 364 365 In other words, filter this vector based on `f()`. 366 367 Example 368 ------- 369 ```py 370 vec = Vec([1, 2, 3]) 371 vec.retain(lambda elem: elem > 1) 372 373 assert vec == [2, 3] 374 ``` 375 """ 376 if not self._ptr: 377 return 378 379 self._ptr = [e for e in self._ptr if f(e)] 380 381 def swap_remove(self, item: T) -> T: 382 """Remove the first appearance of `item` from this vector and return it. 383 384 Raises 385 ------ 386 * `ValueError`: if `item` is not in this vector. 387 * `MemoryError`: if this vector hasn't allocated, Aka nothing has been pushed to it. 388 389 Example 390 ------- 391 ```py 392 vec = Vector(('a', 'b', 'c')) 393 element = vec.remove('a') 394 assert vec == ['b', 'c'] and element == 'a' 395 ``` 396 """ 397 if self._ptr is None: 398 raise MemoryError("Vec is unallocated.") from None 399 400 try: 401 i = next(i for i in self._ptr if i == item) 402 except StopIteration: 403 raise ValueError(f"Item `{item}` not in list") from None 404 405 self.remove(i) 406 return i 407 408 def push(self, item: T) -> None: 409 """Push an element at the end of the vector. 410 411 Example 412 ------- 413 ```py 414 vec = Vec() 415 vec.push(1) 416 417 assert vec == [1] 418 ``` 419 """ 420 if self._capacity is not None: 421 self.push_within_capacity(item) 422 return 423 424 if self._ptr is None: 425 self._ptr = [] 426 427 self._ptr.append(item) 428 429 def push_within_capacity(self, x: T) -> Result[None, T]: 430 """Appends an element if there is sufficient spare capacity, otherwise an error is returned with the element. 431 432 Example 433 ------- 434 ```py 435 vec: Vec[int] = Vec.with_capacity(3) 436 for i in range(3): 437 match vec.push_within_capacity(i): 438 case Ok(_): 439 print("All good.") 440 case Err(elem): 441 print("Reached max cap :< cant push", elem) 442 ``` 443 444 Or you can also just call `Vec.push` and it will push within capacity if `Vec.capacity()` is not `None`. 445 ```py 446 vec: Vec[int] = Vec.with_capacity(3) 447 448 vec.extend((1, 2, 3)) 449 vec.push(4) 450 451 assert vec.len() == 3 452 ``` 453 """ 454 if self._ptr is None: 455 self._ptr = [] 456 457 if self._capacity is not None and self._capacity <= self.len(): 458 return _result.Err(x) 459 460 self._ptr.append(x) 461 return _result.Ok(None) 462 463 def reserve(self, additional: int) -> None: 464 """Reserves capacity for at least additional more elements to be inserted in the given Vec<T>. 465 466 Example 467 ------- 468 ```py 469 vec = Vec.with_capacity(3) 470 is_vip = random.choice((True, False)) 471 472 for i in range(4): 473 match vec.push_within_capacity(i): 474 case Ok(_): 475 print("All good") 476 case Err(person): 477 # If the person is a VIP, then reserve for one more. 478 if is_vip: 479 vec.reserve(1) 480 continue 481 482 # is_vip was false. 483 print("Can't reserve for non-VIP members...", person) 484 break 485 ``` 486 """ 487 if self._capacity is not None: 488 self._capacity += additional 489 490 ########################## 491 # * Builtin Operations * 492 ########################## 493 494 # For people how are used to calling list.append 495 append = push 496 """An alias for `Vec.push` method.""" 497 498 def get(self, index: int) -> _option.Option[T]: 499 """Get the item at the given index, or `Some[None]` if its out of bounds. 500 501 Example 502 ------- 503 ```py 504 vec = Vec((1, 2, 3)) 505 vec.get(0) == Some(1) 506 vec.get(3) == Some(None) 507 ``` 508 """ 509 try: 510 return _option.Some(self.__getitem__(index)) 511 except IndexError: 512 return _option.nothing_unchecked() 513 514 def insert(self, index: int, value: T) -> None: 515 """Insert an element at the position `index`. 516 517 Example 518 -------- 519 ```py 520 vec = Vec((2, 3)) 521 vec.insert(0, 1) 522 assert vec == [1, 2, 3] 523 ``` 524 """ 525 self.__setitem__(index, value) 526 527 def pop(self, index: int = -1) -> _option.Option[T]: 528 """Removes the last element from a vector and returns it, or `sain.Some(None)` if it is empty. 529 530 Example 531 ------- 532 ```py 533 vec = Vec((1, 2, 3)) 534 assert vec.pop() == Some(3) 535 assert vec == [1, 2] 536 ``` 537 """ 538 if not self._ptr: 539 return _option.nothing_unchecked() 540 541 return _option.Some(self._ptr.pop(index)) 542 543 def remove(self, item: T) -> None: 544 """Remove `item` from this vector. 545 546 Example 547 ------- 548 ```py 549 vec = Vector(('a', 'b', 'c')) 550 vec.remove('a') 551 assert vec == ['b', 'c'] 552 ``` 553 """ 554 if not self._ptr: 555 return 556 557 self._ptr.remove(item) 558 559 def extend(self, iterable: collections.Iterable[T]) -> None: 560 """Extend this vector from another iterable. 561 562 Example 563 ------- 564 ```py 565 vec = Vec((1, 2, 3)) 566 vec.extend((4, 5, 6)) 567 568 assert vec == [1, 2, 3, 4, 5, 6] 569 ``` 570 """ 571 if self._ptr is None: 572 self._ptr = [] 573 574 self._ptr.extend(iterable) 575 576 def copy(self) -> Vec[T]: 577 """Create a vector that copies all of its elements and place it into the new one. 578 579 If the vector hasn't been allocated, `self` is returned. 580 581 Example 582 ------- 583 ```py 584 original = Vec((1,2,3)) 585 copy = original.copy() 586 copy.push(4) 587 588 print(original) # [1, 2, 3] 589 ``` 590 """ 591 return Vec(self._ptr[:]) if self._ptr is not None else self 592 593 def clear(self) -> None: 594 """Clear all elements of this vector. 595 596 Example 597 ------- 598 ```py 599 vec = Vec((1,2,3)) 600 vec.clear() 601 assert vec.len() == 0 602 ``` 603 """ 604 if not self._ptr: 605 return 606 607 self._ptr.clear() 608 609 def sort( 610 self, 611 *, 612 key: collections.Callable[[T], SupportsRichComparison] | None = None, 613 reverse: bool = False, 614 ) -> None: 615 """This method sorts the list in place, using only < comparisons between items. 616 617 Example 618 ------- 619 ```py 620 vec = Vec((2, 1, 3)) 621 vec.sort() 622 assert vec == [1, 2, 3] 623 ``` 624 """ 625 if not self._ptr: 626 return 627 628 # key can be `None` here just fine, idk why pyright is complaining. 629 self._ptr.sort(key=key, reverse=reverse) # pyright: ignore 630 631 def index( 632 self, item: T, start: typing.SupportsIndex = 0, end: int = _sys.maxsize 633 ) -> int: 634 # << Official documentation >> 635 """Return zero-based index in the vec of the first item whose value is equal to `item`. 636 Raises a ValueError if there is no such item. 637 638 Example 639 ------- 640 ```py 641 vec = Vec((1, 2, 3)) 642 assert vec.index(2) == 1 643 ``` 644 """ 645 if self._ptr is None: 646 raise ValueError from None 647 648 return self._ptr.index(item, start, end) 649 650 def count(self, item: T) -> int: 651 """Return the number of occurrences of `item` in the vec. 652 653 `0` is returned if the vector is empty or hasn't been initialized, as well if them item not found. 654 655 Example 656 -------- 657 ```py 658 vec = Vec((1, 2, 3, 3)) 659 assert vec.count(3) == 2 660 ``` 661 """ 662 if self._ptr is None: 663 return 0 664 665 return self._ptr.count(item) 666 667 def __len__(self) -> int: 668 return len(self._ptr) if self._ptr else 0 669 670 def __setitem__(self, index: int, value: T): 671 if not self._ptr: 672 raise IndexError from None 673 674 self._ptr[index] = value 675 676 @typing.overload 677 def __getitem__(self, index: slice) -> Vec[T]: ... 678 679 @typing.overload 680 def __getitem__(self, index: int) -> T: ... 681 682 def __getitem__(self, index: int | slice) -> T | Vec[T]: 683 if not self._ptr: 684 raise IndexError("Index out of range") 685 686 if isinstance(index, slice): 687 return self.__class__(self._ptr[index]) 688 689 return self._ptr[index] 690 691 def __delitem__(self, index: int) -> None: 692 if not self._ptr: 693 return 694 695 del self._ptr[index] 696 697 def __contains__(self, element: T) -> bool: 698 return element in self._ptr if self._ptr else False 699 700 def __iter__(self) -> collections.Iterator[T]: 701 if self._ptr is None: 702 return iter(()) 703 704 return self._ptr.__iter__() 705 706 def __repr__(self) -> str: 707 return _LIST_REPR if not self._ptr else repr(self._ptr) 708 709 def __eq__(self, other: object) -> bool: 710 if not self._ptr: 711 return False 712 713 if isinstance(other, Vec): 714 return self._ptr == other._ptr 715 716 elif isinstance(other, list): 717 return self._ptr == other 718 719 elif isinstance(other, collections.Iterable): 720 # We have to copy here. 721 return self._ptr == list(other) 722 723 return NotImplemented 724 725 def __ne__(self, other: object) -> bool: 726 return not self.__eq__(other) 727 728 def __le__(self, other: list[T]) -> bool: 729 if not self._ptr: 730 return False 731 732 return self._ptr <= other 733 734 def __ge__(self, other: list[T]) -> bool: 735 if not self._ptr: 736 return False 737 738 return self._ptr >= other 739 740 def __lt__(self, other: list[T]) -> bool: 741 if not self._ptr: 742 return False 743 744 return self._ptr < other 745 746 def __gt__(self, other: list[T]) -> bool: 747 if not self._ptr: 748 return False 749 750 return self._ptr > other 751 752 def __bool__(self) -> bool: 753 return bool(self._ptr)
A contiguous growable alternative to builtin list
with extra functionalities.
The layout of Vec
is almost the same as list
.
When initializing a vec, it will not build the underlying list until the first element gets pushed. Which saves a little bit of memory.
Example
names = Vec()
names.push('foo')
names.push('bar')
print(names) # ['foo', 'bar']
assert names.len() == 2
Constructing
Vec()
: Create an unallocated vec, Which means the underlying list will beNone
until you start pushing into itVec(other_list)
: Create a vec which points toother_list
Vec((1, 2, 3))
: Create a vec with[1, 2, 3]
pre-allocatedVec.with_capacity(5)
: Create a vec that can hold up to 5 elementsfrom_args(1, 2, 3)
: Create a vec from arguments. This is not a classmethod
Iterating over Vec
There're two ways to iterate over a Vec
. The first is to normally use for
loop.
for i in names:
print(i)
# foo
# bar
The second is to use Vec.iter
, which yields all items in this Vec
from start to end.
Then the iterator gets exhausted as usual, See sain.Iterator
.
iterator = names.iter()
for name in iterator.map(str.upper):
print(name)
# FOO
# BAR
# No more items, The actual vec is left unchanged.
assert iterator.next().is_none()
Comparison operators
Comparing different collections with Vec
have a cost. Depending on what you're comparing it wit.
Any iterable that is not a list
or Vec
that is used to compare with will get copied into a list
,
So be careful what you compare a Vec
with.
vec = Vec((1,2,3))
# zero-cost
vec == [1, 2, 3]
# Copies {1, 2, 3} -> [1, 2, 3] which can cost.
vec == {1, 2, 3}
Zero-Copy
A vec that gets initialized from a list
will point to it and doesn't copy it.
So any element that gets appended to the collection will also get pushed into the vec.
cells: list[str] = []
vec = Vec(cells) # This DOES NOT copy the `cells`.
cells.append("foo")
vec[0] == "foo" # True
The opposite of the above is to initialize the vec from either an iterable or args, or copy the list.
# Creates a new vec and extend it with the elements.
from_args = vec.from_args("foo", "bar")
# inlined from another iterable.
from_iter = Vec(["foo", "bar"])
# Copy the list into a vec.
vec = Vec(cells[:])
cells.append("bar")
vec[2] # IndexError: "bar" doesn't exist in vec.
178 def __init__(self, iterable: collections.Iterable[T] | None = None) -> None: 179 # We won't allocate to build the list here. 180 # Instead, On first push or fist indexed set 181 # we allocate if it was None. 182 if isinstance(iterable, list): 183 # Calling `list()` on another list will copy it, So instead we just point to it. 184 self._ptr = iterable 185 elif isinstance(iterable, Vec): 186 self._ptr = iterable._ptr 187 # any other iterable that ain't a list needs to get copied into a new list. 188 else: 189 self._ptr: list[T] | None = list(iterable) if iterable else None 190 191 self._capacity: int | None = None
193 @classmethod 194 def with_capacity(cls, capacity: int) -> Self: 195 """Create a new `Vec` with at least the specified capacity. 196 This vec will be able to hold `capacity` elements without pushing further. 197 198 Check out `Vec.push_within_capacity` as well. 199 200 Example 201 ------- 202 ```py 203 vec = Vec.with_capacity(3) 204 assert vec.len() == 0 and vec.capacity() >= 3 205 206 vec.push(1) 207 vec.push(2) 208 vec.push(3) 209 print(vec.len()) # 3 210 211 # This won't push. 212 vec.push(4) 213 ``` 214 """ 215 v = cls() 216 v._capacity = capacity 217 return v
Create a new Vec
with at least the specified capacity.
This vec will be able to hold capacity
elements without pushing further.
Check out Vec.push_within_capacity
as well.
Example
vec = Vec.with_capacity(3)
assert vec.len() == 0 and vec.capacity() >= 3
vec.push(1)
vec.push(2)
vec.push(3)
print(vec.len()) # 3
# This won't push.
vec.push(4)
219 def as_ref(self) -> collections.Collection[T]: 220 """Return an immutable view over this vector elements. 221 222 Example 223 ------- 224 ```py 225 vec = Vec((1,2,3)) 226 227 assert vec.as_ref() == (1,2,3) 228 ``` 229 """ 230 return tuple(self)
Return an immutable view over this vector elements.
Example
vec = Vec((1,2,3))
assert vec.as_ref() == (1,2,3)
232 def len(self) -> int: 233 """Return the number of elements in this vector. 234 235 Example 236 ------- 237 ```py 238 vec = Vec((1,2,3)) 239 240 assert vec.len() == 3 241 ``` 242 """ 243 return self.__len__()
Return the number of elements in this vector.
Example
vec = Vec((1,2,3))
assert vec.len() == 3
245 def capacity(self) -> int: 246 """Return the capacity of this vector if set. 0 if not . 247 248 Example 249 ------- 250 ```py 251 vec_with_cap = Vec.with_capacity(3) 252 assert vec_with_cap.capacity().unwrap() == 3 253 254 vec = Vec([1, 2, 3]) 255 assert vec.capacity().is_none() 256 ``` 257 """ 258 return 0 if self._capacity is None else self._capacity
Return the capacity of this vector if set. 0 if not .
Example
vec_with_cap = Vec.with_capacity(3)
assert vec_with_cap.capacity().unwrap() == 3
vec = Vec([1, 2, 3])
assert vec.capacity().is_none()
260 def iter(self) -> _iter.Iterator[T]: 261 """Return an iterator over this vector elements. 262 263 Example 264 ------- 265 ```py 266 vec = Vec((1,2,3)) 267 iterator = vec.iter() 268 269 # Map each element to a str 270 for element in iterator.map(str): 271 print(element) 272 ``` 273 """ 274 return _iter.Iter(self)
Return an iterator over this vector elements.
Example
vec = Vec((1,2,3))
iterator = vec.iter()
# Map each element to a str
for element in iterator.map(str):
print(element)
276 def is_empty(self) -> bool: 277 """Returns true if the vector contains no elements.""" 278 return not self._ptr
Returns true if the vector contains no elements.
280 def split_off(self, at: int) -> Vec[T]: 281 """Split the vector off at the specified position. 282 283 if this vec is empty, `self` is returned unchanged. 284 285 Example 286 ------- 287 ```py 288 vec = Vec((1, 2, 3)) 289 split = vec.split_off(1) 290 291 print(split, vec) # [1], [2, 3] 292 ``` 293 294 Raises 295 ------ 296 `RuntimeError` 297 This method will raise if `at` > `len(self)` 298 """ 299 if at > self.len(): 300 raise RuntimeError( 301 f"Index `at` ({at}) should be <= than len of vector ({self.len()}) " 302 ) from None 303 304 # Either the list is empty or uninit. 305 if not self._ptr: 306 return self 307 308 split = self[at : self.len()] 309 self._ptr = self._ptr[0:at] 310 return split
Split the vector off at the specified position.
if this vec is empty, self
is returned unchanged.
Example
vec = Vec((1, 2, 3))
split = vec.split_off(1)
print(split, vec) # [1], [2, 3]
Raises
RuntimeError
: This method will raise ifat
>len(self)
312 def split_first(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 313 """Split the first and rest elements of the vector, If empty, `Some[None]` is returned. 314 315 Example 316 ------- 317 ```py 318 vec = Vec([1, 2, 3]) 319 split = vec.split_first() 320 assert split == Some((1, [2, 3])) 321 322 vec: Vec[int] = Vec() 323 split = vec.split_first() 324 assert split == Some(None) 325 ``` 326 """ 327 if not self._ptr: 328 return _option.nothing_unchecked() 329 330 first, *rest = self._ptr 331 return _option.Some((first, rest))
Split the first and rest elements of the vector, If empty, Some[None]
is returned.
Example
vec = Vec([1, 2, 3])
split = vec.split_first()
assert split == Some((1, [2, 3]))
vec: Vec[int] = Vec()
split = vec.split_first()
assert split == Some(None)
333 def first(self) -> _option.Option[T]: 334 """Get the first element in this vec, returning `Some[None]` if there's none. 335 336 Example 337 ------- 338 ```py 339 vec = Vec((1,2,3)) 340 first = vec.first() 341 assert ~first == 1 342 ``` 343 """ 344 return self.get(0)
Get the first element in this vec, returning Some[None]
if there's none.
Example
vec = Vec((1,2,3))
first = vec.first()
assert ~first == 1
346 def truncate(self, size: int) -> None: 347 """Shortens the vec, keeping the first `size` elements and dropping the rest. 348 349 Example 350 ------- 351 ```py 352 vec = Vec([1,2,3]) 353 vec.truncate(1) 354 assert vec == [1] 355 ``` 356 """ 357 if not self._ptr: 358 return 359 360 self._ptr = self._ptr[:size]
Shortens the vec, keeping the first size
elements and dropping the rest.
Example
vec = Vec([1,2,3])
vec.truncate(1)
assert vec == [1]
362 def retain(self, f: collections.Callable[[T], bool]) -> None: 363 """Remove elements from this vec while `f()` returns `True`. 364 365 In other words, filter this vector based on `f()`. 366 367 Example 368 ------- 369 ```py 370 vec = Vec([1, 2, 3]) 371 vec.retain(lambda elem: elem > 1) 372 373 assert vec == [2, 3] 374 ``` 375 """ 376 if not self._ptr: 377 return 378 379 self._ptr = [e for e in self._ptr if f(e)]
Remove elements from this vec while f()
returns True
.
In other words, filter this vector based on f()
.
Example
vec = Vec([1, 2, 3])
vec.retain(lambda elem: elem > 1)
assert vec == [2, 3]
381 def swap_remove(self, item: T) -> T: 382 """Remove the first appearance of `item` from this vector and return it. 383 384 Raises 385 ------ 386 * `ValueError`: if `item` is not in this vector. 387 * `MemoryError`: if this vector hasn't allocated, Aka nothing has been pushed to it. 388 389 Example 390 ------- 391 ```py 392 vec = Vector(('a', 'b', 'c')) 393 element = vec.remove('a') 394 assert vec == ['b', 'c'] and element == 'a' 395 ``` 396 """ 397 if self._ptr is None: 398 raise MemoryError("Vec is unallocated.") from None 399 400 try: 401 i = next(i for i in self._ptr if i == item) 402 except StopIteration: 403 raise ValueError(f"Item `{item}` not in list") from None 404 405 self.remove(i) 406 return i
Remove the first appearance of item
from this vector and return it.
Raises
*
ValueError
(ifitem
is not in this vector.):*
MemoryError
(if this vector hasn't allocated, Aka nothing has been pushed to it.):
Example
vec = Vector(('a', 'b', 'c'))
element = vec.remove('a')
assert vec == ['b', 'c'] and element == 'a'
408 def push(self, item: T) -> None: 409 """Push an element at the end of the vector. 410 411 Example 412 ------- 413 ```py 414 vec = Vec() 415 vec.push(1) 416 417 assert vec == [1] 418 ``` 419 """ 420 if self._capacity is not None: 421 self.push_within_capacity(item) 422 return 423 424 if self._ptr is None: 425 self._ptr = [] 426 427 self._ptr.append(item)
Push an element at the end of the vector.
Example
vec = Vec()
vec.push(1)
assert vec == [1]
429 def push_within_capacity(self, x: T) -> Result[None, T]: 430 """Appends an element if there is sufficient spare capacity, otherwise an error is returned with the element. 431 432 Example 433 ------- 434 ```py 435 vec: Vec[int] = Vec.with_capacity(3) 436 for i in range(3): 437 match vec.push_within_capacity(i): 438 case Ok(_): 439 print("All good.") 440 case Err(elem): 441 print("Reached max cap :< cant push", elem) 442 ``` 443 444 Or you can also just call `Vec.push` and it will push within capacity if `Vec.capacity()` is not `None`. 445 ```py 446 vec: Vec[int] = Vec.with_capacity(3) 447 448 vec.extend((1, 2, 3)) 449 vec.push(4) 450 451 assert vec.len() == 3 452 ``` 453 """ 454 if self._ptr is None: 455 self._ptr = [] 456 457 if self._capacity is not None and self._capacity <= self.len(): 458 return _result.Err(x) 459 460 self._ptr.append(x) 461 return _result.Ok(None)
Appends an element if there is sufficient spare capacity, otherwise an error is returned with the element.
Example
vec: Vec[int] = Vec.with_capacity(3)
for i in range(3):
match vec.push_within_capacity(i):
case Ok(_):
print("All good.")
case Err(elem):
print("Reached max cap :< cant push", elem)
Or you can also just call Vec.push
and it will push within capacity if Vec.capacity()
is not None
.
vec: Vec[int] = Vec.with_capacity(3)
vec.extend((1, 2, 3))
vec.push(4)
assert vec.len() == 3
463 def reserve(self, additional: int) -> None: 464 """Reserves capacity for at least additional more elements to be inserted in the given Vec<T>. 465 466 Example 467 ------- 468 ```py 469 vec = Vec.with_capacity(3) 470 is_vip = random.choice((True, False)) 471 472 for i in range(4): 473 match vec.push_within_capacity(i): 474 case Ok(_): 475 print("All good") 476 case Err(person): 477 # If the person is a VIP, then reserve for one more. 478 if is_vip: 479 vec.reserve(1) 480 continue 481 482 # is_vip was false. 483 print("Can't reserve for non-VIP members...", person) 484 break 485 ``` 486 """ 487 if self._capacity is not None: 488 self._capacity += additional
Reserves capacity for at least additional more elements to be inserted in the given Vec
Example
vec = Vec.with_capacity(3)
is_vip = random.choice((True, False))
for i in range(4):
match vec.push_within_capacity(i):
case Ok(_):
print("All good")
case Err(person):
# If the person is a VIP, then reserve for one more.
if is_vip:
vec.reserve(1)
continue
# is_vip was false.
print("Can't reserve for non-VIP members...", person)
break
408 def push(self, item: T) -> None: 409 """Push an element at the end of the vector. 410 411 Example 412 ------- 413 ```py 414 vec = Vec() 415 vec.push(1) 416 417 assert vec == [1] 418 ``` 419 """ 420 if self._capacity is not None: 421 self.push_within_capacity(item) 422 return 423 424 if self._ptr is None: 425 self._ptr = [] 426 427 self._ptr.append(item)
An alias for Vec.push
method.
498 def get(self, index: int) -> _option.Option[T]: 499 """Get the item at the given index, or `Some[None]` if its out of bounds. 500 501 Example 502 ------- 503 ```py 504 vec = Vec((1, 2, 3)) 505 vec.get(0) == Some(1) 506 vec.get(3) == Some(None) 507 ``` 508 """ 509 try: 510 return _option.Some(self.__getitem__(index)) 511 except IndexError: 512 return _option.nothing_unchecked()
Get the item at the given index, or Some[None]
if its out of bounds.
Example
vec = Vec((1, 2, 3))
vec.get(0) == Some(1)
vec.get(3) == Some(None)
514 def insert(self, index: int, value: T) -> None: 515 """Insert an element at the position `index`. 516 517 Example 518 -------- 519 ```py 520 vec = Vec((2, 3)) 521 vec.insert(0, 1) 522 assert vec == [1, 2, 3] 523 ``` 524 """ 525 self.__setitem__(index, value)
Insert an element at the position index
.
Example
vec = Vec((2, 3))
vec.insert(0, 1)
assert vec == [1, 2, 3]
527 def pop(self, index: int = -1) -> _option.Option[T]: 528 """Removes the last element from a vector and returns it, or `sain.Some(None)` if it is empty. 529 530 Example 531 ------- 532 ```py 533 vec = Vec((1, 2, 3)) 534 assert vec.pop() == Some(3) 535 assert vec == [1, 2] 536 ``` 537 """ 538 if not self._ptr: 539 return _option.nothing_unchecked() 540 541 return _option.Some(self._ptr.pop(index))
Removes the last element from a vector and returns it, or sain.Some(None)
if it is empty.
Example
vec = Vec((1, 2, 3))
assert vec.pop() == Some(3)
assert vec == [1, 2]
543 def remove(self, item: T) -> None: 544 """Remove `item` from this vector. 545 546 Example 547 ------- 548 ```py 549 vec = Vector(('a', 'b', 'c')) 550 vec.remove('a') 551 assert vec == ['b', 'c'] 552 ``` 553 """ 554 if not self._ptr: 555 return 556 557 self._ptr.remove(item)
Remove item
from this vector.
Example
vec = Vector(('a', 'b', 'c'))
vec.remove('a')
assert vec == ['b', 'c']
559 def extend(self, iterable: collections.Iterable[T]) -> None: 560 """Extend this vector from another iterable. 561 562 Example 563 ------- 564 ```py 565 vec = Vec((1, 2, 3)) 566 vec.extend((4, 5, 6)) 567 568 assert vec == [1, 2, 3, 4, 5, 6] 569 ``` 570 """ 571 if self._ptr is None: 572 self._ptr = [] 573 574 self._ptr.extend(iterable)
Extend this vector from another iterable.
Example
vec = Vec((1, 2, 3))
vec.extend((4, 5, 6))
assert vec == [1, 2, 3, 4, 5, 6]
576 def copy(self) -> Vec[T]: 577 """Create a vector that copies all of its elements and place it into the new one. 578 579 If the vector hasn't been allocated, `self` is returned. 580 581 Example 582 ------- 583 ```py 584 original = Vec((1,2,3)) 585 copy = original.copy() 586 copy.push(4) 587 588 print(original) # [1, 2, 3] 589 ``` 590 """ 591 return Vec(self._ptr[:]) if self._ptr is not None else self
Create a vector that copies all of its elements and place it into the new one.
If the vector hasn't been allocated, self
is returned.
Example
original = Vec((1,2,3))
copy = original.copy()
copy.push(4)
print(original) # [1, 2, 3]
593 def clear(self) -> None: 594 """Clear all elements of this vector. 595 596 Example 597 ------- 598 ```py 599 vec = Vec((1,2,3)) 600 vec.clear() 601 assert vec.len() == 0 602 ``` 603 """ 604 if not self._ptr: 605 return 606 607 self._ptr.clear()
Clear all elements of this vector.
Example
vec = Vec((1,2,3))
vec.clear()
assert vec.len() == 0
609 def sort( 610 self, 611 *, 612 key: collections.Callable[[T], SupportsRichComparison] | None = None, 613 reverse: bool = False, 614 ) -> None: 615 """This method sorts the list in place, using only < comparisons between items. 616 617 Example 618 ------- 619 ```py 620 vec = Vec((2, 1, 3)) 621 vec.sort() 622 assert vec == [1, 2, 3] 623 ``` 624 """ 625 if not self._ptr: 626 return 627 628 # key can be `None` here just fine, idk why pyright is complaining. 629 self._ptr.sort(key=key, reverse=reverse) # pyright: ignore
This method sorts the list in place, using only < comparisons between items.
Example
vec = Vec((2, 1, 3))
vec.sort()
assert vec == [1, 2, 3]
631 def index( 632 self, item: T, start: typing.SupportsIndex = 0, end: int = _sys.maxsize 633 ) -> int: 634 # << Official documentation >> 635 """Return zero-based index in the vec of the first item whose value is equal to `item`. 636 Raises a ValueError if there is no such item. 637 638 Example 639 ------- 640 ```py 641 vec = Vec((1, 2, 3)) 642 assert vec.index(2) == 1 643 ``` 644 """ 645 if self._ptr is None: 646 raise ValueError from None 647 648 return self._ptr.index(item, start, end)
Return zero-based index in the vec of the first item whose value is equal to item
.
Raises a ValueError if there is no such item.
Example
vec = Vec((1, 2, 3))
assert vec.index(2) == 1
650 def count(self, item: T) -> int: 651 """Return the number of occurrences of `item` in the vec. 652 653 `0` is returned if the vector is empty or hasn't been initialized, as well if them item not found. 654 655 Example 656 -------- 657 ```py 658 vec = Vec((1, 2, 3, 3)) 659 assert vec.count(3) == 2 660 ``` 661 """ 662 if self._ptr is None: 663 return 0 664 665 return self._ptr.count(item)
Return the number of occurrences of item
in the vec.
0
is returned if the vector is empty or hasn't been initialized, as well if them item not found.
Example
vec = Vec((1, 2, 3, 3))
assert vec.count(3) == 2
79@typing.runtime_checkable 80class Error(typing.Protocol): 81 """`Error` is an interface usually used for values that returns `sain.Result[T, E]` 82 83 where `E` is an implementation of this interface. 84 85 Example 86 ------- 87 ```py 88 import requests 89 from dataclasses import dataclass 90 91 from sain import Error, Option, Some 92 from sain import Result, Ok, Err 93 94 # Base error. 95 class HTTPError(Error): ... 96 97 @dataclass 98 class NotFound(HTTPError): 99 message = "The response returned [404]: not found." 100 http_status = 404 101 response: requests.Response 102 103 def description(self) -> str: 104 return "Couldn't find what you're looking for " + self.response.url 105 106 # It is not necessary to define this method, 107 # it just gives more context to the user handling this error. 108 def source(self) -> Option[type[HTTPError]]: 109 return Some(HTTPError) 110 111 @dataclass 112 class UserNotFound(NotFound): 113 user_id: int 114 115 def __post_init__(self) -> None: 116 request = self.response.request 117 self.message = f"User {self.user_id} fetched from {request.path_url} was not found." 118 119 # It is not necessary to define this method, 120 # it just gives more context to the user handling this error. 121 def source(self) -> Option[type[NotFound]]: 122 return Some(NotFound) 123 124 def description(self) -> str: 125 return f"Couldn't find the resource: {self.response.raw}." 126 127 # A simple request that handles [404] responses. 128 def request( 129 url: str, 130 resourceful: bool = False, 131 uid: int 132 ) -> Result[requests.Response, HTTPError]: 133 response = requests.get( 134 url, 135 json={"resourceful": True, "user_id": uid} 136 if resourceful else None 137 ) 138 139 if response.status_code == 404: 140 if resourceful: 141 return Err(UserNotFound(response, uid)) 142 return Err(NotFound(response)) 143 144 return Ok(response) 145 146 # Execute the request 147 match request("some-url.com', True, uid=0): 148 case Ok(response): 149 # Deal with the response 150 ... 151 case Err(why): 152 # Deal with the error. 153 print(why.message) 154 ``` 155 """ 156 157 __slots__ = ("message",) 158 159 def __init__(self, message: str = "") -> None: 160 self.message = message 161 """A basic error message.""" 162 163 def source(self) -> Option[type[Error]]: 164 """The source of this error, if any.""" 165 return _option.nothing_unchecked() 166 167 def description(self) -> str: 168 """Context for this error.""" 169 return "" 170 171 def __repr__(self) -> str: 172 source = None if (src := self.source()).is_none() else src 173 return ( 174 f'{type(self).__qualname__}(message: "{self.message}, source: {source!r})' 175 ) 176 177 def __str__(self) -> str: 178 return self.message 179 180 # An error is always falsy. 181 def __bool__(self) -> typing.Literal[False]: 182 return False
Error
is an interface usually used for values that returns sain.Result[T, E]
where E
is an implementation of this interface.
Example
import requests
from dataclasses import dataclass
from sain import Error, Option, Some
from sain import Result, Ok, Err
# Base error.
class HTTPError(Error): ...
@dataclass
class NotFound(HTTPError):
message = "The response returned [404]: not found."
http_status = 404
response: requests.Response
def description(self) -> str:
return "Couldn't find what you're looking for " + self.response.url
# It is not necessary to define this method,
# it just gives more context to the user handling this error.
def source(self) -> Option[type[HTTPError]]:
return Some(HTTPError)
@dataclass
class UserNotFound(NotFound):
user_id: int
def __post_init__(self) -> None:
request = self.response.request
self.message = f"User {self.user_id} fetched from {request.path_url} was not found."
# It is not necessary to define this method,
# it just gives more context to the user handling this error.
def source(self) -> Option[type[NotFound]]:
return Some(NotFound)
def description(self) -> str:
return f"Couldn't find the resource: {self.response.raw}."
# A simple request that handles [404] responses.
def request(
url: str,
resourceful: bool = False,
uid: int
) -> Result[requests.Response, HTTPError]:
response = requests.get(
url,
json={"resourceful": True, "user_id": uid}
if resourceful else None
)
if response.status_code == 404:
if resourceful:
return Err(UserNotFound(response, uid))
return Err(NotFound(response))
return Ok(response)
# Execute the request
match request("some-url.com', True, uid=0):
case Ok(response):
# Deal with the response
...
case Err(why):
# Deal with the error.
print(why.message)
58@typing.final 59class Box(typing.Generic[T]): 60 """The box object for expiring data. not thread-safe. 61 62 A box is an object that contains a value of type `T` which expires it after the given amount of time, 63 The box won't start expiring the data until its first access with `Box.get` method. 64 65 Example 66 ------- 67 ```py 68 # Initializing a box doesn't mean it started expiring. instead, 69 # getting the value the first time will start the process. 70 cache: dict[str, Box[int]] = {"sora": Box(999, timedelta(seconds=5)} 71 72 # first start expiring here. 73 cache["sora"].get().unwrap() 74 time.sleep(6) 75 assert cache["sora"].has_expired() 76 ``` 77 """ 78 79 __slots__ = ("_inner", "_expire_in", "_on_expire", "_mono") 80 81 def __init__(self, value: T, expire_in: int | float | datetime.timedelta) -> None: 82 if isinstance(expire_in, datetime.timedelta): 83 expire_in = expire_in.total_seconds() 84 else: 85 expire_in = float(expire_in) 86 87 if expire_in <= 0: 88 raise ValueError("expire_in must be more than 0 seconds.") 89 90 # We set the last call on the first access to the value. 91 self._mono: float | None = None 92 self._inner: Option[T] = option.Some(value) 93 self._on_expire: collections.Callable[[T], typing.Any] | None = None 94 self._expire_in = expire_in 95 96 @property 97 def has_expired(self) -> bool: 98 """Returns True if the value has expired.""" 99 # return self._mono is not None and not self._expire_in <= ( 100 # time.monotonic() - self._mono 101 # ) 102 return self._mono is not None and ( 103 not self._mono or self._expire_in <= (time.monotonic() - self._mono) 104 ) 105 106 def on_expire(self, callback: collections.Callable[[T], typing.Any]) -> Self: 107 """Set a callback that will be invoked when this value gets expired. 108 109 Both async and sync callbacks are supported. 110 111 Example 112 ------- 113 ```py 114 async def sink(message: str) -> None: 115 await client.create_message(message) 116 print("Sinked", message) 117 118 box = Box("bluh", 5).on_expire(sink) 119 120 while box.get().is_some(): 121 time.sleep(5) 122 ``` 123 First `.get` call on an expired box, the `sink` callback will be invoked, 124 also the inner value will be set to `Some(None)`. 125 126 After 5 seconds. 127 ```py 128 assert box.get() == Some("bluh") # This last call invokes the callback. 129 # Sinked bluh 130 assert box.get().is_none() 131 ``` 132 """ 133 self._on_expire = callback 134 return self 135 136 def remaining(self) -> float: 137 """Returns when this box will expire in seconds. 138 139 Example 140 -------- 141 ```py 142 jogo = Box("jogo", 3) 143 assert jogo.get().unwrap() == "jogo" 144 145 time.sleep(1) 146 assert jogo.remaining() == 2 147 ``` 148 """ 149 if not self._mono: 150 return 0.0 151 152 return math.floor( 153 (self._expire_in - (time.monotonic() - self._mono) + 1) * 0.99 154 ) 155 156 def get(self) -> Option[T]: 157 """Get the contained value if it was not expired, otherwise `Some(None)` is returned. 158 159 Example 160 ------- 161 ```py 162 pizza = Box("pizza", timedelta(days=1)) 163 164 while not pizza.get().is_none(): 165 # Do stuff with the value while its not expired. 166 167 # After 1 day. 168 assert pizza.get().is_none() 169 ``` 170 """ 171 if self.has_expired: 172 if self._on_expire is not None: 173 try: 174 if asyncio.iscoroutinefunction(self._on_expire): 175 futures.loop().run_until_complete( 176 self._on_expire(self._inner.unwrap_unchecked()) 177 ) 178 else: 179 self._on_expire(self._inner.unwrap_unchecked()) 180 finally: 181 self._on_expire = None 182 183 self._inner.take() 184 self._mono = None 185 # SAFETY: The value is expired, therefore we always return None. 186 return option.nothing_unchecked() 187 188 if self._mono is None: 189 self._mono = time.monotonic() 190 191 return self._inner 192 193 def __repr__(self) -> str: 194 return f"Box(value: {self._inner}, expired: {self.has_expired})" 195 196 __str__ = __repr__ 197 198 def __eq__(self, value: object, /) -> bool: 199 if not isinstance(value, Box): 200 return NotImplemented 201 202 return ( 203 self._inner == value._inner 204 and not self.has_expired 205 and not value.has_expired 206 ) 207 208 def __ne__(self, value: object, /) -> bool: 209 return not self.__eq__(value) 210 211 def __hash__(self) -> int: 212 return hash(self._inner) 213 214 def __bool__(self) -> bool: 215 return not self.has_expired
The box object for expiring data. not thread-safe.
A box is an object that contains a value of type T
which expires it after the given amount of time,
The box won't start expiring the data until its first access with Box.get
method.
Example
# Initializing a box doesn't mean it started expiring. instead,
# getting the value the first time will start the process.
cache: dict[str, Box[int]] = {"sora": Box(999, timedelta(seconds=5)}
# first start expiring here.
cache["sora"].get().unwrap()
time.sleep(6)
assert cache["sora"].has_expired()
81 def __init__(self, value: T, expire_in: int | float | datetime.timedelta) -> None: 82 if isinstance(expire_in, datetime.timedelta): 83 expire_in = expire_in.total_seconds() 84 else: 85 expire_in = float(expire_in) 86 87 if expire_in <= 0: 88 raise ValueError("expire_in must be more than 0 seconds.") 89 90 # We set the last call on the first access to the value. 91 self._mono: float | None = None 92 self._inner: Option[T] = option.Some(value) 93 self._on_expire: collections.Callable[[T], typing.Any] | None = None 94 self._expire_in = expire_in
96 @property 97 def has_expired(self) -> bool: 98 """Returns True if the value has expired.""" 99 # return self._mono is not None and not self._expire_in <= ( 100 # time.monotonic() - self._mono 101 # ) 102 return self._mono is not None and ( 103 not self._mono or self._expire_in <= (time.monotonic() - self._mono) 104 )
Returns True if the value has expired.
106 def on_expire(self, callback: collections.Callable[[T], typing.Any]) -> Self: 107 """Set a callback that will be invoked when this value gets expired. 108 109 Both async and sync callbacks are supported. 110 111 Example 112 ------- 113 ```py 114 async def sink(message: str) -> None: 115 await client.create_message(message) 116 print("Sinked", message) 117 118 box = Box("bluh", 5).on_expire(sink) 119 120 while box.get().is_some(): 121 time.sleep(5) 122 ``` 123 First `.get` call on an expired box, the `sink` callback will be invoked, 124 also the inner value will be set to `Some(None)`. 125 126 After 5 seconds. 127 ```py 128 assert box.get() == Some("bluh") # This last call invokes the callback. 129 # Sinked bluh 130 assert box.get().is_none() 131 ``` 132 """ 133 self._on_expire = callback 134 return self
Set a callback that will be invoked when this value gets expired.
Both async and sync callbacks are supported.
Example
async def sink(message: str) -> None:
await client.create_message(message)
print("Sinked", message)
box = Box("bluh", 5).on_expire(sink)
while box.get().is_some():
time.sleep(5)
First .get
call on an expired box, the sink
callback will be invoked,
also the inner value will be set to Some(None)
.
After 5 seconds.
assert box.get() == Some("bluh") # This last call invokes the callback.
# Sinked bluh
assert box.get().is_none()
136 def remaining(self) -> float: 137 """Returns when this box will expire in seconds. 138 139 Example 140 -------- 141 ```py 142 jogo = Box("jogo", 3) 143 assert jogo.get().unwrap() == "jogo" 144 145 time.sleep(1) 146 assert jogo.remaining() == 2 147 ``` 148 """ 149 if not self._mono: 150 return 0.0 151 152 return math.floor( 153 (self._expire_in - (time.monotonic() - self._mono) + 1) * 0.99 154 )
Returns when this box will expire in seconds.
Example
jogo = Box("jogo", 3)
assert jogo.get().unwrap() == "jogo"
time.sleep(1)
assert jogo.remaining() == 2
156 def get(self) -> Option[T]: 157 """Get the contained value if it was not expired, otherwise `Some(None)` is returned. 158 159 Example 160 ------- 161 ```py 162 pizza = Box("pizza", timedelta(days=1)) 163 164 while not pizza.get().is_none(): 165 # Do stuff with the value while its not expired. 166 167 # After 1 day. 168 assert pizza.get().is_none() 169 ``` 170 """ 171 if self.has_expired: 172 if self._on_expire is not None: 173 try: 174 if asyncio.iscoroutinefunction(self._on_expire): 175 futures.loop().run_until_complete( 176 self._on_expire(self._inner.unwrap_unchecked()) 177 ) 178 else: 179 self._on_expire(self._inner.unwrap_unchecked()) 180 finally: 181 self._on_expire = None 182 183 self._inner.take() 184 self._mono = None 185 # SAFETY: The value is expired, therefore we always return None. 186 return option.nothing_unchecked() 187 188 if self._mono is None: 189 self._mono = time.monotonic() 190 191 return self._inner
Get the contained value if it was not expired, otherwise Some(None)
is returned.
Example
pizza = Box("pizza", timedelta(days=1))
while not pizza.get().is_none():
# Do stuff with the value while its not expired.
# After 1 day.
assert pizza.get().is_none()
47@typing.final 48class MaybeUninit(typing.Generic[T]): 49 """A wrapper type to construct uninitialized instances of `T`. 50 51 This is kinda equivalent to Rust's `MaybeUninit<T>` wrapper. 52 53 The difference is what's happening under the hood is when initializing an instance of this object, 54 nothing really is being initialized, until you call `.write()` to initialize the inner value. 55 56 What?? 57 ----- 58 Ok, so `MaybeUninit<T>` is in a sense the unsafe equivalent of `Option<T>`, it either contains a value of type `T`, 59 or it contains uninitialized memory (`the attribute doesn't exist`). 60 61 However, `MaybeUninit` is unable to tell you whether the value it contains is `Some(T)` or `None`, So you 62 as a programmer is responsible for initializing it. 63 64 And by default, `Option<T>` always contain a default value, which is `None`. 65 66 Examples 67 -------- 68 ```py 69 # Create a list of 3 uninitialized strings preallocated. 70 pool: list[MaybeUninit[str]] = MaybeUninit.uninit_array(3) 71 chars = ['a', 'b', 'c'] 72 73 for index, uninit in enumerate(pool): 74 uninit.write(chars[index]) 75 76 assert all(obj.assume_init() for obj in pool) 77 ``` 78 """ 79 80 __slots__ = ("__value",) 81 82 @typing.overload 83 def __init__(self) -> None: ... 84 @typing.overload 85 def __init__(self, value: T) -> None: ... 86 87 def __init__(self, value: T | None = None) -> None: 88 if value is None: 89 return None 90 else: 91 # we simply pre-initialize the value if it was passed 92 # when constructing the instance. 93 self.__value = value 94 95 @classmethod 96 def uninit(cls) -> Self: 97 """Creates a new `MaybeUninit<T>` in an uninitialized state. 98 99 Example 100 ------- 101 ```py 102 v: MaybeUninit[str] = MaybeUninit().uninit() # or just MaybeUninit() 103 ``` 104 """ 105 return cls() 106 107 @classmethod 108 def uninit_array(cls, n: int) -> collections.Sequence[Self]: 109 """Creates an immutable sequence of `MaybeUninit<T>` in an uninitialized state. 110 111 Note that `inner` value doesn't exist in this state until you call `.write` to set it. 112 113 Example 114 ------- 115 ```py 116 v = MaybeUninit[str].uninit_array(3) 117 assert len(v) == 3 118 for uninit in v: 119 uninit.write('content') 120 121 initialized = uninit.assume_init() 122 ``` 123 """ 124 return tuple(cls() for _ in range(n)) 125 126 @macros.unsafe 127 def assume_init(self) -> T: 128 """Get the inner value, assuming that it was initialized by the caller. 129 130 It is unsafe and undefined behavior to call this method on an uninitialized state, 131 132 Example 133 ------- 134 ```py 135 uninit: MaybeUninit[int] = MaybeUninit.uninit() 136 val = uninit.assume_init() # This is UNSAFE ⚠️ 137 138 # Initialize it first. 139 uninit.write(0) 140 val = uninit.assume_init() # This is safe to access. 141 ``` 142 143 Raises 144 ------ 145 `LookupError` 146 If the `self` is not initialized. 147 """ 148 # SAFETY: the caller must guarantee that `self` is initialized. 149 try: 150 return self.__value 151 except AttributeError: 152 raise LookupError("Invalid access to uninitialized value.") from None 153 154 def write(self, value: T) -> T: 155 """Sets the value of the `MaybeUninit[T]`. 156 157 This will overwrite any previous values, if was initialized obviously. 158 159 Example 160 ------- 161 ```py 162 def scoped(value: MaybeUninit[bytes]) -> MaybeUninit[bytes]: 163 response = requests.get("...") 164 if response.ok: 165 # If ok, initialize. 166 value.write(response.content) 167 168 return value 169 170 buffer: MaybeUninit[bytes] = MaybeUninit() 171 data = scoped(buffer) # buffer is initialized now. 172 print(data.assume_init()) 173 ``` 174 """ 175 self.__write_mangling(value) 176 return self.assume_init() 177 178 # These are magic methods to bypass the name mangling. 179 def __write_mangling(self, value: T) -> T: 180 # A little hack to bypass name dangling. 181 object.__setattr__(self, "_MaybeUninit__value", value) 182 return value 183 184 def __read_mangling(self) -> T: 185 return getattr(self, "_MaybeUninit__value") 186 187 def __repr__(self) -> str: 188 # We could technically do an `if self: ...` here but 189 # thats not how bool checks work. for example if an empty str 190 # in the content of this cell it will return `False` 191 # which isn't correct. So we actually need to check 192 # whether the attribute is set or not. Also since 3.11, 193 # exception are zero cost, So this shouldn't be a critically inefficient. 194 try: 195 return f"MaybeUninit(value: {self.__read_mangling()!r})" 196 except AttributeError: 197 return "<uninit>" 198 199 __str__ = __repr__ 200 201 def __bool__(self) -> bool: 202 """Perform a boolean check on whether the value is initialized or not. 203 204 Example 205 ------- 206 ```py 207 v = MaybeUninit[bool]() 208 assert not v 209 ``` 210 """ 211 return hasattr(self, "_MaybeUninit__value") 212 213 def __eq__(self, value: object, /) -> bool: 214 if not isinstance(value, MaybeUninit): 215 return NotImplemented 216 217 if not self or not value: 218 # either ones not initialized. 219 return False 220 221 return self.__read_mangling() == typing.cast( 222 "MaybeUninit[T]", value.__read_mangling() 223 ) 224 225 def __ne__(self, value: object, /) -> bool: 226 return not self.__eq__(value) 227 228 def __hash__(self) -> int: 229 return self.__read_mangling().__hash__()
A wrapper type to construct uninitialized instances of T
.
This is kinda equivalent to Rust's MaybeUninit<T>
wrapper.
The difference is what's happening under the hood is when initializing an instance of this object,
nothing really is being initialized, until you call .write()
to initialize the inner value.
What??
Ok, so MaybeUninit<T>
is in a sense the unsafe equivalent of Option<T>
, it either contains a value of type T
,
or it contains uninitialized memory (the attribute doesn't exist
).
However, MaybeUninit
is unable to tell you whether the value it contains is Some(T)
or None
, So you
as a programmer is responsible for initializing it.
And by default, Option<T>
always contain a default value, which is None
.
Examples
# Create a list of 3 uninitialized strings preallocated.
pool: list[MaybeUninit[str]] = MaybeUninit.uninit_array(3)
chars = ['a', 'b', 'c']
for index, uninit in enumerate(pool):
uninit.write(chars[index])
assert all(obj.assume_init() for obj in pool)
95 @classmethod 96 def uninit(cls) -> Self: 97 """Creates a new `MaybeUninit<T>` in an uninitialized state. 98 99 Example 100 ------- 101 ```py 102 v: MaybeUninit[str] = MaybeUninit().uninit() # or just MaybeUninit() 103 ``` 104 """ 105 return cls()
Creates a new MaybeUninit<T>
in an uninitialized state.
Example
v: MaybeUninit[str] = MaybeUninit().uninit() # or just MaybeUninit()
107 @classmethod 108 def uninit_array(cls, n: int) -> collections.Sequence[Self]: 109 """Creates an immutable sequence of `MaybeUninit<T>` in an uninitialized state. 110 111 Note that `inner` value doesn't exist in this state until you call `.write` to set it. 112 113 Example 114 ------- 115 ```py 116 v = MaybeUninit[str].uninit_array(3) 117 assert len(v) == 3 118 for uninit in v: 119 uninit.write('content') 120 121 initialized = uninit.assume_init() 122 ``` 123 """ 124 return tuple(cls() for _ in range(n))
Creates an immutable sequence of MaybeUninit<T>
in an uninitialized state.
Note that inner
value doesn't exist in this state until you call .write
to set it.
Example
v = MaybeUninit[str].uninit_array(3)
assert len(v) == 3
for uninit in v:
uninit.write('content')
initialized = uninit.assume_init()
126 @macros.unsafe 127 def assume_init(self) -> T: 128 """Get the inner value, assuming that it was initialized by the caller. 129 130 It is unsafe and undefined behavior to call this method on an uninitialized state, 131 132 Example 133 ------- 134 ```py 135 uninit: MaybeUninit[int] = MaybeUninit.uninit() 136 val = uninit.assume_init() # This is UNSAFE ⚠️ 137 138 # Initialize it first. 139 uninit.write(0) 140 val = uninit.assume_init() # This is safe to access. 141 ``` 142 143 Raises 144 ------ 145 `LookupError` 146 If the `self` is not initialized. 147 """ 148 # SAFETY: the caller must guarantee that `self` is initialized. 149 try: 150 return self.__value 151 except AttributeError: 152 raise LookupError("Invalid access to uninitialized value.") from None
Get the inner value, assuming that it was initialized by the caller.
It is unsafe and undefined behavior to call this method on an uninitialized state,
Example
uninit: MaybeUninit[int] = MaybeUninit.uninit()
val = uninit.assume_init() # This is UNSAFE ⚠️
# Initialize it first.
uninit.write(0)
val = uninit.assume_init() # This is safe to access.
Raises
LookupError
: If theself
is not initialized.- # Safety ⚠️
- **Calling this method on
None
is considered undefined behavior:
154 def write(self, value: T) -> T: 155 """Sets the value of the `MaybeUninit[T]`. 156 157 This will overwrite any previous values, if was initialized obviously. 158 159 Example 160 ------- 161 ```py 162 def scoped(value: MaybeUninit[bytes]) -> MaybeUninit[bytes]: 163 response = requests.get("...") 164 if response.ok: 165 # If ok, initialize. 166 value.write(response.content) 167 168 return value 169 170 buffer: MaybeUninit[bytes] = MaybeUninit() 171 data = scoped(buffer) # buffer is initialized now. 172 print(data.assume_init()) 173 ``` 174 """ 175 self.__write_mangling(value) 176 return self.assume_init()
Sets the value of the MaybeUninit[T]
.
This will overwrite any previous values, if was initialized obviously.
Example
def scoped(value: MaybeUninit[bytes]) -> MaybeUninit[bytes]:
response = requests.get("...")
if response.ok:
# If ok, initialize.
value.write(response.content)
return value
buffer: MaybeUninit[bytes] = MaybeUninit()
data = scoped(buffer) # buffer is initialized now.
print(data.assume_init())
112@typing.runtime_checkable 113class From(typing.Protocol[T_co]): 114 """Used to do value-to-value conversions while consuming the input value. It is the reciprocal of Into. 115 116 As the Rust documentation says, One should always prefer implementing From over Into 117 because implementing From automatically provides one with an implementation of Into. 118 119 But there's no such thing in Python, as it's impossible to auto-impl `Into<T>` for all types 120 that impl `From<T>`. 121 122 So for the sake of simplicity, You should implement whichever interface you want deal with, 123 Or simply, implement both as the same time. 124 125 Example 126 ------- 127 ```py 128 @dataclass 129 class Id(From[str]): 130 value: int 131 132 @classmethod 133 def from_t(cls, value: str) -> Self: 134 return cls(value=int(value)) 135 136 ``` 137 """ 138 139 __slots__ = () 140 141 @classmethod 142 def from_t(cls, value: T_co) -> Self: 143 """Perform the conversion.""" 144 raise NotImplementedError
Used to do value-to-value conversions while consuming the input value. It is the reciprocal of Into.
As the Rust documentation says, One should always prefer implementing From over Into because implementing From automatically provides one with an implementation of Into.
But there's no such thing in Python, as it's impossible to auto-impl Into<T>
for all types
that impl From<T>
.
So for the sake of simplicity, You should implement whichever interface you want deal with, Or simply, implement both as the same time.
Example
@dataclass
class Id(From[str]):
value: int
@classmethod
def from_t(cls, value: str) -> Self:
return cls(value=int(value))
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
147@typing.runtime_checkable 148class TryFrom(typing.Protocol[T_co, E]): 149 """Simple and safe type conversions that may fail in a controlled way under some circumstances. 150 It is the reciprocal of `TryInto`. 151 152 It is useful to implement this when you know that the conversion may fail in some way. 153 154 Generic Implementations 155 ------------------- 156 This interface takes two type arguments, and return `Result<Self, E>` 157 158 * `T`: Which's the first generic `T` is the type that's being converted from. 159 * `E`: If the conversion fails in a way, this is what will return as the error. 160 * `Self`: Which's the instance of the class that is being converted into. 161 162 Example 163 ------- 164 ```py 165 @dataclass 166 class Id(TryFrom[str, str]): 167 value: int 168 169 @classmethod 170 def try_from(cls, value: str) -> Result[Self, str]: 171 if not value.isnumeric(): 172 # NaN 173 return Err(f"Couldn't convert: {value} to self") 174 # otherwise convert it to an Id instance. 175 return Ok(value=cls(int(value))) 176 ``` 177 """ 178 179 __slots__ = () 180 181 @classmethod 182 def try_from(cls, value: T_co) -> Result[Self, E]: 183 """Perform the conversion.""" 184 raise NotImplementedError
Simple and safe type conversions that may fail in a controlled way under some circumstances.
It is the reciprocal of TryInto
.
It is useful to implement this when you know that the conversion may fail in some way.
Generic Implementations
This interface takes two type arguments, and return Result<Self, E>
T
: Which's the first genericT
is the type that's being converted from.E
: If the conversion fails in a way, this is what will return as the error.Self
: Which's the instance of the class that is being converted into.
Example
@dataclass
class Id(TryFrom[str, str]):
value: int
@classmethod
def try_from(cls, value: str) -> Result[Self, str]:
if not value.isnumeric():
# NaN
return Err(f"Couldn't convert: {value} to self")
# otherwise convert it to an Id instance.
return Ok(value=cls(int(value)))
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
187@typing.runtime_checkable 188class Into(typing.Protocol[T_cov]): 189 """Conversion from `self`, which may or may not be expensive. 190 191 Example 192 ------- 193 ```py 194 @dataclass 195 class Id(Into[str]): 196 value: int 197 198 def into(self) -> str: 199 return str(self.value) 200 ``` 201 """ 202 203 __slots__ = () 204 205 def into(self) -> T_cov: 206 """Perform the conversion.""" 207 raise NotImplementedError
Conversion from self
, which may or may not be expensive.
Example
@dataclass
class Id(Into[str]):
value: int
def into(self) -> str:
return str(self.value)
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)
210@typing.runtime_checkable 211class TryInto(typing.Protocol[T, E]): 212 """An attempted conversion from `self`, which may or may not be expensive. 213 214 It is useful to implement this when you know that the conversion may fail in some way. 215 216 Generic Implementations 217 ------------------- 218 This interface takes two type arguments, and return `Result<T, E>` 219 220 * `T`: The first generic `T` is the type that's being converted into. 221 * `E`: If the conversion fails in a way, this is what will return as the error. 222 223 Example 224 ------- 225 ```py 226 @dataclass 227 class Id(TryInto[int, str]): 228 value: str 229 230 def try_into(self) -> Result[int, str]: 231 if not self.value.isnumeric(): 232 return Err(f"{self.value} is not a number...") 233 return Ok(int(self.value)) 234 ``` 235 """ 236 237 __slots__ = () 238 239 def try_into(self) -> Result[T, E]: 240 """Perform the conversion.""" 241 raise NotImplementedError
An attempted conversion from self
, which may or may not be expensive.
It is useful to implement this when you know that the conversion may fail in some way.
Generic Implementations
This interface takes two type arguments, and return Result<T, E>
T
: The first genericT
is the type that's being converted into.E
: If the conversion fails in a way, this is what will return as the error.
Example
@dataclass
class Id(TryInto[int, str]):
value: str
def try_into(self) -> Result[int, str]:
if not self.value.isnumeric():
return Err(f"{self.value} is not a number...")
return Ok(int(self.value))
1739def _no_init_or_replace_init(self, *args, **kwargs): 1740 cls = type(self) 1741 1742 if cls._is_protocol: 1743 raise TypeError('Protocols cannot be instantiated') 1744 1745 # Already using a custom `__init__`. No need to calculate correct 1746 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1747 if cls.__init__ is not _no_init_or_replace_init: 1748 return 1749 1750 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1751 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1752 # searches for a proper new `__init__` in the MRO. The new `__init__` 1753 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1754 # instantiation of the protocol subclass will thus use the new 1755 # `__init__` and no longer call `_no_init_or_replace_init`. 1756 for base in cls.__mro__: 1757 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1758 if init is not _no_init_or_replace_init: 1759 cls.__init__ = init 1760 break 1761 else: 1762 # should not happen 1763 cls.__init__ = object.__init__ 1764 1765 cls.__init__(self, *args, **kwargs)