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 # error.py 68 "error", 69 "Error", 70 # boxed.py 71 "boxed", 72 "Box", 73 # sync 74 "sync", 75 # maybe_uninit.py 76 "maybe_uninit", 77 # convert 78 "convert", 79 "From", 80 "TryFrom", 81 "Into", 82 "TryInto", 83 "ToString", 84) 85 86from . import boxed 87from . import collections 88from . import convert 89from . import default 90from . import error 91from . import futures 92from . import iter 93from . import macros 94from . import maybe_uninit 95from . import option 96from . import result 97from . import sync 98from .boxed import Box 99from .cfg import cfg 100from .cfg import cfg_attr 101from .collections import Vec 102from .convert import From 103from .convert import Into 104from .convert import ToString 105from .convert import TryFrom 106from .convert import TryInto 107from .default import Default 108from .error import Error 109from .iter import Iter 110from .iter import Iterator 111from .macros import deprecated 112from .macros import doc 113from .macros import todo 114from .macros import unimplemented 115from .option import NOTHING 116from .option import Option 117from .option import Some 118from .result import Err 119from .result import Ok 120from .result import Result 121 122__version__: str = "1.3.0" 123__url__: str = "https://github.com/nxtlo/sain" 124__author__: str = "nxtlo" 125__about__: str = ( 126 "Sain is a dependency-free library that implements some of the Rust core" 127 "types which abstracts over common patterns in Rust for Python." 128) 129__license__: str = "BSD 3-Clause License"
173@rustc_diagnostic_item("cfg") 174def cfg( 175 target_os: System | None = None, 176 python_version: tuple[int, int, int] | None = None, 177 target_arch: Arch | None = None, 178 impl: Python | None = None, 179) -> bool: 180 """A function that will run the code only if all predicate attributes returns `True`. 181 182 The difference between this function and `cfg_attr` is that this function will not raise an exception. 183 Instead it will return `False` if any of the attributes fails. 184 185 Example 186 ------- 187 ```py 188 import sain 189 190 if cfg(target_os="windows"): 191 print("Windows") 192 elif cfg(target_os="linux", target_arch="arm64"): 193 print("Linux") 194 else: 195 print("Something else") 196 ``` 197 198 Parameters 199 ---------- 200 target_os : `str | None` 201 The targeted operating system thats required for the object to be ran. 202 python_version : `tuple[int, int, int] | None` 203 The targeted Python version thats required for the object to be ran. Format must be `(3, 9, 5)`` 204 target_arch : `str | None` 205 The CPU targeted architecture thats required for the object to be ran. 206 impl : `str | None` 207 The Python implementation thats required for the object to be ran. 208 209 Returns 210 ------- 211 `bool` 212 The condition that was checked. 213 """ 214 checker = _AttrCheck( 215 lambda: None, 216 no_raise=True, 217 target_os=target_os, 218 python_version=python_version, 219 target_arch=target_arch, 220 impl=impl, 221 ) 222 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.- # Implementations
- **This function implements cfg:
101@rustc_diagnostic_item("cfg_attr") 102def cfg_attr( 103 *, 104 target_os: System | None = None, 105 python_version: tuple[int, int, int] | None = None, 106 target_arch: Arch | None = None, 107 impl: Python | None = None, 108) -> collections.Callable[[F], F]: 109 """Conditional runtime object configuration based on passed arguments. 110 111 If the decorated object gets called and one of the attributes returns `False`, 112 `RuntimeError` will be raised and the object will not run. 113 114 Example 115 ------- 116 ```py 117 import sain 118 119 @cfg_attr(target_os="windows") 120 def windows_only(): 121 # Do stuff with Windows's API. 122 ... 123 124 # Mut be PyPy Python implementation or `RuntimeError` will be raised 125 # when creating the instance. 126 @cfg_attr(impl="PyPy") 127 class Zoo: 128 @sain.cfg_attr(target_os="linux") 129 def bark(self) -> None: 130 ... 131 132 # An instance will not be created if raised. 133 zoo = Zoo() 134 # RuntimeError("class Zoo requires PyPy implementation") 135 ``` 136 137 Parameters 138 ---------- 139 target_os : `str | None` 140 The targeted operating system thats required for the object. 141 python_version : `tuple[int, int, int] | None` 142 The targeted Python version thats required for the object. Format must be `(3, 9, 5)`. 143 target_arch : `str | None` 144 The CPU targeted architecture thats required for the object. 145 impl : `str | None` 146 The Python implementation thats required for the object. 147 148 Raises 149 ------ 150 `RuntimeError` 151 This fails if any of the attributes returns `False`. 152 `ValueError` 153 If the passed Python implementation is unknown. 154 """ 155 156 def decorator(callback: F) -> F: 157 @functools.wraps(callback) 158 def wrapper(*args: typing.Any, **kwargs: typing.Any) -> F: 159 checker = _AttrCheck( 160 callback, 161 target_os=target_os, 162 python_version=python_version, 163 target_arch=target_arch, 164 impl=impl, 165 ) 166 return checker(*args, **kwargs) 167 168 return typing.cast(F, wrapper) 169 170 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:
...
# An instance will not be created if raised.
zoo = Zoo()
# RuntimeError("class Zoo requires PyPy implementation")
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.- # Implementations
- **This function implements cfg_attr:
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)
# {}
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 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",) 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 transpose(self) -> T | None: 104 """Convert `Option[T]` into `T | None`. 105 106 Examples 107 -------- 108 ```py 109 opt = Some('char') 110 x = opt.transpose() 111 assert x == 'char' 112 113 opt = Some(None) 114 assert opt.transpose() 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("Called `Option.unwrap()` on `None`.") from None 143 144 return self._value 145 146 def unwrap_or(self, default: T, /) -> T: 147 """Unwrap the inner value either returning if its not `None` or returning `default`. 148 149 Example 150 ------- 151 ```py 152 value = Some(5) 153 print(value.unwrap_or(10)) 154 # 5 155 156 # Type hint is required here. 157 value: Option[int] = Some(None) 158 print(value.unwrap_or(10)) 159 # 10 160 ``` 161 """ 162 if self._value is None: 163 return default 164 165 return self._value 166 167 def unwrap_or_else(self, f: FnOnce[T], /) -> T: 168 """Unwrap the inner value either returning if its not `None` or calling `f` to get a default value. 169 170 Example 171 ------- 172 ```py 173 value = Some(5) 174 print(value.unwrap_or_else(lambda: 10)) 175 # 5 176 177 value: Option[bool] = Some(None) 178 print(value.unwrap_or_else(lambda: True)) 179 # True 180 ``` 181 """ 182 if self._value is None: 183 return f() 184 185 return self._value 186 187 @macros.unsafe 188 def unwrap_unchecked(self) -> T: 189 """Returns the contained Some value without checking that the value is not None. 190 191 Example 192 ------- 193 ```py 194 v: Option[float] = Some(1.2) 195 v.unwrap_unchecked() # 1.2 196 197 v: Option[float] = Some(None) 198 print(v.unwrap_unchecked()) # Undefined Behavior 199 ``` 200 """ 201 #! SAFETY: The caller guarantees that the value is not None. 202 return self._value # pyright: ignore 203 204 def expect(self, message: str, /) -> T: 205 """Returns the contained value if it is not `None` otherwise raises a `RuntimeError`. 206 207 Example 208 ------- 209 ```py 210 value = Some("Hello") 211 212 print(value.expect("Value is None")) 213 # "Hello" 214 215 value: Option[str] = Some(None) 216 print(value.expect("Value is None")) 217 # RuntimeError("Value is None") 218 ``` 219 """ 220 if self._value is None: 221 raise RuntimeError(message) 222 223 return self._value 224 225 # *- object transformation -* 226 227 def map(self, f: Fn[T, U], /) -> Option[U]: 228 """Map the inner value to another type. Returning `Some(None)` if `T` is `None`. 229 230 Example 231 ------- 232 ```py 233 value = Some(5.0) 234 235 print(value.map(lambda x: x * 2.0)) 236 # Some(10.0) 237 238 value: Option[bool] = Some(None) 239 print(value) 240 # Some(None) 241 ``` 242 """ 243 if self._value is None: 244 return NOTHING # pyright: ignore 245 246 return Some(f(self._value)) 247 248 def map_or(self, default: U, f: Fn[T, U], /) -> U: 249 """Map the inner value to another type or return `default` if its `None`. 250 251 Example 252 ------- 253 ```py 254 value: Option[float] = Some(5.0) 255 256 # map to int. 257 print(value.map_or(0, int)) 258 # 6 259 260 value: Option[float] = Some(None) 261 print(value.map_or(0, int) 262 # 0 263 ``` 264 """ 265 if self._value is None: 266 return default 267 268 return f(self._value) 269 270 def map_or_else(self, default: FnOnce[U], f: Fn[T, U], /) -> U: 271 """Map the inner value to another type, or return `default()` if its `None`. 272 273 Example 274 ------- 275 ```py 276 def default() -> int: 277 return sys.getsizeof(object()) 278 279 value: Option[float] = Some(5.0) 280 281 # map to int. 282 print(value.map_or_else(default, int)) 283 # 6 284 285 value: Option[float] = Some(None) 286 print(value.map_or_else(default, int) 287 # 28 <- size of object() 288 ``` 289 """ 290 if self._value is None: 291 return default() 292 293 return f(self._value) 294 295 def filter(self, predicate: Fn[T, bool]) -> Option[T]: 296 """Returns `Some(None)` if the contained value is `None`, 297 298 otherwise calls the predicate and returns `Some(T)` if the predicate returns `True`. 299 300 Example 301 ------- 302 ```py 303 value = Some([1, 2, 3]) 304 305 print(value.filter(lambda x: 1 in x)) 306 # Some([1, 2, 3]) 307 308 value: Option[int] = Some([1, 2, 3]) # or Some(None) 309 print(value.filter(lambda x: 1 not in x)) 310 # None 311 ``` 312 """ 313 if (value := self._value) is not None: 314 if predicate(value): 315 return Some(value) 316 317 return NOTHING # pyright: ignore 318 319 def ok_or(self, err: U) -> _result.Result[T, U]: 320 """Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err)`. 321 322 Example 323 ------- 324 ```py 325 xyz: Option[str] = Some("foo") 326 assert xyz.ok_or(None) == Ok("foo") 327 328 xyz: Option[str] = Some(None) 329 assert xyz.ok_or(None) == Err(None) 330 ``` 331 """ 332 if self._value is None: 333 return _result.Err(err) 334 335 return _result.Ok(self._value) 336 337 def ok_or_else(self, err: FnOnce[U]) -> _result.Result[T, U]: 338 """Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err())`. 339 340 Example 341 ------- 342 ```py 343 xyz: Option[str] = Some("foo") 344 assert xyz.ok_or(None) == Ok("foo") 345 346 xyz: Option[str] = Some(None) 347 assert xyz.ok_or(None) == Err(None) 348 ``` 349 """ 350 if self._value is None: 351 return _result.Err(err()) 352 353 return _result.Ok(self._value) 354 355 def zip(self, other: Option[U]) -> Option[tuple[T, U]]: 356 """Zips `self` with `other`. 357 358 if `self` is `Some(s)` and other is `Some(o)`, this returns `Some((s, o))` otherwise `None`. 359 360 Example 361 ------- 362 ```py 363 x = Some(1) 364 y = Some("hi") 365 z: Option[str] = Some(None) 366 367 assert x.zip(y) == Some((1, "hi")) 368 assert x.zip(z) == Some(None) 369 ``` 370 """ 371 if self._value is not None and other._value is not None: 372 return Some((self._value, other._value)) 373 374 return NOTHING # type: ignore 375 376 def zip_with( 377 self, other: Option[U], f: collections.Callable[[T, U], T_co] 378 ) -> Option[T_co]: 379 """Zips `self` with `other` using function `f`. 380 381 if `self` is `Some(s)` and other is `Some(o)`, this returns `Some(f(s, o))` otherwise `None`. 382 383 Example 384 ------- 385 ```py 386 @dataclass 387 class Point: 388 x: float 389 y: float 390 391 x, y = Some(32.1), Some(42.4) 392 assert x.zip_with(y, Point) == Some(Point(32.1, 42.4)) 393 ``` 394 """ 395 if self._value is not None and other._value is not None: 396 return Some(f(self._value, other._value)) 397 398 return NOTHING # type: ignore 399 400 # *- Inner operations *- 401 402 def take(self) -> Option[T]: 403 """Take the value from `Self`, Setting it to `None`. 404 405 Example 406 ------- 407 ```py 408 original = Some("Hi") 409 new = original.take() 410 411 print(original, new) 412 # None, Some("Hi") 413 ``` 414 """ 415 if self._value is None: 416 return NOTHING # pyright: ignore 417 418 val = self._value 419 self._value = None 420 return Some(val) 421 422 def take_if(self, predicate: collections.Callable[[T], bool]) -> Option[T]: 423 """Take the value from `Self`, Setting it to `None` only if predicate returns `True`. 424 425 Example 426 ------- 427 ```py 428 def validate(email: str) -> bool: 429 # you can obviously validate this better. 430 return email.find('@') == 1 431 432 original = Some("flex@gg.com") 433 valid = original.take_if(validate) 434 assert is_allowed.is_some() and original.is_none() 435 436 original = Some("mail.example.com") 437 invalid = original.take_if(validate) 438 assert invalid.is_none() and original.is_some() 439 ``` 440 """ 441 if self.map_or(False, predicate): 442 return self.take() 443 444 return NOTHING # pyright: ignore 445 446 def replace(self, value: T) -> Option[T]: 447 """Replace the contained value with another value. 448 449 Use `Option.insert` if you want to return the original value 450 that got inserted instead of `self` 451 452 Example 453 ------- 454 ```py 455 value: Option[str] = Some(None) 456 value.replace("Hello") 457 # Some("Hello") 458 ``` 459 """ 460 self._value = value 461 return self 462 463 def insert(self, value: T) -> T: 464 """Insert a value into the option, and then return a reference to it. 465 466 This will overwrite the old value if it was already contained. 467 468 Example 469 ------- 470 ```py 471 flag: Option[bool] = Some(None) 472 flag_ref = flag.insert(True) 473 assert flag_ref == True 474 assert flag.unwrap() == True 475 ``` 476 """ 477 self._value = value 478 return value 479 480 def get_or_insert(self, value: T) -> T: 481 """Insert a value into the option if it was `None`, 482 and then return a reference to it. 483 484 Example 485 ------- 486 ```py 487 state: Option[bool] = Some(None) 488 assert state.get_or_insert(True) is True 489 assert state.get_or_insert(False) is True 490 ``` 491 """ 492 if self._value is not None: 493 return self._value 494 495 self._value = value 496 return value 497 498 def get_or_insert_with(self, f: FnOnce[T]) -> T: 499 """Insert a value into the option computed from `f()` if it was `None`, 500 and then return a reference to it. 501 502 Example 503 ------- 504 ```py 505 flag: Option[bool] = Some(None) 506 flag_ref = flag.insert(True) 507 assert flag_ref == True 508 assert flag.unwrap() == True 509 ``` 510 """ 511 if self._value is not None: 512 return self._value 513 514 v = self._value = f() 515 return v 516 517 def and_ok(self, optb: Option[T]) -> Option[T]: 518 """Returns `None` if `self` or `optb` is `None`, otherwise return `optb`. 519 520 aliases: `Option::and` 521 522 Example 523 ------- 524 ```py 525 x = Some(1) 526 y: Option[str] = Some(None) 527 assert x.and_ok(y) == Some(None) 528 529 x: Option[str] = Some(None) 530 y = Some(1) 531 assert x.and_ok(y) == Some(None) 532 533 y: Option[str] = Some("hi") 534 y = Some(100) 535 assert x.and_ok(y) == Some(100) 536 ``` 537 """ 538 if self._value is None or optb._value is None: 539 return optb 540 541 return NOTHING # pyright: ignore 542 543 def and_then(self, f: Fn[T, Option[T]]) -> Option[T]: 544 """Returns `Some(None)` if the contained value is `None`, otherwise call `f()` 545 on `T` and return `Option[T]`. 546 547 Example 548 ------- 549 ```py 550 value = Some(5) 551 print(value.and_then(lambda x: Some(x * 2))) 552 # Some(10) 553 554 value: Option[int] = Some(None) 555 print(value.and_then(lambda x: Some(x * 2))) 556 # Some(None) 557 ``` 558 """ 559 if self._value is None: 560 return NOTHING # pyright: ignore 561 562 return f(self._value) 563 564 def inspect(self, f: Fn[T, typing.Any]) -> Option[T]: 565 """Calls `f()` on the contained value if it was `Some(v)`, otherwise does nothing. 566 567 Example 568 ------- 569 ```py 570 def debug(x: str) -> None: 571 print("Debugging:", x) 572 573 value = Some("foo") 574 inner = value.inspect(debug).expect("no value to debug") 575 # prints: Debugging: "foo" 576 577 value: Option[str] = Some(None) 578 value.inspect(debug) # prints nothing 579 """ 580 if self._value is not None: 581 f(self._value) 582 583 return self 584 585 # *- Builder methods *- 586 587 def iter(self) -> _iter.Iterator[T]: 588 """Returns an iterator over the contained value. 589 590 Example 591 ------- 592 ```py 593 from sain import Some 594 value = Some("gg").iter() 595 assert value.next() == Some("gg") 596 597 value: Option[int] = Some(None) 598 assert value.iter().next().is_none() 599 ``` 600 """ 601 if self._value is None: 602 return _iter.empty() 603 604 return _iter.once(self._value) 605 606 # *- Boolean checks *- 607 608 def is_some(self) -> bool: 609 """Returns `True` if the contained value is not `None`, otherwise returns `False`. 610 611 Example 612 ------- 613 ```py 614 value = Some(5) 615 print(value.is_some()) 616 # True 617 618 value: Option[int] = Some(None) 619 print(value.is_some()) 620 # False 621 ``` 622 """ 623 return self._value is not None 624 625 def is_some_and(self, predicate: Fn[T, bool]) -> bool: 626 """Returns `True` if the contained value is not `None` and 627 the predicate returns `True`, otherwise returns `False`. 628 629 Example 630 ------- 631 ```py 632 value = Some(5) 633 print(value.is_some_and(lambda x: x > 3)) 634 # True 635 636 value: Option[int] = Some(None) 637 print(value.is_some_and(lambda x: x > 3)) 638 # False 639 ``` 640 """ 641 return self._value is not None and predicate(self._value) 642 643 def is_none(self) -> bool: 644 """Returns `True` if the contained value is `None`, otherwise returns `False`. 645 646 Example 647 ------- 648 ```py 649 value = Some(5) 650 print(value.is_none()) 651 # False 652 653 value: Option[int] = Some(None) 654 print(value.is_none()) 655 # True 656 ``` 657 """ 658 return self._value is None 659 660 def is_none_or(self, f: Fn[T, bool]) -> bool: 661 """Returns `True` if the contained value is `None` or the predicate returns `True`, 662 otherwise returns `False`. 663 664 Example 665 ------- 666 ```py 667 value = Some(5) 668 print(value.is_none_or(lambda x: x > 3)) 669 # False 670 671 value: Option[int] = Some(None) 672 print(value.is_none_or(lambda x: x > 3)) 673 # True 674 ``` 675 """ 676 match self._value: 677 case None: 678 return True 679 case x: 680 return f(x) 681 682 def __repr__(self) -> str: 683 if self._value is None: 684 return "None" 685 return f"Some({self._value!r})" 686 687 __str__ = __repr__ 688 689 def __invert__(self) -> T: 690 return self.unwrap() 691 692 def __or__(self, other: T) -> T: 693 return self.unwrap_or(other) 694 695 def __bool__(self) -> bool: 696 return self.is_some() 697 698 def __eq__(self, other: object) -> bool: 699 if not isinstance(other, Some): 700 return NotImplemented 701 702 return self._value == other._value # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType] 703 704 def __ne__(self, other: object) -> bool: 705 return not self.__eq__(other) 706 707 def __hash__(self) -> int: 708 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 transpose(self) -> T | None: 104 """Convert `Option[T]` into `T | None`. 105 106 Examples 107 -------- 108 ```py 109 opt = Some('char') 110 x = opt.transpose() 111 assert x == 'char' 112 113 opt = Some(None) 114 assert opt.transpose() is None 115 ``` 116 """ 117 return self._value
Convert Option[T]
into T | None
.
Examples
opt = Some('char')
x = opt.transpose()
assert x == 'char'
opt = Some(None)
assert opt.transpose() 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("Called `Option.unwrap()` on `None`.") from None 143 144 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
.
146 def unwrap_or(self, default: T, /) -> T: 147 """Unwrap the inner value either returning if its not `None` or returning `default`. 148 149 Example 150 ------- 151 ```py 152 value = Some(5) 153 print(value.unwrap_or(10)) 154 # 5 155 156 # Type hint is required here. 157 value: Option[int] = Some(None) 158 print(value.unwrap_or(10)) 159 # 10 160 ``` 161 """ 162 if self._value is None: 163 return default 164 165 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
167 def unwrap_or_else(self, f: FnOnce[T], /) -> T: 168 """Unwrap the inner value either returning if its not `None` or calling `f` to get a default value. 169 170 Example 171 ------- 172 ```py 173 value = Some(5) 174 print(value.unwrap_or_else(lambda: 10)) 175 # 5 176 177 value: Option[bool] = Some(None) 178 print(value.unwrap_or_else(lambda: True)) 179 # True 180 ``` 181 """ 182 if self._value is None: 183 return f() 184 185 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
187 @macros.unsafe 188 def unwrap_unchecked(self) -> T: 189 """Returns the contained Some value without checking that the value is not None. 190 191 Example 192 ------- 193 ```py 194 v: Option[float] = Some(1.2) 195 v.unwrap_unchecked() # 1.2 196 197 v: Option[float] = Some(None) 198 print(v.unwrap_unchecked()) # Undefined Behavior 199 ``` 200 """ 201 #! SAFETY: The caller guarantees that the value is not None. 202 return self._value # pyright: 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.
204 def expect(self, message: str, /) -> T: 205 """Returns the contained value if it is not `None` otherwise raises a `RuntimeError`. 206 207 Example 208 ------- 209 ```py 210 value = Some("Hello") 211 212 print(value.expect("Value is None")) 213 # "Hello" 214 215 value: Option[str] = Some(None) 216 print(value.expect("Value is None")) 217 # RuntimeError("Value is None") 218 ``` 219 """ 220 if self._value is None: 221 raise RuntimeError(message) 222 223 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")
227 def map(self, f: Fn[T, U], /) -> Option[U]: 228 """Map the inner value to another type. Returning `Some(None)` if `T` is `None`. 229 230 Example 231 ------- 232 ```py 233 value = Some(5.0) 234 235 print(value.map(lambda x: x * 2.0)) 236 # Some(10.0) 237 238 value: Option[bool] = Some(None) 239 print(value) 240 # Some(None) 241 ``` 242 """ 243 if self._value is None: 244 return NOTHING # pyright: ignore 245 246 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)
248 def map_or(self, default: U, f: Fn[T, U], /) -> U: 249 """Map the inner value to another type or return `default` if its `None`. 250 251 Example 252 ------- 253 ```py 254 value: Option[float] = Some(5.0) 255 256 # map to int. 257 print(value.map_or(0, int)) 258 # 6 259 260 value: Option[float] = Some(None) 261 print(value.map_or(0, int) 262 # 0 263 ``` 264 """ 265 if self._value is None: 266 return default 267 268 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
270 def map_or_else(self, default: FnOnce[U], f: Fn[T, U], /) -> U: 271 """Map the inner value to another type, or return `default()` if its `None`. 272 273 Example 274 ------- 275 ```py 276 def default() -> int: 277 return sys.getsizeof(object()) 278 279 value: Option[float] = Some(5.0) 280 281 # map to int. 282 print(value.map_or_else(default, int)) 283 # 6 284 285 value: Option[float] = Some(None) 286 print(value.map_or_else(default, int) 287 # 28 <- size of object() 288 ``` 289 """ 290 if self._value is None: 291 return default() 292 293 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()
295 def filter(self, predicate: Fn[T, bool]) -> Option[T]: 296 """Returns `Some(None)` if the contained value is `None`, 297 298 otherwise calls the predicate and returns `Some(T)` if the predicate returns `True`. 299 300 Example 301 ------- 302 ```py 303 value = Some([1, 2, 3]) 304 305 print(value.filter(lambda x: 1 in x)) 306 # Some([1, 2, 3]) 307 308 value: Option[int] = Some([1, 2, 3]) # or Some(None) 309 print(value.filter(lambda x: 1 not in x)) 310 # None 311 ``` 312 """ 313 if (value := self._value) is not None: 314 if predicate(value): 315 return Some(value) 316 317 return NOTHING # pyright: ignore
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
319 def ok_or(self, err: U) -> _result.Result[T, U]: 320 """Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err)`. 321 322 Example 323 ------- 324 ```py 325 xyz: Option[str] = Some("foo") 326 assert xyz.ok_or(None) == Ok("foo") 327 328 xyz: Option[str] = Some(None) 329 assert xyz.ok_or(None) == Err(None) 330 ``` 331 """ 332 if self._value is None: 333 return _result.Err(err) 334 335 return _result.Ok(self._value)
Transforms the Option<T>
into a Result<T, E>
, mapping Some(v)
to Ok(v)
and None
to Err(err)
.
Example
xyz: Option[str] = Some("foo")
assert xyz.ok_or(None) == Ok("foo")
xyz: Option[str] = Some(None)
assert xyz.ok_or(None) == Err(None)
337 def ok_or_else(self, err: FnOnce[U]) -> _result.Result[T, U]: 338 """Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err())`. 339 340 Example 341 ------- 342 ```py 343 xyz: Option[str] = Some("foo") 344 assert xyz.ok_or(None) == Ok("foo") 345 346 xyz: Option[str] = Some(None) 347 assert xyz.ok_or(None) == Err(None) 348 ``` 349 """ 350 if self._value is None: 351 return _result.Err(err()) 352 353 return _result.Ok(self._value)
Transforms the Option<T>
into a Result<T, E>
, mapping Some(v)
to Ok(v)
and None
to Err(err())
.
Example
xyz: Option[str] = Some("foo")
assert xyz.ok_or(None) == Ok("foo")
xyz: Option[str] = Some(None)
assert xyz.ok_or(None) == Err(None)
355 def zip(self, other: Option[U]) -> Option[tuple[T, U]]: 356 """Zips `self` with `other`. 357 358 if `self` is `Some(s)` and other is `Some(o)`, this returns `Some((s, o))` otherwise `None`. 359 360 Example 361 ------- 362 ```py 363 x = Some(1) 364 y = Some("hi") 365 z: Option[str] = Some(None) 366 367 assert x.zip(y) == Some((1, "hi")) 368 assert x.zip(z) == Some(None) 369 ``` 370 """ 371 if self._value is not None and other._value is not None: 372 return Some((self._value, other._value)) 373 374 return NOTHING # type: ignore
Zips self
with other
.
if self
is Some(s)
and other is Some(o)
, this returns Some((s, o))
otherwise None
.
Example
x = Some(1)
y = Some("hi")
z: Option[str] = Some(None)
assert x.zip(y) == Some((1, "hi"))
assert x.zip(z) == Some(None)
376 def zip_with( 377 self, other: Option[U], f: collections.Callable[[T, U], T_co] 378 ) -> Option[T_co]: 379 """Zips `self` with `other` using function `f`. 380 381 if `self` is `Some(s)` and other is `Some(o)`, this returns `Some(f(s, o))` otherwise `None`. 382 383 Example 384 ------- 385 ```py 386 @dataclass 387 class Point: 388 x: float 389 y: float 390 391 x, y = Some(32.1), Some(42.4) 392 assert x.zip_with(y, Point) == Some(Point(32.1, 42.4)) 393 ``` 394 """ 395 if self._value is not None and other._value is not None: 396 return Some(f(self._value, other._value)) 397 398 return NOTHING # type: ignore
Zips self
with other
using function f
.
if self
is Some(s)
and other is Some(o)
, this returns Some(f(s, o))
otherwise None
.
Example
@dataclass
class Point:
x: float
y: float
x, y = Some(32.1), Some(42.4)
assert x.zip_with(y, Point) == Some(Point(32.1, 42.4))
402 def take(self) -> Option[T]: 403 """Take the value from `Self`, Setting it to `None`. 404 405 Example 406 ------- 407 ```py 408 original = Some("Hi") 409 new = original.take() 410 411 print(original, new) 412 # None, Some("Hi") 413 ``` 414 """ 415 if self._value is None: 416 return NOTHING # pyright: ignore 417 418 val = self._value 419 self._value = None 420 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")
422 def take_if(self, predicate: collections.Callable[[T], bool]) -> Option[T]: 423 """Take the value from `Self`, Setting it to `None` only if predicate returns `True`. 424 425 Example 426 ------- 427 ```py 428 def validate(email: str) -> bool: 429 # you can obviously validate this better. 430 return email.find('@') == 1 431 432 original = Some("flex@gg.com") 433 valid = original.take_if(validate) 434 assert is_allowed.is_some() and original.is_none() 435 436 original = Some("mail.example.com") 437 invalid = original.take_if(validate) 438 assert invalid.is_none() and original.is_some() 439 ``` 440 """ 441 if self.map_or(False, predicate): 442 return self.take() 443 444 return NOTHING # pyright: ignore
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()
446 def replace(self, value: T) -> Option[T]: 447 """Replace the contained value with another value. 448 449 Use `Option.insert` if you want to return the original value 450 that got inserted instead of `self` 451 452 Example 453 ------- 454 ```py 455 value: Option[str] = Some(None) 456 value.replace("Hello") 457 # Some("Hello") 458 ``` 459 """ 460 self._value = value 461 return self
Replace the contained value with another value.
Use Option.insert
if you want to return the original value
that got inserted instead of self
Example
value: Option[str] = Some(None)
value.replace("Hello")
# Some("Hello")
463 def insert(self, value: T) -> T: 464 """Insert a value into the option, and then return a reference to it. 465 466 This will overwrite the old value if it was already contained. 467 468 Example 469 ------- 470 ```py 471 flag: Option[bool] = Some(None) 472 flag_ref = flag.insert(True) 473 assert flag_ref == True 474 assert flag.unwrap() == True 475 ``` 476 """ 477 self._value = value 478 return value
Insert a value into the option, and then return a reference to it.
This will overwrite the old value if it was already contained.
Example
flag: Option[bool] = Some(None)
flag_ref = flag.insert(True)
assert flag_ref == True
assert flag.unwrap() == True
480 def get_or_insert(self, value: T) -> T: 481 """Insert a value into the option if it was `None`, 482 and then return a reference to it. 483 484 Example 485 ------- 486 ```py 487 state: Option[bool] = Some(None) 488 assert state.get_or_insert(True) is True 489 assert state.get_or_insert(False) is True 490 ``` 491 """ 492 if self._value is not None: 493 return self._value 494 495 self._value = value 496 return value
Insert a value into the option if it was None
,
and then return a reference to it.
Example
state: Option[bool] = Some(None)
assert state.get_or_insert(True) is True
assert state.get_or_insert(False) is True
498 def get_or_insert_with(self, f: FnOnce[T]) -> T: 499 """Insert a value into the option computed from `f()` if it was `None`, 500 and then return a reference to it. 501 502 Example 503 ------- 504 ```py 505 flag: Option[bool] = Some(None) 506 flag_ref = flag.insert(True) 507 assert flag_ref == True 508 assert flag.unwrap() == True 509 ``` 510 """ 511 if self._value is not None: 512 return self._value 513 514 v = self._value = f() 515 return v
Insert a value into the option computed from f()
if it was None
,
and then return a reference to it.
Example
flag: Option[bool] = Some(None)
flag_ref = flag.insert(True)
assert flag_ref == True
assert flag.unwrap() == True
517 def and_ok(self, optb: Option[T]) -> Option[T]: 518 """Returns `None` if `self` or `optb` is `None`, otherwise return `optb`. 519 520 aliases: `Option::and` 521 522 Example 523 ------- 524 ```py 525 x = Some(1) 526 y: Option[str] = Some(None) 527 assert x.and_ok(y) == Some(None) 528 529 x: Option[str] = Some(None) 530 y = Some(1) 531 assert x.and_ok(y) == Some(None) 532 533 y: Option[str] = Some("hi") 534 y = Some(100) 535 assert x.and_ok(y) == Some(100) 536 ``` 537 """ 538 if self._value is None or optb._value is None: 539 return optb 540 541 return NOTHING # pyright: ignore
Returns None
if self
or optb
is None
, otherwise return optb
.
aliases: Option::and
Example
x = Some(1)
y: Option[str] = Some(None)
assert x.and_ok(y) == Some(None)
x: Option[str] = Some(None)
y = Some(1)
assert x.and_ok(y) == Some(None)
y: Option[str] = Some("hi")
y = Some(100)
assert x.and_ok(y) == Some(100)
543 def and_then(self, f: Fn[T, Option[T]]) -> Option[T]: 544 """Returns `Some(None)` if the contained value is `None`, otherwise call `f()` 545 on `T` and return `Option[T]`. 546 547 Example 548 ------- 549 ```py 550 value = Some(5) 551 print(value.and_then(lambda x: Some(x * 2))) 552 # Some(10) 553 554 value: Option[int] = Some(None) 555 print(value.and_then(lambda x: Some(x * 2))) 556 # Some(None) 557 ``` 558 """ 559 if self._value is None: 560 return NOTHING # pyright: ignore 561 562 return f(self._value)
Returns Some(None)
if the contained value is None
, otherwise call f()
on T
and return Option[T]
.
Example
value = Some(5)
print(value.and_then(lambda x: Some(x * 2)))
# Some(10)
value: Option[int] = Some(None)
print(value.and_then(lambda x: Some(x * 2)))
# Some(None)
564 def inspect(self, f: Fn[T, typing.Any]) -> Option[T]: 565 """Calls `f()` on the contained value if it was `Some(v)`, otherwise does nothing. 566 567 Example 568 ------- 569 ```py 570 def debug(x: str) -> None: 571 print("Debugging:", x) 572 573 value = Some("foo") 574 inner = value.inspect(debug).expect("no value to debug") 575 # prints: Debugging: "foo" 576 577 value: Option[str] = Some(None) 578 value.inspect(debug) # prints nothing 579 """ 580 if self._value is not None: 581 f(self._value) 582 583 return self
Calls f()
on the contained value if it was Some(v)
, otherwise does nothing.
Example
```py def debug(x: str) -> None: print("Debugging:", x)
value = Some("foo") inner = value.inspect(debug).expect("no value to debug")
prints: Debugging: "foo"
value: Option[str] = Some(None) value.inspect(debug) # prints nothing
587 def iter(self) -> _iter.Iterator[T]: 588 """Returns an iterator over the contained value. 589 590 Example 591 ------- 592 ```py 593 from sain import Some 594 value = Some("gg").iter() 595 assert value.next() == Some("gg") 596 597 value: Option[int] = Some(None) 598 assert value.iter().next().is_none() 599 ``` 600 """ 601 if self._value is None: 602 return _iter.empty() 603 604 return _iter.once(self._value)
Returns an iterator over the contained value.
Example
from sain import Some
value = Some("gg")sain.iter()
assert value.next() == Some("gg")
value: Option[int] = Some(None)
assert value.iter().next().is_none()
608 def is_some(self) -> bool: 609 """Returns `True` if the contained value is not `None`, otherwise returns `False`. 610 611 Example 612 ------- 613 ```py 614 value = Some(5) 615 print(value.is_some()) 616 # True 617 618 value: Option[int] = Some(None) 619 print(value.is_some()) 620 # False 621 ``` 622 """ 623 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
625 def is_some_and(self, predicate: Fn[T, bool]) -> bool: 626 """Returns `True` if the contained value is not `None` and 627 the predicate returns `True`, otherwise returns `False`. 628 629 Example 630 ------- 631 ```py 632 value = Some(5) 633 print(value.is_some_and(lambda x: x > 3)) 634 # True 635 636 value: Option[int] = Some(None) 637 print(value.is_some_and(lambda x: x > 3)) 638 # False 639 ``` 640 """ 641 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
643 def is_none(self) -> bool: 644 """Returns `True` if the contained value is `None`, otherwise returns `False`. 645 646 Example 647 ------- 648 ```py 649 value = Some(5) 650 print(value.is_none()) 651 # False 652 653 value: Option[int] = Some(None) 654 print(value.is_none()) 655 # True 656 ``` 657 """ 658 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
660 def is_none_or(self, f: Fn[T, bool]) -> bool: 661 """Returns `True` if the contained value is `None` or the predicate returns `True`, 662 otherwise returns `False`. 663 664 Example 665 ------- 666 ```py 667 value = Some(5) 668 print(value.is_none_or(lambda x: x > 3)) 669 # False 670 671 value: Option[int] = Some(None) 672 print(value.is_none_or(lambda x: x > 3)) 673 # True 674 ``` 675 """ 676 match self._value: 677 case None: 678 return True 679 case x: 680 return f(x)
Returns True
if the contained value is None
or the predicate returns True
,
otherwise returns False
.
Example
value = Some(5)
print(value.is_none_or(lambda x: x > 3))
# False
value: Option[int] = Some(None)
print(value.is_none_or(lambda x: x > 3))
# True
862@typing.final 863# @rustc_diagnostic_item("Iter") 864@diagnostic 865class Iter(Iterator[Item]): 866 """a lazy iterator that has its items ready in-memory. 867 868 This is similar to Rust `std::slice::Iter<T>` item which iterables can build 869 from this via `.iter()` method. 870 871 Example 872 ------- 873 ```py 874 iterator = Iter([1, 2, 3]) 875 876 # Limit the results to 2. 877 for item in iterator.take(2): 878 print(item) 879 # 1 880 # 2 881 882 # Filter the results. 883 for item in iterator.filter(lambda item: item > 1): 884 print(item) 885 # 2 886 # 3 887 # 3 888 889 # Indexing is supported. 890 print(iterator[0]) 891 # 1 892 ``` 893 894 Parameters 895 ---------- 896 items: `Iterable[Item]` 897 The items to iterate over. This can be anything that implements `__iter__` and `__next__`. 898 """ 899 900 __slots__ = ("_it",) 901 902 def __init__(self, iterable: collections.Iterable[Item]) -> None: 903 self._it = iter(iterable) 904 905 def clone(self) -> Iter[Item]: 906 """Return a copy of this iterator. 907 908 ```py 909 it = Iterator([1, 2, 3]) 910 911 for i in it.clone(): 912 ... 913 914 # The actual iterator hasn't been exhausted. 915 assert it.count() == 3 916 ``` 917 """ 918 return Iter(copy.copy(self._it)) 919 920 def __next__(self) -> Item: 921 try: 922 return next(self._it) 923 except StopIteration: 924 raise 925 926 def __getitem__(self, index: int) -> Option[Item]: 927 try: 928 return self.skip(index).first() 929 except IndexError: 930 unreachable() 931 932 def __contains__(self, item: Item) -> bool: 933 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__
.
905 def clone(self) -> Iter[Item]: 906 """Return a copy of this iterator. 907 908 ```py 909 it = Iterator([1, 2, 3]) 910 911 for i in it.clone(): 912 ... 913 914 # The actual iterator hasn't been exhausted. 915 assert it.count() == 3 916 ``` 917 """ 918 return Iter(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
115class Iterator( 116 typing.Generic[Item], 117 abc.ABC, 118 _default.Default["Empty[Item]"], 119): 120 """An abstract interface for dealing with iterators. 121 122 This is exactly the same trait as `core::iter::Iterator` trait from Rust. 123 124 This is the main interface that any type can implement by basically inheriting from it. 125 The method `__next__` is the only method that needs to be implemented, You get all the other methods for free. 126 127 If you want to use a ready iterator for general purposes, Use `Iter`. This interface is only for implementors 128 and type hints. 129 130 Example 131 ------- 132 ```py 133 @dataclass 134 class Counter(Iterator[int]): 135 start: int = 0 136 stop: int | None = None 137 138 # implement the required method. 139 def __next__(self) -> int: 140 result = self.start 141 self.start += 1 142 143 if self.stop is not None and result >= self.stop: 144 raise StopIteration 145 146 return result 147 148 counter = Counter(start=0, stop=10) 149 for i in counter.map(lambda x: x * 2): # multiply each number 150 ... 151 ``` 152 """ 153 154 @abc.abstractmethod 155 def __next__(self) -> Item: 156 raise NotImplementedError 157 158 ################### 159 # const functions # 160 ################### 161 162 @staticmethod 163 @typing.final 164 def default() -> Empty[Item]: 165 """Return the default iterator for this type. It returns an empty iterator. 166 167 Example 168 ------- 169 ```py 170 it: Iterator[int] = Iter.default() 171 assert t.next().is_none() 172 ``` 173 """ 174 return Empty() 175 176 @typing.overload 177 @typing.final 178 def collect(self) -> collections.Sequence[Item]: ... 179 180 @typing.overload 181 @typing.final 182 def collect( 183 self, *, cast: collections.Callable[[Item], OtherItem] 184 ) -> collections.Sequence[OtherItem]: ... 185 186 @typing.final 187 def collect( 188 self, *, cast: collections.Callable[[Item], OtherItem] | None = None 189 ) -> collections.Sequence[Item] | collections.Sequence[OtherItem]: 190 """Collects all items in the iterator into an immutable sequence. 191 192 Example 193 ------- 194 ```py 195 iterator = Iter(range(3)) 196 iterator.collect() 197 # (0, 1, 2, 3) 198 iterator.collect(cast=str) # Map each element and collect it. 199 # ('0', '1', '2', '3') 200 ``` 201 202 Parameters 203 ---------- 204 cast: `T | None` 205 An optional type to cast the items into. 206 If not provided the items will be returned as it's original type. 207 """ 208 if cast is not None: 209 return tuple(cast(i) for i in self) 210 211 return tuple(_ for _ in self) 212 213 @typing.final 214 def collect_into(self, collection: Collector[Item]) -> None: 215 """Consume this iterator, extending all items in the iterator into a mutable `collection`. 216 217 Example 218 ------- 219 ```py 220 iterator = Iter([1, 1, 2, 3, 4, 2, 6]) 221 uniques = set() 222 iterator.collect_into(uniques) 223 # assert uniques == {1, 2, 3, 4, 6} 224 ``` 225 226 Parameters 227 ---------- 228 collection: `MutableSequence[T]` | `set[T]` 229 The collection to extend the items in this iterator with. 230 """ 231 if isinstance(collection, collections.MutableSequence): 232 collection.extend(_ for _ in self) 233 elif isinstance(collection, collections.MutableSet): 234 collection.update(_ for _ in self) 235 else: 236 for idx, item in enumerate(self): 237 collection[idx] = item 238 239 @typing.final 240 def to_vec(self) -> vec.Vec[Item]: 241 """Convert this iterator into `Vec[T]`. 242 243 Example 244 ------- 245 ```py 246 it = sain.iter.once(0) 247 vc = it.to_vec() 248 249 assert to_vec == [0] 250 ``` 251 """ 252 return vec.Vec(_ for _ in self) 253 254 @typing.final 255 def sink(self) -> None: 256 """Consume all elements from this iterator, flushing it into the sink. 257 258 Example 259 ------- 260 ```py 261 it = Iter((1, 2, 3)) 262 it.sink() 263 assert it.next().is_none() 264 ``` 265 """ 266 for _ in self: 267 pass 268 269 @typing.final 270 def raw_parts(self) -> collections.Generator[Item, None, None]: 271 """Decompose all elements from this iterator, yielding it one by one 272 as a normal generator. 273 274 This mainly used for objects that needs to satisfy its exact type. 275 276 ```py 277 it = Iter("cba") 278 sort = sorted(it.raw_parts()) 279 280 assert it.count() == 0 281 assert sort == ["a", "b", "c"] 282 ``` 283 """ 284 for item in self: 285 yield item 286 287 ################## 288 # default impl's # 289 ################## 290 291 def next(self) -> Option[Item]: 292 """Advance the iterator, Returning the next item, `Some(None)` if all items yielded. 293 294 Example 295 ------- 296 ```py 297 iterator = Iter(["1", "2"]) 298 assert iterator.next() == Some("1") 299 assert iterator.next() == Some("2") 300 assert iterator.next().is_none() 301 ``` 302 """ 303 try: 304 return _option.Some(self.__next__()) 305 except StopIteration: 306 # ! SAFETY: No more items in the iterator. 307 return _option.NOTHING # pyright: ignore 308 309 def cloned(self) -> Cloned[Item]: 310 """Creates an iterator which shallow copies its elements by reference. 311 312 If you need a copy of the actual iterator and not the elements. 313 use `Iter.clone()` 314 315 .. note:: 316 This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html) 317 on each item that is being yielded. 318 319 Example 320 ------- 321 ```py 322 @dataclass 323 class User: 324 users_ids: list[int] = [] 325 326 # An iterator which elements points to the same user. 327 user = User() 328 it = Iter((user, user)) 329 330 for u in it.cloned(): 331 u.user_ids.append(1) 332 333 # We iterated over the same user pointer twice and appended "1" 334 # since `copy` returns a shallow copy of nested structures. 335 assert len(user.user_ids) == 2 336 ``` 337 """ 338 return Cloned(self) 339 340 def copied(self) -> Copied[Item]: 341 """Creates an iterator which copies all of its elements by value. 342 343 If you only need a copy of the item reference, Use `.cloned()` instead. 344 345 .. note:: 346 This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html) 347 on each item that is being yielded. 348 349 Example 350 ------- 351 ```py 352 @dataclass 353 class User: 354 users_ids: list[int] = [] 355 356 # An iterator which elements points to the same user. 357 user = User() 358 it = Iter((user, user)) 359 360 for u in it.copied(): 361 # A new list is created for each item. 362 u.user_ids.append(1) 363 364 # The actual list is untouched since we consumed a deep copy of it. 365 assert len(user.user_ids) == 0 366 ``` 367 """ 368 return Copied(self) 369 370 def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]: 371 """Maps each item in the iterator to another type. 372 373 Example 374 ------- 375 ```py 376 iterator = Iter(["1", "2", "3"]).map(int) 377 378 for item in iterator: 379 assert isinstance(item, int) 380 ``` 381 382 Parameters 383 ---------- 384 predicate: `Callable[[Item], OtherItem]` 385 The function to map each item in the iterator to the other type. 386 """ 387 return Map(self, fn) 388 389 def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]: 390 """Filters the iterator to only yield items that match the predicate. 391 392 Example 393 ------- 394 ```py 395 places = Iter(['London', 'Paris', 'Los Angeles']) 396 for place in places.filter(lambda place: place.startswith('L')): 397 print(place) 398 399 # London 400 # Los Angeles 401 ``` 402 """ 403 return Filter(self, predicate) 404 405 def take(self, count: int) -> Take[Item]: 406 """Take the first number of items until the number of items 407 are yielded or the end of the iterator is exhausted. 408 409 Example 410 ------- 411 ```py 412 iterator = Iter(['c', 'x', 'y']) 413 414 for x in iterator.take(2): 415 assert x in ('c', 'x') 416 417 # <Iter(['c', 'x'])> 418 ``` 419 """ 420 return Take(self, count) 421 422 def skip(self, count: int) -> Skip[Item]: 423 """Skips the first number of items in the iterator. 424 425 Example 426 ------- 427 ```py 428 iterator = Iter((1, 2, 3, 4)) 429 for i in iterator.skip(2): 430 print(i) 431 432 # 3 433 # 4 434 ``` 435 """ 436 return Skip(self, count) 437 438 def enumerate(self, *, start: int = 0) -> Enumerate[Item]: 439 """Create a new iterator that yields a tuple of the index and item. 440 441 Example 442 ------- 443 ```py 444 iterator = Iter([1, 2, 3]) 445 for index, item in iterator.enumerate(): 446 print(index, item) 447 448 # 0 1 449 # 1 2 450 # 2 3 451 ``` 452 """ 453 return Enumerate(self, start) 454 455 def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]: 456 """yields items from the iterator while predicate returns `True`. 457 458 The rest of the items are discarded as soon as the predicate returns `False` 459 460 Example 461 ------- 462 ```py 463 iterator = Iter(['a', 'ab', 'xd', 'ba']) 464 for x in iterator.take_while(lambda x: 'a' in x): 465 print(x) 466 467 # a 468 # ab 469 ``` 470 471 Parameters 472 ---------- 473 predicate: `collections.Callable[[Item], bool]` 474 The function to predicate each item in the iterator. 475 """ 476 return TakeWhile(self, f) 477 478 def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]: 479 """Yields items from the iterator while predicate returns `False`. 480 481 Example 482 ------- 483 ```py 484 iterator = Iter(['a', 'ab', 'xd', 'ba']) 485 for x in iterator.drop_while(lambda x: 'a' in x): 486 print(x) 487 488 # xd 489 # ba 490 ``` 491 492 Parameters 493 ---------- 494 predicate: `collections.Callable[[Item], bool]` 495 The function to predicate each item in the iterator. 496 """ 497 return DropWhile(self, f) 498 499 def chunks(self, chunk_size: int, /) -> Chunks[Item]: 500 """Returns an iterator over `chunk_size` elements of the iterator at a time, 501 starting at the beginning of the iterator. 502 503 Example 504 ------- 505 ```py 506 iter = Iter(['a', 'b', 'c', 'd', 'e']) 507 chunks = iter.chunks() 508 assert chunks.next().unwrap() == ['a', 'b'] 509 assert chunks.next().unwrap() == ['c', 'd'] 510 assert chunks.next().unwrap() == ['e'] 511 assert chunks.next().is_none() 512 ``` 513 """ 514 return Chunks(self, chunk_size) 515 516 def all(self, predicate: collections.Callable[[Item], bool]) -> bool: 517 """Return `True` if all items in the iterator match the predicate. 518 519 Example 520 ------- 521 ```py 522 iterator = Iter([1, 2, 3]) 523 while iterator.all(lambda item: isinstance(item, int)): 524 print("Still all integers") 525 continue 526 # Still all integers 527 ``` 528 529 Parameters 530 ---------- 531 predicate: `collections.Callable[[Item], bool]` 532 The function to test each item in the iterator. 533 """ 534 return all(predicate(item) for item in self) 535 536 def any(self, predicate: collections.Callable[[Item], bool]) -> bool: 537 """`True` if any items in the iterator match the predicate. 538 539 Example 540 ------- 541 ```py 542 iterator = Iter([1, 2, 3]) 543 if iterator.any(lambda item: isinstance(item, int)): 544 print("At least one item is an int.") 545 # At least one item is an int. 546 ``` 547 548 Parameters 549 ---------- 550 predicate: `collections.Callable[[Item], bool]` 551 The function to test each item in the iterator. 552 """ 553 return any(predicate(item) for item in self) 554 555 def zip( 556 self, other: collections.Iterable[OtherItem] 557 ) -> Iterator[tuple[Item, OtherItem]]: 558 """Zips the iterator with another iterable. 559 560 Example 561 ------- 562 ```py 563 iterator = Iter([1, 2, 3]) 564 for item, other_item in iterator.zip([4, 5, 6]): 565 assert item == other_item 566 <Iter([(1, 4), (2, 5), (3, 6)])> 567 ``` 568 569 Parameters 570 ---------- 571 other: `Iter[OtherItem]` 572 The iterable to zip with. 573 574 Returns 575 ------- 576 `Iter[tuple[Item, OtherItem]]` 577 The zipped iterator. 578 579 """ 580 return Iter(zip(self.raw_parts(), other)) 581 582 def sort( 583 self, 584 *, 585 key: collections.Callable[[Item], _typeshed.SupportsRichComparison], 586 reverse: bool = False, 587 ) -> Iterator[Item]: 588 """Sorts the iterator. 589 590 Example 591 ------- 592 ```py 593 iterator = Iter([3, 1, 6, 7]) 594 for item in iterator.sort(key=lambda item: item < 3): 595 print(item) 596 # 1 597 # 3 598 # 6 599 # 7 600 ``` 601 602 Parameters 603 ---------- 604 key: `collections.Callable[[Item], Any]` 605 The function to sort by. 606 reverse: `bool` 607 Whether to reverse the sort. 608 609 """ 610 return Iter(sorted(self.raw_parts(), key=key, reverse=reverse)) 611 612 def reversed(self) -> Iterator[Item]: 613 """Returns a new iterator that yields the items in the iterator in reverse order. 614 615 Example 616 ------- 617 ```py 618 iterator = Iter([3, 1, 6, 7]) 619 for item in iterator.reversed(): 620 print(item) 621 # 7 622 # 6 623 # 1 624 # 3 625 ``` 626 """ 627 # NOTE: In order to reverse the iterator we need to 628 # first collect it into some collection. 629 return Iter(reversed(self.collect())) 630 631 def union(self, other: collections.Iterable[Item]) -> Iterator[Item]: 632 """Returns a new iterator that yields all items from both iterators. 633 634 Example 635 ------- 636 ```py 637 iterator = Iter([1, 2, 3]) 638 other = [4, 5, 6] 639 640 for item in iterator.union(other): 641 print(item) 642 # 1 643 # 2 644 # 3 645 # 4 646 # 5 647 # 6 648 ``` 649 650 Parameters 651 ---------- 652 other: `Iter[Item]` 653 The iterable to union with. 654 """ 655 return Iter(itertools.chain(self.raw_parts(), other)) 656 657 def first(self) -> Option[Item]: 658 """Returns the first item in the iterator. 659 660 Example 661 ------- 662 ```py 663 iterator = Iter([3, 1, 6, 7]) 664 iterator.first().is_some_and(lambda x: x == 3) 665 ``` 666 """ 667 return self.take(1).next() 668 669 def last(self) -> Option[Item]: 670 """Returns the last item in the iterator. 671 672 Example 673 ------- 674 ```py 675 iterator = Iter([3, 1, 6, 7]) 676 iterator.last().is_some_and(lambda x: x == 7) 677 ``` 678 """ 679 return self.reversed().first() 680 681 def count(self) -> int: 682 """Return the count of elements in memory this iterator has. 683 684 Example 685 ------- 686 ```py 687 it = Iter(range(3)) 688 assert it.count() == 3 689 ``` 690 """ 691 count = 0 692 for _ in self: 693 count += 1 694 695 return count 696 697 def find(self, predicate: collections.Callable[[Item], bool]) -> Option[Item]: 698 """Searches for an element of an iterator that satisfies a predicate. 699 700 If you want the position of the element, use `Iterator.position` instead. 701 702 `find()` takes a lambda that returns true or false. It applies this closure to each element of the iterator, 703 and if any of them return true, then find() returns `Some(element)`. If they all return false, it returns None. 704 705 Example 706 ------- 707 ```py 708 it = Iter(range(10)) 709 item = it.find(lambda num: num > 5) 710 print(item) # 6 711 ``` 712 """ 713 for item in self: 714 if predicate(item): 715 return _option.Some(item) 716 717 # no more items 718 return _option.NOTHING # pyright: ignore 719 720 def position(self, predicate: collections.Callable[[Item], bool]) -> Option[int]: 721 """Searches for the position of an element in the iterator that satisfies a predicate. 722 723 If you want the object itself, use `Iterator.find` instead. 724 725 `position()` takes a lambda that returns true or false. It applies this closure to each element of the iterator, 726 and if any of them return true, then position() returns `Some(position_of_element)`. If they all return false, it returns None. 727 728 Example 729 ------- 730 ```py 731 it = Iter(range(10)) 732 position = it.find(lambda num: num > 5) 733 assert position.unwrap() == 6 734 ``` 735 """ 736 for position, value in self.enumerate(): 737 if predicate(value): 738 return _option.Some(position) 739 740 # no more items 741 return _option.NOTHING # pyright: ignore 742 743 def fold( 744 self, init: OtherItem, f: collections.Callable[[OtherItem, Item], OtherItem] 745 ) -> OtherItem: 746 """Folds every element into an accumulator by applying an operation, returning the final result. 747 748 fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. 749 The closure returns the value that the accumulator should have for the next iteration. 750 751 The initial value is the value the accumulator will have on the first call. 752 753 After applying this closure to every element of the iterator, fold() returns the accumulator. 754 755 This operation is sometimes called ‘reduce’ or ‘inject’. 756 757 Example 758 ------- 759 ```py 760 a = Iter([1, 2, 3, 4]) 761 sum = a.fold(0, lambda acc, elem: acc + elem) 762 assert sum == 10 763 ``` 764 """ 765 accum = init 766 while True: 767 try: 768 x = self.__next__() 769 accum = f(accum, x) 770 except StopIteration: 771 break 772 773 return accum 774 775 def sum(self: Sum) -> int: 776 """Sums an iterator of a possible type that can be converted to an integer. 777 778 Example 779 ------- 780 ```py 781 numbers: Iterator[str] = Iter(["1", "2", "3"]) 782 total = numbers.sum() 783 assert total == 6 784 ``` 785 """ 786 return sum(int(_) for _ in self) 787 788 def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None: 789 """Calls `func` on each item in the iterator. 790 791 Example 792 ------- 793 ```py 794 iterator = Iter([1, 2, 3]) 795 iterator.for_each(lambda item: print(item)) 796 # 1 797 # 2 798 # 3 799 ``` 800 801 Parameters 802 ---------- 803 func: `collections.Callable[[Item], typing.Any]` 804 The function to call on each item in the iterator. 805 """ 806 for item in self: 807 func(item) 808 809 async def async_for_each( 810 self, 811 func: collections.Callable[ 812 [Item], collections.Coroutine[None, typing.Any, OtherItem] 813 ], 814 ) -> _result.Result[collections.Sequence[OtherItem], futures.SpawnError]: 815 """Calls the async function on each item in the iterator concurrently. 816 817 Example 818 ------- 819 ```py 820 async def create_user(username: str) -> None: 821 async with aiohttp.request("POST", f'.../{username}') as r: 822 return await r.json() 823 824 async def main(): 825 users = sain.into_iter(["danny", "legalia"]) 826 results = await users.async_for_each(lambda username: create_user(username)) 827 for k, v in results.unwrap().items(): 828 ... 829 ``` 830 831 Parameters 832 ---------- 833 func: `collections.Callable[[Item], Coroutine[None, Any, Any]]` 834 The async function to call on each item in the iterator. 835 """ 836 return await futures.spawn(*(func(item) for item in self)) 837 838 def __reversed__(self) -> Iterator[Item]: 839 return self.reversed() 840 841 def __setitem__(self, _: None, __: None) -> typing.NoReturn: 842 raise NotImplementedError( 843 f"{type(self).__name__} doesn't support item assignment." 844 ) from None 845 846 def __repr__(self) -> str: 847 return "<Iterator>" 848 849 def __copy__(self) -> Cloned[Item]: 850 return self.cloned() 851 852 def __deepcopy__(self) -> Copied[Item]: 853 return self.copied() 854 855 def __len__(self) -> int: 856 return self.count() 857 858 def __iter__(self) -> Iterator[Item]: 859 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 Counter(Iterator[int]):
start: int = 0
stop: int | None = None
# implement the required method.
def __next__(self) -> int:
result = self.start
self.start += 1
if self.stop is not None and result >= self.stop:
raise StopIteration
return result
counter = Counter(start=0, stop=10)
for i in counter.map(lambda x: x * 2): # multiply each number
...
162 @staticmethod 163 @typing.final 164 def default() -> Empty[Item]: 165 """Return the default iterator for this type. It returns an empty iterator. 166 167 Example 168 ------- 169 ```py 170 it: Iterator[int] = Iter.default() 171 assert t.next().is_none() 172 ``` 173 """ 174 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()
186 @typing.final 187 def collect( 188 self, *, cast: collections.Callable[[Item], OtherItem] | None = None 189 ) -> collections.Sequence[Item] | collections.Sequence[OtherItem]: 190 """Collects all items in the iterator into an immutable sequence. 191 192 Example 193 ------- 194 ```py 195 iterator = Iter(range(3)) 196 iterator.collect() 197 # (0, 1, 2, 3) 198 iterator.collect(cast=str) # Map each element and collect it. 199 # ('0', '1', '2', '3') 200 ``` 201 202 Parameters 203 ---------- 204 cast: `T | None` 205 An optional type to cast the items into. 206 If not provided the items will be returned as it's original type. 207 """ 208 if cast is not None: 209 return tuple(cast(i) for i in self) 210 211 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.
213 @typing.final 214 def collect_into(self, collection: Collector[Item]) -> None: 215 """Consume this iterator, extending all items in the iterator into a mutable `collection`. 216 217 Example 218 ------- 219 ```py 220 iterator = Iter([1, 1, 2, 3, 4, 2, 6]) 221 uniques = set() 222 iterator.collect_into(uniques) 223 # assert uniques == {1, 2, 3, 4, 6} 224 ``` 225 226 Parameters 227 ---------- 228 collection: `MutableSequence[T]` | `set[T]` 229 The collection to extend the items in this iterator with. 230 """ 231 if isinstance(collection, collections.MutableSequence): 232 collection.extend(_ for _ in self) 233 elif isinstance(collection, collections.MutableSet): 234 collection.update(_ for _ in self) 235 else: 236 for idx, item in enumerate(self): 237 collection[idx] = item
Consume this iterator, extending all items in the iterator into a mutable collection
.
Example
iterator = Iter([1, 1, 2, 3, 4, 2, 6])
uniques = set()
iterator.collect_into(uniques)
# assert uniques == {1, 2, 3, 4, 6}
Parameters
- collection (
MutableSequence[T]
|set[T]
): The collection to extend the items in this iterator with.
239 @typing.final 240 def to_vec(self) -> vec.Vec[Item]: 241 """Convert this iterator into `Vec[T]`. 242 243 Example 244 ------- 245 ```py 246 it = sain.iter.once(0) 247 vc = it.to_vec() 248 249 assert to_vec == [0] 250 ``` 251 """ 252 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]
254 @typing.final 255 def sink(self) -> None: 256 """Consume all elements from this iterator, flushing it into the sink. 257 258 Example 259 ------- 260 ```py 261 it = Iter((1, 2, 3)) 262 it.sink() 263 assert it.next().is_none() 264 ``` 265 """ 266 for _ in self: 267 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()
269 @typing.final 270 def raw_parts(self) -> collections.Generator[Item, None, None]: 271 """Decompose all elements from this iterator, yielding it one by one 272 as a normal generator. 273 274 This mainly used for objects that needs to satisfy its exact type. 275 276 ```py 277 it = Iter("cba") 278 sort = sorted(it.raw_parts()) 279 280 assert it.count() == 0 281 assert sort == ["a", "b", "c"] 282 ``` 283 """ 284 for item in self: 285 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"]
291 def next(self) -> Option[Item]: 292 """Advance the iterator, Returning the next item, `Some(None)` if all items yielded. 293 294 Example 295 ------- 296 ```py 297 iterator = Iter(["1", "2"]) 298 assert iterator.next() == Some("1") 299 assert iterator.next() == Some("2") 300 assert iterator.next().is_none() 301 ``` 302 """ 303 try: 304 return _option.Some(self.__next__()) 305 except StopIteration: 306 # ! SAFETY: No more items in the iterator. 307 return _option.NOTHING # pyright: ignore
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()
309 def cloned(self) -> Cloned[Item]: 310 """Creates an iterator which shallow copies its elements by reference. 311 312 If you need a copy of the actual iterator and not the elements. 313 use `Iter.clone()` 314 315 .. note:: 316 This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html) 317 on each item that is being yielded. 318 319 Example 320 ------- 321 ```py 322 @dataclass 323 class User: 324 users_ids: list[int] = [] 325 326 # An iterator which elements points to the same user. 327 user = User() 328 it = Iter((user, user)) 329 330 for u in it.cloned(): 331 u.user_ids.append(1) 332 333 # We iterated over the same user pointer twice and appended "1" 334 # since `copy` returns a shallow copy of nested structures. 335 assert len(user.user_ids) == 2 336 ``` 337 """ 338 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
340 def copied(self) -> Copied[Item]: 341 """Creates an iterator which copies all of its elements by value. 342 343 If you only need a copy of the item reference, Use `.cloned()` instead. 344 345 .. note:: 346 This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html) 347 on each item that is being yielded. 348 349 Example 350 ------- 351 ```py 352 @dataclass 353 class User: 354 users_ids: list[int] = [] 355 356 # An iterator which elements points to the same user. 357 user = User() 358 it = Iter((user, user)) 359 360 for u in it.copied(): 361 # A new list is created for each item. 362 u.user_ids.append(1) 363 364 # The actual list is untouched since we consumed a deep copy of it. 365 assert len(user.user_ids) == 0 366 ``` 367 """ 368 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
370 def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]: 371 """Maps each item in the iterator to another type. 372 373 Example 374 ------- 375 ```py 376 iterator = Iter(["1", "2", "3"]).map(int) 377 378 for item in iterator: 379 assert isinstance(item, int) 380 ``` 381 382 Parameters 383 ---------- 384 predicate: `Callable[[Item], OtherItem]` 385 The function to map each item in the iterator to the other type. 386 """ 387 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.
389 def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]: 390 """Filters the iterator to only yield items that match the predicate. 391 392 Example 393 ------- 394 ```py 395 places = Iter(['London', 'Paris', 'Los Angeles']) 396 for place in places.filter(lambda place: place.startswith('L')): 397 print(place) 398 399 # London 400 # Los Angeles 401 ``` 402 """ 403 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
405 def take(self, count: int) -> Take[Item]: 406 """Take the first number of items until the number of items 407 are yielded or the end of the iterator is exhausted. 408 409 Example 410 ------- 411 ```py 412 iterator = Iter(['c', 'x', 'y']) 413 414 for x in iterator.take(2): 415 assert x in ('c', 'x') 416 417 # <Iter(['c', 'x'])> 418 ``` 419 """ 420 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'])>
422 def skip(self, count: int) -> Skip[Item]: 423 """Skips the first number of items in the iterator. 424 425 Example 426 ------- 427 ```py 428 iterator = Iter((1, 2, 3, 4)) 429 for i in iterator.skip(2): 430 print(i) 431 432 # 3 433 # 4 434 ``` 435 """ 436 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
438 def enumerate(self, *, start: int = 0) -> Enumerate[Item]: 439 """Create a new iterator that yields a tuple of the index and item. 440 441 Example 442 ------- 443 ```py 444 iterator = Iter([1, 2, 3]) 445 for index, item in iterator.enumerate(): 446 print(index, item) 447 448 # 0 1 449 # 1 2 450 # 2 3 451 ``` 452 """ 453 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
455 def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]: 456 """yields items from the iterator while predicate returns `True`. 457 458 The rest of the items are discarded as soon as the predicate returns `False` 459 460 Example 461 ------- 462 ```py 463 iterator = Iter(['a', 'ab', 'xd', 'ba']) 464 for x in iterator.take_while(lambda x: 'a' in x): 465 print(x) 466 467 # a 468 # ab 469 ``` 470 471 Parameters 472 ---------- 473 predicate: `collections.Callable[[Item], bool]` 474 The function to predicate each item in the iterator. 475 """ 476 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.
478 def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]: 479 """Yields items from the iterator while predicate returns `False`. 480 481 Example 482 ------- 483 ```py 484 iterator = Iter(['a', 'ab', 'xd', 'ba']) 485 for x in iterator.drop_while(lambda x: 'a' in x): 486 print(x) 487 488 # xd 489 # ba 490 ``` 491 492 Parameters 493 ---------- 494 predicate: `collections.Callable[[Item], bool]` 495 The function to predicate each item in the iterator. 496 """ 497 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.
499 def chunks(self, chunk_size: int, /) -> Chunks[Item]: 500 """Returns an iterator over `chunk_size` elements of the iterator at a time, 501 starting at the beginning of the iterator. 502 503 Example 504 ------- 505 ```py 506 iter = Iter(['a', 'b', 'c', 'd', 'e']) 507 chunks = iter.chunks() 508 assert chunks.next().unwrap() == ['a', 'b'] 509 assert chunks.next().unwrap() == ['c', 'd'] 510 assert chunks.next().unwrap() == ['e'] 511 assert chunks.next().is_none() 512 ``` 513 """ 514 return Chunks(self, chunk_size)
Returns an iterator over chunk_size
elements of the iterator at a time,
starting at the beginning of the iterator.
Example
iter = Iter(['a', 'b', 'c', 'd', 'e'])
chunks = iter.chunks()
assert chunks.next().unwrap() == ['a', 'b']
assert chunks.next().unwrap() == ['c', 'd']
assert chunks.next().unwrap() == ['e']
assert chunks.next().is_none()
516 def all(self, predicate: collections.Callable[[Item], bool]) -> bool: 517 """Return `True` if all items in the iterator match the predicate. 518 519 Example 520 ------- 521 ```py 522 iterator = Iter([1, 2, 3]) 523 while iterator.all(lambda item: isinstance(item, int)): 524 print("Still all integers") 525 continue 526 # Still all integers 527 ``` 528 529 Parameters 530 ---------- 531 predicate: `collections.Callable[[Item], bool]` 532 The function to test each item in the iterator. 533 """ 534 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.
536 def any(self, predicate: collections.Callable[[Item], bool]) -> bool: 537 """`True` if any items in the iterator match the predicate. 538 539 Example 540 ------- 541 ```py 542 iterator = Iter([1, 2, 3]) 543 if iterator.any(lambda item: isinstance(item, int)): 544 print("At least one item is an int.") 545 # At least one item is an int. 546 ``` 547 548 Parameters 549 ---------- 550 predicate: `collections.Callable[[Item], bool]` 551 The function to test each item in the iterator. 552 """ 553 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.
555 def zip( 556 self, other: collections.Iterable[OtherItem] 557 ) -> Iterator[tuple[Item, OtherItem]]: 558 """Zips the iterator with another iterable. 559 560 Example 561 ------- 562 ```py 563 iterator = Iter([1, 2, 3]) 564 for item, other_item in iterator.zip([4, 5, 6]): 565 assert item == other_item 566 <Iter([(1, 4), (2, 5), (3, 6)])> 567 ``` 568 569 Parameters 570 ---------- 571 other: `Iter[OtherItem]` 572 The iterable to zip with. 573 574 Returns 575 ------- 576 `Iter[tuple[Item, OtherItem]]` 577 The zipped iterator. 578 579 """ 580 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.
582 def sort( 583 self, 584 *, 585 key: collections.Callable[[Item], _typeshed.SupportsRichComparison], 586 reverse: bool = False, 587 ) -> Iterator[Item]: 588 """Sorts the iterator. 589 590 Example 591 ------- 592 ```py 593 iterator = Iter([3, 1, 6, 7]) 594 for item in iterator.sort(key=lambda item: item < 3): 595 print(item) 596 # 1 597 # 3 598 # 6 599 # 7 600 ``` 601 602 Parameters 603 ---------- 604 key: `collections.Callable[[Item], Any]` 605 The function to sort by. 606 reverse: `bool` 607 Whether to reverse the sort. 608 609 """ 610 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.
612 def reversed(self) -> Iterator[Item]: 613 """Returns a new iterator that yields the items in the iterator in reverse order. 614 615 Example 616 ------- 617 ```py 618 iterator = Iter([3, 1, 6, 7]) 619 for item in iterator.reversed(): 620 print(item) 621 # 7 622 # 6 623 # 1 624 # 3 625 ``` 626 """ 627 # NOTE: In order to reverse the iterator we need to 628 # first collect it into some collection. 629 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
631 def union(self, other: collections.Iterable[Item]) -> Iterator[Item]: 632 """Returns a new iterator that yields all items from both iterators. 633 634 Example 635 ------- 636 ```py 637 iterator = Iter([1, 2, 3]) 638 other = [4, 5, 6] 639 640 for item in iterator.union(other): 641 print(item) 642 # 1 643 # 2 644 # 3 645 # 4 646 # 5 647 # 6 648 ``` 649 650 Parameters 651 ---------- 652 other: `Iter[Item]` 653 The iterable to union with. 654 """ 655 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.
657 def first(self) -> Option[Item]: 658 """Returns the first item in the iterator. 659 660 Example 661 ------- 662 ```py 663 iterator = Iter([3, 1, 6, 7]) 664 iterator.first().is_some_and(lambda x: x == 3) 665 ``` 666 """ 667 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)
669 def last(self) -> Option[Item]: 670 """Returns the last item in the iterator. 671 672 Example 673 ------- 674 ```py 675 iterator = Iter([3, 1, 6, 7]) 676 iterator.last().is_some_and(lambda x: x == 7) 677 ``` 678 """ 679 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)
681 def count(self) -> int: 682 """Return the count of elements in memory this iterator has. 683 684 Example 685 ------- 686 ```py 687 it = Iter(range(3)) 688 assert it.count() == 3 689 ``` 690 """ 691 count = 0 692 for _ in self: 693 count += 1 694 695 return count
Return the count of elements in memory this iterator has.
Example
it = Iter(range(3))
assert it.count() == 3
697 def find(self, predicate: collections.Callable[[Item], bool]) -> Option[Item]: 698 """Searches for an element of an iterator that satisfies a predicate. 699 700 If you want the position of the element, use `Iterator.position` instead. 701 702 `find()` takes a lambda that returns true or false. It applies this closure to each element of the iterator, 703 and if any of them return true, then find() returns `Some(element)`. If they all return false, it returns None. 704 705 Example 706 ------- 707 ```py 708 it = Iter(range(10)) 709 item = it.find(lambda num: num > 5) 710 print(item) # 6 711 ``` 712 """ 713 for item in self: 714 if predicate(item): 715 return _option.Some(item) 716 717 # no more items 718 return _option.NOTHING # pyright: ignore
Searches for an element of an iterator that satisfies a predicate.
If you want the position of the element, use Iterator.position
instead.
find()
takes a lambda that returns true or false. It applies this closure to each element of the iterator,
and if any of them return true, then find() returns Some(element)
. If they all return false, it returns None.
Example
it = Iter(range(10))
item = it.find(lambda num: num > 5)
print(item) # 6
720 def position(self, predicate: collections.Callable[[Item], bool]) -> Option[int]: 721 """Searches for the position of an element in the iterator that satisfies a predicate. 722 723 If you want the object itself, use `Iterator.find` instead. 724 725 `position()` takes a lambda that returns true or false. It applies this closure to each element of the iterator, 726 and if any of them return true, then position() returns `Some(position_of_element)`. If they all return false, it returns None. 727 728 Example 729 ------- 730 ```py 731 it = Iter(range(10)) 732 position = it.find(lambda num: num > 5) 733 assert position.unwrap() == 6 734 ``` 735 """ 736 for position, value in self.enumerate(): 737 if predicate(value): 738 return _option.Some(position) 739 740 # no more items 741 return _option.NOTHING # pyright: ignore
Searches for the position of an element in the iterator that satisfies a predicate.
If you want the object itself, use Iterator.find
instead.
position()
takes a lambda that returns true or false. It applies this closure to each element of the iterator,
and if any of them return true, then position() returns Some(position_of_element)
. If they all return false, it returns None.
Example
it = Iter(range(10))
position = it.find(lambda num: num > 5)
assert position.unwrap() == 6
743 def fold( 744 self, init: OtherItem, f: collections.Callable[[OtherItem, Item], OtherItem] 745 ) -> OtherItem: 746 """Folds every element into an accumulator by applying an operation, returning the final result. 747 748 fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. 749 The closure returns the value that the accumulator should have for the next iteration. 750 751 The initial value is the value the accumulator will have on the first call. 752 753 After applying this closure to every element of the iterator, fold() returns the accumulator. 754 755 This operation is sometimes called ‘reduce’ or ‘inject’. 756 757 Example 758 ------- 759 ```py 760 a = Iter([1, 2, 3, 4]) 761 sum = a.fold(0, lambda acc, elem: acc + elem) 762 assert sum == 10 763 ``` 764 """ 765 accum = init 766 while True: 767 try: 768 x = self.__next__() 769 accum = f(accum, x) 770 except StopIteration: 771 break 772 773 return accum
Folds every element into an accumulator by applying an operation, returning the final result.
fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. The closure returns the value that the accumulator should have for the next iteration.
The initial value is the value the accumulator will have on the first call.
After applying this closure to every element of the iterator, fold() returns the accumulator.
This operation is sometimes called ‘reduce’ or ‘inject’.
Example
a = Iter([1, 2, 3, 4])
sum = a.fold(0, lambda acc, elem: acc + elem)
assert sum == 10
775 def sum(self: Sum) -> int: 776 """Sums an iterator of a possible type that can be converted to an integer. 777 778 Example 779 ------- 780 ```py 781 numbers: Iterator[str] = Iter(["1", "2", "3"]) 782 total = numbers.sum() 783 assert total == 6 784 ``` 785 """ 786 return sum(int(_) for _ in self)
Sums an iterator of a possible type that can be converted to an integer.
Example
numbers: Iterator[str] = Iter(["1", "2", "3"])
total = numbers.sum()
assert total == 6
788 def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None: 789 """Calls `func` on each item in the iterator. 790 791 Example 792 ------- 793 ```py 794 iterator = Iter([1, 2, 3]) 795 iterator.for_each(lambda item: print(item)) 796 # 1 797 # 2 798 # 3 799 ``` 800 801 Parameters 802 ---------- 803 func: `collections.Callable[[Item], typing.Any]` 804 The function to call on each item in the iterator. 805 """ 806 for item in self: 807 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.
809 async def async_for_each( 810 self, 811 func: collections.Callable[ 812 [Item], collections.Coroutine[None, typing.Any, OtherItem] 813 ], 814 ) -> _result.Result[collections.Sequence[OtherItem], futures.SpawnError]: 815 """Calls the async function on each item in the iterator concurrently. 816 817 Example 818 ------- 819 ```py 820 async def create_user(username: str) -> None: 821 async with aiohttp.request("POST", f'.../{username}') as r: 822 return await r.json() 823 824 async def main(): 825 users = sain.into_iter(["danny", "legalia"]) 826 results = await users.async_for_each(lambda username: create_user(username)) 827 for k, v in results.unwrap().items(): 828 ... 829 ``` 830 831 Parameters 832 ---------- 833 func: `collections.Callable[[Item], Coroutine[None, Any, Any]]` 834 The async function to call on each item in the iterator. 835 """ 836 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.
365@rustc_diagnostic_item("todo") 366def todo(message: typing.LiteralString | None = None) -> typing.NoReturn: 367 """A place holder that indicates unfinished code. 368 369 Example 370 ------- 371 ```py 372 from sain import todo 373 374 def from_json(payload: dict[str, int]) -> int: 375 # Calling this function will raise `Error`. 376 todo() 377 ``` 378 379 Parameters 380 ---------- 381 message : `str | None` 382 Multiple optional arguments to pass if the error was raised. 383 """ 384 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. - # Implementations
- **This function implements todo:
280@rustc_diagnostic_item("deprecated") 281def deprecated( 282 *, 283 since: typing.Literal["CURRENT_VERSION"] | typing.LiteralString | None = None, 284 removed_in: typing.LiteralString | None = None, 285 use_instead: typing.LiteralString | None = None, 286 hint: typing.LiteralString | None = None, 287) -> collections.Callable[ 288 [collections.Callable[P, U]], 289 collections.Callable[P, U], 290]: 291 """A decorator that marks a function as deprecated. 292 293 An attempt to call the object that's marked will cause a runtime warn. 294 295 Example 296 ------- 297 ```py 298 from sain import deprecated 299 300 @deprecated( 301 since = "1.0.0", 302 removed_in ="3.0.0", 303 use_instead = "UserImpl()", 304 hint = "Hint for ux." 305 ) 306 class User: 307 ... 308 309 user = User() # This will cause a warning at runtime. 310 ``` 311 312 Parameters 313 ---------- 314 since : `str` 315 The version that the function was deprecated. the `CURRENT_VERSION` is used internally only. 316 removed_in : `str | None` 317 If provided, It will log when will the object will be removed in. 318 use_instead : `str | None` 319 If provided, This should be the alternative object name that should be used instead. 320 hint: `str` 321 An optional hint for the user. 322 """ 323 324 def _create_message(obj: typing.Any) -> str: 325 msg = f"{_obj_type(obj)} `{obj.__module__}.{obj.__name__}` is deprecated." 326 327 if since is not None: 328 if since == "CURRENT_VERSION": 329 from sain import __version__ as _version 330 331 msg += " since " + _version 332 else: 333 msg += " since " + since 334 335 if removed_in: 336 msg += f" Scheduled for removal in `{removed_in}`." 337 338 if use_instead is not None: 339 msg += f" Use `{use_instead}` instead." 340 341 if hint: 342 msg += f" Hint: {hint}" 343 return msg 344 345 def decorator(func: collections.Callable[P, U]) -> collections.Callable[P, U]: 346 message = _create_message(func) 347 348 @functools.wraps(func) 349 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 350 _warn(message) 351 return func(*args, **kwargs) 352 353 # idk why pyright doesn't know the type of wrapper. 354 m = f"\n# Warning ⚠️\n{message}." 355 if wrapper.__doc__: 356 # append this message to an existing document. 357 wrapper.__doc__ = inspect.cleandoc(wrapper.__doc__) + f"{m}" 358 else: 359 wrapper.__doc__ = m 360 return wrapper 361 362 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. theCURRENT_VERSION
is used internally only. - 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. - # Implementations
- **This function implements deprecated:
387@rustc_diagnostic_item("unimplemented") 388def unimplemented( 389 *, 390 message: typing.LiteralString | None = None, 391 available_in: typing.LiteralString | None = None, 392) -> collections.Callable[ 393 [collections.Callable[P, U]], 394 collections.Callable[P, U], 395]: 396 """A decorator that marks an object as unimplemented. 397 398 An attempt to call the object that's marked will cause a runtime warn. 399 400 Example 401 ------- 402 ```py 403 from sain import unimplemented 404 405 @unimplemented("User object is not implemented yet.") 406 class User: 407 ... 408 ``` 409 410 Parameters 411 ---------- 412 message : `str | None` 413 An optional message to be displayed when the function is called. Otherwise default message will be used. 414 available_in : `str | None` 415 If provided, This will be shown as what release this object be implemented. 416 """ 417 418 def _create_message(obj: typing.Any) -> str: 419 msg = ( 420 message 421 or f"{_obj_type(obj)} `{obj.__module__}.{obj.__name__}` is not yet implemented." 422 ) # noqa: W503 423 424 if available_in: 425 msg += f" Available in `{available_in}`." 426 return msg 427 428 def decorator(obj: collections.Callable[P, U]) -> collections.Callable[P, U]: 429 message = _create_message(obj) 430 431 @functools.wraps(obj) 432 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 433 _warn(message) 434 return obj(*args, **kwargs) 435 436 # idk why pyright doesn't know the type of wrapper. 437 m = f"\n# Warning ⚠️\n{message}." 438 if wrapper.__doc__: 439 # append this message to an existing document. 440 wrapper.__doc__ = inspect.cleandoc(wrapper.__doc__) + f"{m}" 441 else: 442 wrapper.__doc__ = m 443 return wrapper 444 445 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. - # Implementations
- **This function implements unimplemented:
448@rustc_diagnostic_item("doc") 449def doc( 450 path: Read, 451) -> collections.Callable[ 452 [collections.Callable[P, U]], 453 collections.Callable[P, U], 454]: 455 """Set `path` to be the object's documentation. 456 457 Example 458 ------- 459 ```py 460 from sain import doc 461 from pathlib import Path 462 463 @doc(Path("../README.md")) 464 class User: 465 466 @doc("bool.html") 467 def bool_docs() -> None: 468 ... 469 ``` 470 471 Parameters 472 ---------- 473 path: `type[int] | type[str] | type[bytes] | type[PathLike[str]] | type[PathLike[bytes]]` 474 The path to read the content from. 475 """ 476 477 def decorator(f: collections.Callable[P, U]) -> collections.Callable[P, U]: 478 with open(path, "r") as file: 479 f.__doc__ = file.read() 480 481 @functools.wraps(f) 482 def wrapper(*args: P.args, **kwargs: P.kwargs) -> U: 483 return f(*args, **kwargs) 484 485 return wrapper 486 487 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. - # Implementations
- **This function implements doc:
119@typing.final 120@dataclasses.dataclass(slots=True, frozen=True, repr=False) 121class Ok(typing.Generic[T]): 122 """Contains the success value of `Result[T, ...]`.""" 123 124 _inner: T 125 126 ############################### 127 # * Boolean operations. * # 128 ############################### 129 130 def is_ok(self) -> typing.Literal[True]: 131 """Returns `True` if the contained value is `Ok` and `False` if it an `Err`. 132 133 Example 134 ------- 135 ```py 136 value: Result[str, None] = Ok("value") 137 assert value.is_ok() == True 138 ``` 139 """ 140 return True 141 142 def is_ok_and(self, f: F[T, bool]) -> bool: 143 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 144 145 Example 146 ------- 147 ```py 148 value: Result[str, None] = Ok("value") 149 150 assert value.is_ok_and(lambda inner: inner == "value") 151 # True 152 ``` 153 """ 154 return f(self._inner) 155 156 # These are never truthy in an `Ok` instance. 157 def is_err(self) -> typing.Literal[False]: 158 """Returns `True` if the contained value is `Err`. 159 160 Example 161 ------- 162 ```py 163 value: Result[str, None] = Ok("value") 164 165 assert value.is_err() == False 166 ``` 167 """ 168 return False 169 170 def is_err_and(self, f: F[T, bool]) -> typing.Literal[False]: 171 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 172 173 Example 174 ------- 175 ```py 176 value: Result[str, None] = Ok("value") 177 178 assert value.is_err_and(lambda inner: inner == "value") 179 # False 180 ``` 181 """ 182 return False 183 184 ################### 185 # * Extractors. * # 186 ################### 187 188 def expect(self, message: str, /) -> T: 189 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 190 if it was `Err` with `message` passed to it. 191 192 Example 193 ------- 194 ```py 195 ok: Result[str, None] = Ok("owo") 196 ok.expect("err") # owo 197 198 err: Result[str, None] = Err(None) 199 err.expect("err") # RuntimeError("err") 200 ``` 201 """ 202 return self._inner 203 204 def expect_err(self) -> typing.NoReturn: 205 """Return the `Err` value if `self` is an `Err`, panicking otherwise. 206 207 Example 208 ------- 209 ```py 210 ok: Result[str, None] = Ok("owo") 211 ok.expect_err() # RuntimeError("Called expect_err on `Ok`) 212 213 err: Result[str, None] = Err(None) 214 err.expect_err() # None 215 ``` 216 """ 217 raise RuntimeError("Called `expect_err` on an `Ok` value.") 218 219 def unwrap(self) -> T: 220 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 221 222 Example 223 ------- 224 ```py 225 ok: Result[str, None] = Ok("owo") 226 ok.unwrap() # owo 227 228 err: Result[str, None] = Err(None) 229 err.unwrap() # RuntimeError 230 ``` 231 """ 232 return self._inner 233 234 def unwrap_or(self, default: T, /) -> T: 235 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 236 237 Example 238 ------- 239 ```py 240 ok: Result[str, None] = Ok("OwO") 241 ok.unwrap_or("uwu") # OwO 242 243 err: Result[str, None] = Err(None) 244 err.unwrap_or("uwu") # uwu 245 ``` 246 """ 247 return self._inner 248 249 def unwrap_or_else(self, f: F[E, T]) -> T: 250 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 251 252 Example 253 ------- 254 ```py 255 ok: Result[int, str] = Ok(4) 256 ok.unwrap_or_else(lambda e: 0) # 4 257 258 err: Result[int, str] = Err("word") 259 err.unwrap_or_else(lambda e: len(e)) # 4 260 ``` 261 """ 262 return self._inner 263 264 def unwrap_err(self) -> typing.NoReturn: 265 """Return the contained `Err` value, Raising if it was `Ok`. 266 267 Example 268 ------- 269 ```py 270 ok: Result[str, None] = Ok("buh") 271 ok.unwrap_err() # RuntimeError 272 273 err: Result[str, None] = Err(None) 274 err.unwrap_err() == None 275 # True 276 ``` 277 """ 278 raise RuntimeError(f"Called `unwrap_err` on an `Ok` variant: {self._inner!r}") 279 280 ############################ 281 # * Conversion adapters. * # 282 ############################ 283 284 def ok(self) -> Option[T]: 285 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 286 287 Example 288 ------- 289 ```py 290 value: Result[str, None] = Ok("buh") 291 value.ok().is_some() # True 292 293 value: Result[str, int] = Err(0) 294 value.ok().is_none() # True 295 ``` 296 """ 297 return _option.Some(self._inner) 298 299 def err(self) -> Option[T]: 300 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 301 302 Example 303 ------- 304 ```py 305 value: Result[str, None] = Ok("buh") 306 value.err().is_none() # True 307 308 value: Result[str, int] = Err(0) 309 value.err().is_some() # True 310 ``` 311 """ 312 return _option.NOTHING # pyright: ignore 313 314 def inspect(self, f: F[T, typing.Any]) -> None: 315 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 316 317 Example 318 ------- 319 ```py 320 def sink(value: str) -> None: 321 # do something with value 322 print("Called " + value) 323 324 x: Result[str, None] = Ok("ok") 325 x.inspect(sink) # "Called ok" 326 327 x: Result[str, str] = Err("err") 328 x.inspect(sink) # None 329 ``` 330 """ 331 f(self._inner) 332 333 def inspect_err(self, f: F[E, typing.Any]) -> None: 334 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 335 336 Example 337 ------- 338 ```py 339 def sink(value: str) -> None: 340 # do something with value 341 print("Called " + value) 342 343 x: Result[str, None] = Ok("ok") 344 x.inspect_err(sink) # None 345 346 x: Result[str, str] = Err("err") 347 x.inspect_err(sink) # Called err 348 ``` 349 """ 350 351 def map(self, f: F[T, U], /) -> Ok[U]: 352 """Map `Result<T, E>` to `Result<U, E>` by applying a function to the `Ok` value, 353 leaving `Err` untouched. 354 355 Example 356 ------- 357 ```py 358 ok: Result[str, int] = Ok("1") 359 ok.map(lambda c: int(c) + 1) # Ok(2) 360 361 err: Result[str, int] = Err(0) 362 err.map(str.upper) # Err(0) 363 ``` 364 """ 365 return Ok(f(self._inner)) 366 367 def map_or(self, f: F[T, U], default: U, /) -> U: 368 """Returns the provided `default` if the contained value is `Err`, 369 370 Otherwise extracts the `Ok` value and maps it to `f()` 371 372 Example 373 ------- 374 ```py 375 x: Result[str, str] = Ok("foo") 376 assert x.map_or(lambda c: len(c), 42) == 3 377 378 x: Result[str, str] = Err("bar") 379 assert x.map_or(lambda c: len(c), 42) == 42 380 ``` 381 """ 382 return f(self._inner) 383 384 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 385 """Maps a Result<T, E> to U by applying fallback function `default` to a contained Err value, 386 or function `f` to a contained Ok value. 387 388 Example 389 ------- 390 ```py 391 x: Result[str, str] = Ok("four") 392 assert x.map_or_else( 393 lambda ok: 2 * len(ok), 394 default=lambda err: len(err) 395 ) == 8 396 397 x: Result[str, str] = Err("bar") 398 assert x.map_or_else( 399 lambda c: 2 * len(c), 400 lambda err: len(err) 401 ) == 3 402 ``` 403 """ 404 return f(self._inner) 405 406 def map_err(self, f: F[E, U], /) -> Self: 407 """Maps a `Result[T, E]` to `Result[T, U]` by applying function `f`, leaving the `Ok` value untouched. 408 409 Example 410 ------- 411 ```py 412 x: Result[str, int] = Ok("blue") 413 x.map_err(lambda err: err + 1) # Ok("blue") 414 415 x: Result[str, int] = Err(5) 416 x.map_err(float) # Err(5.0) 417 ``` 418 """ 419 return self 420 421 ############################## 422 # * Iterator constructors. * # 423 ############################## 424 425 def iter(self) -> _iter.Iterator[T]: 426 """An iterator over the possible contained value. 427 428 If `self` was `Ok`, then the iterator will yield the Ok `T`. otherwise yields nothing. 429 430 Example 431 ------- 432 ```py 433 c: Result[str, int] = Ok("blue") 434 c.iter().next() == Some("blue") 435 436 c: Result[str, int] = Err(0) 437 c.iter().next() == Some(None) 438 ``` 439 """ 440 return self.__iter__() 441 442 def __iter__(self) -> _iter.Iterator[T]: 443 return _iter.once(self._inner) 444 445 ################# 446 # * Overloads * # 447 ################# 448 449 def __repr__(self) -> str: 450 return f"Ok({self._inner!r})" 451 452 def __or__(self, other: T) -> T: 453 return self._inner 454 455 def __invert__(self) -> T: 456 return self._inner
Contains the success value of Result[T, ...]
.
142 def is_ok_and(self, f: F[T, bool]) -> bool: 143 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 144 145 Example 146 ------- 147 ```py 148 value: Result[str, None] = Ok("value") 149 150 assert value.is_ok_and(lambda inner: inner == "value") 151 # True 152 ``` 153 """ 154 return 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
157 def is_err(self) -> typing.Literal[False]: 158 """Returns `True` if the contained value is `Err`. 159 160 Example 161 ------- 162 ```py 163 value: Result[str, None] = Ok("value") 164 165 assert value.is_err() == False 166 ``` 167 """ 168 return False
Returns True
if the contained value is Err
.
Example
value: Result[str, None] = Ok("value")
assert value.is_err() == False
170 def is_err_and(self, f: F[T, bool]) -> typing.Literal[False]: 171 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 172 173 Example 174 ------- 175 ```py 176 value: Result[str, None] = Ok("value") 177 178 assert value.is_err_and(lambda inner: inner == "value") 179 # False 180 ``` 181 """ 182 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
188 def expect(self, message: str, /) -> T: 189 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 190 if it was `Err` with `message` passed to it. 191 192 Example 193 ------- 194 ```py 195 ok: Result[str, None] = Ok("owo") 196 ok.expect("err") # owo 197 198 err: Result[str, None] = Err(None) 199 err.expect("err") # RuntimeError("err") 200 ``` 201 """ 202 return self._inner
204 def expect_err(self) -> typing.NoReturn: 205 """Return the `Err` value if `self` is an `Err`, panicking otherwise. 206 207 Example 208 ------- 209 ```py 210 ok: Result[str, None] = Ok("owo") 211 ok.expect_err() # RuntimeError("Called expect_err on `Ok`) 212 213 err: Result[str, None] = Err(None) 214 err.expect_err() # None 215 ``` 216 """ 217 raise RuntimeError("Called `expect_err` on an `Ok` value.")
219 def unwrap(self) -> T: 220 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 221 222 Example 223 ------- 224 ```py 225 ok: Result[str, None] = Ok("owo") 226 ok.unwrap() # owo 227 228 err: Result[str, None] = Err(None) 229 err.unwrap() # RuntimeError 230 ``` 231 """ 232 return self._inner
234 def unwrap_or(self, default: T, /) -> T: 235 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 236 237 Example 238 ------- 239 ```py 240 ok: Result[str, None] = Ok("OwO") 241 ok.unwrap_or("uwu") # OwO 242 243 err: Result[str, None] = Err(None) 244 err.unwrap_or("uwu") # uwu 245 ``` 246 """ 247 return self._inner
249 def unwrap_or_else(self, f: F[E, T]) -> T: 250 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 251 252 Example 253 ------- 254 ```py 255 ok: Result[int, str] = Ok(4) 256 ok.unwrap_or_else(lambda e: 0) # 4 257 258 err: Result[int, str] = Err("word") 259 err.unwrap_or_else(lambda e: len(e)) # 4 260 ``` 261 """ 262 return self._inner
264 def unwrap_err(self) -> typing.NoReturn: 265 """Return the contained `Err` value, Raising if it was `Ok`. 266 267 Example 268 ------- 269 ```py 270 ok: Result[str, None] = Ok("buh") 271 ok.unwrap_err() # RuntimeError 272 273 err: Result[str, None] = Err(None) 274 err.unwrap_err() == None 275 # True 276 ``` 277 """ 278 raise RuntimeError(f"Called `unwrap_err` on an `Ok` variant: {self._inner!r}")
284 def ok(self) -> Option[T]: 285 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 286 287 Example 288 ------- 289 ```py 290 value: Result[str, None] = Ok("buh") 291 value.ok().is_some() # True 292 293 value: Result[str, int] = Err(0) 294 value.ok().is_none() # True 295 ``` 296 """ 297 return _option.Some(self._inner)
299 def err(self) -> Option[T]: 300 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 301 302 Example 303 ------- 304 ```py 305 value: Result[str, None] = Ok("buh") 306 value.err().is_none() # True 307 308 value: Result[str, int] = Err(0) 309 value.err().is_some() # True 310 ``` 311 """ 312 return _option.NOTHING # pyright: ignore
314 def inspect(self, f: F[T, typing.Any]) -> None: 315 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 316 317 Example 318 ------- 319 ```py 320 def sink(value: str) -> None: 321 # do something with value 322 print("Called " + value) 323 324 x: Result[str, None] = Ok("ok") 325 x.inspect(sink) # "Called ok" 326 327 x: Result[str, str] = Err("err") 328 x.inspect(sink) # None 329 ``` 330 """ 331 f(self._inner)
333 def inspect_err(self, f: F[E, typing.Any]) -> None: 334 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 335 336 Example 337 ------- 338 ```py 339 def sink(value: str) -> None: 340 # do something with value 341 print("Called " + value) 342 343 x: Result[str, None] = Ok("ok") 344 x.inspect_err(sink) # None 345 346 x: Result[str, str] = Err("err") 347 x.inspect_err(sink) # Called err 348 ``` 349 """
351 def map(self, f: F[T, U], /) -> Ok[U]: 352 """Map `Result<T, E>` to `Result<U, E>` by applying a function to the `Ok` value, 353 leaving `Err` untouched. 354 355 Example 356 ------- 357 ```py 358 ok: Result[str, int] = Ok("1") 359 ok.map(lambda c: int(c) + 1) # Ok(2) 360 361 err: Result[str, int] = Err(0) 362 err.map(str.upper) # Err(0) 363 ``` 364 """ 365 return Ok(f(self._inner))
367 def map_or(self, f: F[T, U], default: U, /) -> U: 368 """Returns the provided `default` if the contained value is `Err`, 369 370 Otherwise extracts the `Ok` value and maps it to `f()` 371 372 Example 373 ------- 374 ```py 375 x: Result[str, str] = Ok("foo") 376 assert x.map_or(lambda c: len(c), 42) == 3 377 378 x: Result[str, str] = Err("bar") 379 assert x.map_or(lambda c: len(c), 42) == 42 380 ``` 381 """ 382 return f(self._inner)
384 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 385 """Maps a Result<T, E> to U by applying fallback function `default` to a contained Err value, 386 or function `f` to a contained Ok value. 387 388 Example 389 ------- 390 ```py 391 x: Result[str, str] = Ok("four") 392 assert x.map_or_else( 393 lambda ok: 2 * len(ok), 394 default=lambda err: len(err) 395 ) == 8 396 397 x: Result[str, str] = Err("bar") 398 assert x.map_or_else( 399 lambda c: 2 * len(c), 400 lambda err: len(err) 401 ) == 3 402 ``` 403 """ 404 return f(self._inner)
Maps a Resultdefault
to a contained Err value,
or function f
to a contained Ok value.
Example
x: Result[str, str] = Ok("four")
assert x.map_or_else(
lambda ok: 2 * len(ok),
default=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
406 def map_err(self, f: F[E, U], /) -> Self: 407 """Maps a `Result[T, E]` to `Result[T, U]` by applying function `f`, leaving the `Ok` value untouched. 408 409 Example 410 ------- 411 ```py 412 x: Result[str, int] = Ok("blue") 413 x.map_err(lambda err: err + 1) # Ok("blue") 414 415 x: Result[str, int] = Err(5) 416 x.map_err(float) # Err(5.0) 417 ``` 418 """ 419 return self
Maps a Result[T, E]
to Result[T, U]
by applying function f
, leaving the Ok
value 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)
425 def iter(self) -> _iter.Iterator[T]: 426 """An iterator over the possible contained value. 427 428 If `self` was `Ok`, then the iterator will yield the Ok `T`. otherwise yields nothing. 429 430 Example 431 ------- 432 ```py 433 c: Result[str, int] = Ok("blue") 434 c.iter().next() == Some("blue") 435 436 c: Result[str, int] = Err(0) 437 c.iter().next() == Some(None) 438 ``` 439 """ 440 return self.__iter__()
An iterator over the possible contained value.
If self
was Ok
, then the iterator will yield the Ok T
. otherwise yields nothing.
Example
c: Result[str, int] = Ok("blue")
c.iter().next() == Some("blue")
c: Result[str, int] = Err(0)
c.iter().next() == Some(None)
459@typing.final 460@dataclasses.dataclass(slots=True, frozen=True, repr=False) 461class Err(typing.Generic[E]): 462 """Contains the error value of `Result[..., E]`.""" 463 464 _inner: E 465 466 ################################ 467 # * Boolean operations. * # 468 ################################ 469 470 def is_ok(self) -> typing.Literal[False]: 471 """Returns `True` if the contained value is `Ok` and `False` if it an `Err`. 472 473 Example 474 ------- 475 ```py 476 value: Result[str, None] = Err(None) 477 478 assert value.is_ok() == False 479 ``` 480 """ 481 return False 482 483 def is_ok_and(self, f: F[E, bool]) -> typing.Literal[False]: 484 """Returns `True` if the contained value is `Ok` and `f()` returns True. 485 486 Example 487 ------- 488 ```py 489 value: Result[str, None] = Err(None) 490 491 assert value.is_ok_and(lambda inner: inner == "value") 492 # False 493 ``` 494 """ 495 return False 496 497 # These are never truthy in an `Ok` instance. 498 def is_err(self) -> typing.Literal[True]: 499 """Returns `True` if the contained value is `Err`. 500 501 Example 502 ------- 503 ```py 504 value: Result[str, None] = Err(None) 505 506 assert value.is_err() == True 507 ``` 508 """ 509 return True 510 511 def is_err_and(self, f: F[E, bool]) -> bool: 512 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 513 514 Example 515 ------- 516 ```py 517 value: Result[str, None] = Err(None) 518 519 assert value.is_err_and(lambda err: err is None) 520 # True 521 ``` 522 """ 523 return f(self._inner) 524 525 ################### 526 # * Extractors. * # 527 ################### 528 529 def expect(self, msg: str) -> typing.NoReturn: 530 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 531 if it was `Err` with `message` passed to it. 532 533 Example 534 ------- 535 ```py 536 ok: Result[str, None] = Ok("owo") 537 ok.expect("err") # owo 538 539 err: Result[str, None] = Err(None) 540 err.expect("err") # RuntimeError("err") 541 ``` 542 """ 543 raise RuntimeError(msg) from None 544 545 def expect_err(self) -> E: 546 """Return the `Err` if it was `Err`, panicking otherwise. 547 548 549 Example 550 ------- 551 ```py 552 x: Result[str, None] = Ok("owo") 553 x.expect_err() # RuntimeError("Called expect_err on `Ok`) 554 555 x: Result[str, None] = Err(None) 556 x.expect_err() # None 557 ``` 558 """ 559 return self._inner 560 561 def unwrap(self) -> typing.NoReturn: 562 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 563 564 Example 565 ------- 566 ```py 567 ok: Result[str, None] = Ok("owo") 568 ok.unwrap() # owo 569 570 err: Result[str, None] = Err(None) 571 err.unwrap() # RuntimeError 572 ``` 573 """ 574 raise RuntimeError( 575 f"Called `unwrap()` on an `Err` variant: {self._inner!r}" 576 ) from None 577 578 def unwrap_or(self, default: T, /) -> T: 579 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 580 581 Example 582 ------- 583 ```py 584 ok: Result[str, None] = Ok("OwO") 585 ok.unwrap_or("uwu") # OwO 586 587 err: Result[str, None] = Err(None) 588 err.unwrap_or("uwu") # uwu 589 ``` 590 """ 591 return default 592 593 def unwrap_or_else(self, f: F[E, T]) -> T: 594 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 595 596 Example 597 ------- 598 ```py 599 ok: Result[int, str] = Ok(4) 600 ok.unwrap_or_else(lambda e: 0) # 4 601 602 err: Result[int, str] = Err("word") 603 err.unwrap_or_else(lambda e: len(e)) # 4 604 ``` 605 """ 606 return f(self._inner) 607 608 def unwrap_err(self) -> E: 609 """Return the contained `Err` value, Raising if it was `Ok`. 610 611 Example 612 ------- 613 ```py 614 ok: Result[str, None] = Ok("buh") 615 ok.unwrap_err() # RuntimeError 616 617 err: Result[str, None] = Err(None) 618 err.unwrap_err() == None 619 # True 620 ``` 621 """ 622 return self._inner 623 624 ############################ 625 # * Conversion adapters. * # 626 ############################ 627 628 def inspect(self, f: F[T, typing.Any]) -> None: 629 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 630 631 Example 632 ------- 633 ```py 634 def sink(value: str) -> None: 635 # do something with value 636 print("Called " + value) 637 638 x: Result[str, None] = Ok("ok") 639 x.inspect(sink) # "Called ok" 640 641 x: Result[str, str] = Err("err") 642 x.inspect(sink) # None 643 ``` 644 """ 645 return None 646 647 def inspect_err(self, f: F[E, typing.Any]) -> None: 648 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 649 650 Example 651 ------- 652 ```py 653 def sink(value: str) -> None: 654 # do something with value 655 print("Called " + value) 656 657 x: Result[str, None] = Ok("ok") 658 x.inspect_err(sink) # None 659 660 x: Result[str, str] = Err("err") 661 x.inspect_err(sink) # Called err 662 ``` 663 """ 664 f(self._inner) 665 666 def ok(self) -> Option[None]: 667 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 668 669 Example 670 ------- 671 ```py 672 value: Result[str, None] = Ok("buh") 673 value.ok().is_some() # True 674 675 value: Result[str, int] = Err(0) 676 value.ok().is_none() # True 677 ``` 678 """ 679 return _option.NOTHING 680 681 def err(self) -> Option[E]: 682 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 683 684 Example 685 ------- 686 ```py 687 value: Result[str, None] = Ok("buh") 688 value.err().is_none() # True 689 690 value: Result[str, int] = Err(0) 691 value.err().is_some() # True 692 ``` 693 """ 694 return _option.Some(self._inner) 695 696 def map(self, f: F[E, U]) -> Self: 697 """Map `Result<T, E>` to `Result<U, E>` by applying a function to the `Ok` value, 698 leaving `Err` untouched. 699 700 Example 701 ------- 702 ```py 703 ok: Result[str, int] = Ok("1") 704 ok.map(lambda c: int(c) + 1) # Ok(2) 705 706 err: Result[str, int] = Err(0) 707 err.map(str.upper) # Err(0) 708 ``` 709 """ 710 return self 711 712 def map_or(self, f: F[E, U], default: U, /) -> U: 713 """Returns the provided `default` if the contained value is `Err`, 714 715 Otherwise extracts the `Ok` value and maps it to `f()` 716 717 Example 718 ------- 719 ```py 720 x: Result[str, str] = Ok("foo") 721 assert x.map_or(lambda c: len(c), 42) == 3 722 723 x: Result[str, str] = Err("bar") 724 assert x.map_or(lambda c: len(c), 42) == 42 725 ``` 726 """ 727 return default 728 729 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 730 """Maps a Result<T, E> to U by applying fallback function `default` to a contained Err value, 731 or function `f` to a contained Ok value. 732 733 Example 734 ------- 735 ```py 736 x: Result[str, str] = Ok("four") 737 assert x.map_or_else( 738 lambda ok: 2 * len(ok), 739 default=lambda err: len(err) 740 ) == 8 741 742 x: Result[str, str] = Err("bar") 743 assert x.map_or_else( 744 lambda c: 2 * len(c), 745 lambda err: len(err) 746 ) == 3 747 ``` 748 """ 749 return default(self._inner) 750 751 def map_err(self, f: F[E, U]) -> Err[U]: 752 """Maps a `Result[T, E]` to `Result[T, U]` by applying function `f`, leaving the `Ok` value untouched. 753 754 Example 755 ------- 756 ```py 757 x: Result[str, int] = Ok("blue") 758 x.map_err(lambda err: err + 1) # Ok("blue") 759 760 x: Result[str, int] = Err(5) 761 x.map_err(float) # Err(5.0) 762 ``` 763 """ 764 return Err(f(self._inner)) 765 766 ############################## 767 # * Iterator constructors. * # 768 ############################## 769 770 def iter(self) -> _iter.Iterator[Never]: 771 """An iterator over the possible contained value. 772 773 If `self` was `Ok`, then the iterator will yield `T`, otherwise yields nothing. 774 775 Example 776 ------- 777 ```py 778 c: Result[str, int] = Ok("blue") 779 c.iter().next() == Some("blue") 780 781 c: Result[str, int] = Err(0) 782 c.iter().next() == Some(None) 783 ``` 784 """ 785 return self.__iter__() 786 787 def __iter__(self) -> _iter.Iterator[Never]: 788 return _iter.Empty() 789 790 ################# 791 # * Overloads * # 792 ################# 793 794 def __repr__(self) -> str: 795 return f"Err({self._inner!r})" 796 797 def __or__(self, other: T) -> T: 798 return other 799 800 def __invert__(self) -> typing.NoReturn: 801 self.unwrap()
Contains the error value of Result[..., E]
.
483 def is_ok_and(self, f: F[E, bool]) -> typing.Literal[False]: 484 """Returns `True` if the contained value is `Ok` and `f()` returns True. 485 486 Example 487 ------- 488 ```py 489 value: Result[str, None] = Err(None) 490 491 assert value.is_ok_and(lambda inner: inner == "value") 492 # False 493 ``` 494 """ 495 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
498 def is_err(self) -> typing.Literal[True]: 499 """Returns `True` if the contained value is `Err`. 500 501 Example 502 ------- 503 ```py 504 value: Result[str, None] = Err(None) 505 506 assert value.is_err() == True 507 ``` 508 """ 509 return True
Returns True
if the contained value is Err
.
Example
value: Result[str, None] = Err(None)
assert value.is_err() == True
511 def is_err_and(self, f: F[E, bool]) -> bool: 512 """Returns `True` if the contained value is `Ok` and `f()` returns True.. 513 514 Example 515 ------- 516 ```py 517 value: Result[str, None] = Err(None) 518 519 assert value.is_err_and(lambda err: err is None) 520 # True 521 ``` 522 """ 523 return 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
529 def expect(self, msg: str) -> typing.NoReturn: 530 """Return the underlying value if it was `Ok`, Raising `RuntimeError` 531 if it was `Err` with `message` passed to it. 532 533 Example 534 ------- 535 ```py 536 ok: Result[str, None] = Ok("owo") 537 ok.expect("err") # owo 538 539 err: Result[str, None] = Err(None) 540 err.expect("err") # RuntimeError("err") 541 ``` 542 """ 543 raise RuntimeError(msg) from None
545 def expect_err(self) -> E: 546 """Return the `Err` if it was `Err`, panicking otherwise. 547 548 549 Example 550 ------- 551 ```py 552 x: Result[str, None] = Ok("owo") 553 x.expect_err() # RuntimeError("Called expect_err on `Ok`) 554 555 x: Result[str, None] = Err(None) 556 x.expect_err() # None 557 ``` 558 """ 559 return self._inner
561 def unwrap(self) -> typing.NoReturn: 562 """Return the underlying value if it was `Ok`, Raising `RuntimeError` if it was `Err`. 563 564 Example 565 ------- 566 ```py 567 ok: Result[str, None] = Ok("owo") 568 ok.unwrap() # owo 569 570 err: Result[str, None] = Err(None) 571 err.unwrap() # RuntimeError 572 ``` 573 """ 574 raise RuntimeError( 575 f"Called `unwrap()` on an `Err` variant: {self._inner!r}" 576 ) from None
578 def unwrap_or(self, default: T, /) -> T: 579 """Return the underlying value if it was `Ok`, returning `default` if it was `Err`. 580 581 Example 582 ------- 583 ```py 584 ok: Result[str, None] = Ok("OwO") 585 ok.unwrap_or("uwu") # OwO 586 587 err: Result[str, None] = Err(None) 588 err.unwrap_or("uwu") # uwu 589 ``` 590 """ 591 return default
593 def unwrap_or_else(self, f: F[E, T]) -> T: 594 """Return the contained `Ok` value or computes it from `f()` if it was `Err`. 595 596 Example 597 ------- 598 ```py 599 ok: Result[int, str] = Ok(4) 600 ok.unwrap_or_else(lambda e: 0) # 4 601 602 err: Result[int, str] = Err("word") 603 err.unwrap_or_else(lambda e: len(e)) # 4 604 ``` 605 """ 606 return f(self._inner)
608 def unwrap_err(self) -> E: 609 """Return the contained `Err` value, Raising if it was `Ok`. 610 611 Example 612 ------- 613 ```py 614 ok: Result[str, None] = Ok("buh") 615 ok.unwrap_err() # RuntimeError 616 617 err: Result[str, None] = Err(None) 618 err.unwrap_err() == None 619 # True 620 ``` 621 """ 622 return self._inner
628 def inspect(self, f: F[T, typing.Any]) -> None: 629 """Call a function to the contained value if it was `Ok` and do nothing if it was `Err` 630 631 Example 632 ------- 633 ```py 634 def sink(value: str) -> None: 635 # do something with value 636 print("Called " + value) 637 638 x: Result[str, None] = Ok("ok") 639 x.inspect(sink) # "Called ok" 640 641 x: Result[str, str] = Err("err") 642 x.inspect(sink) # None 643 ``` 644 """ 645 return None
647 def inspect_err(self, f: F[E, typing.Any]) -> None: 648 """Call a function to the contained value if it was `Err` and do nothing if it was `Ok` 649 650 Example 651 ------- 652 ```py 653 def sink(value: str) -> None: 654 # do something with value 655 print("Called " + value) 656 657 x: Result[str, None] = Ok("ok") 658 x.inspect_err(sink) # None 659 660 x: Result[str, str] = Err("err") 661 x.inspect_err(sink) # Called err 662 ``` 663 """ 664 f(self._inner)
666 def ok(self) -> Option[None]: 667 """Convert `Ok[T]` to `Option[T]` if the contained value was `Ok` and `Option[None]` if it was `Err`. 668 669 Example 670 ------- 671 ```py 672 value: Result[str, None] = Ok("buh") 673 value.ok().is_some() # True 674 675 value: Result[str, int] = Err(0) 676 value.ok().is_none() # True 677 ``` 678 """ 679 return _option.NOTHING
681 def err(self) -> Option[E]: 682 """Convert `Err[T]` to `Option[T]` if the contained value was `Err` and `Option[None]` if it was `Ok`. 683 684 Example 685 ------- 686 ```py 687 value: Result[str, None] = Ok("buh") 688 value.err().is_none() # True 689 690 value: Result[str, int] = Err(0) 691 value.err().is_some() # True 692 ``` 693 """ 694 return _option.Some(self._inner)
696 def map(self, f: F[E, U]) -> Self: 697 """Map `Result<T, E>` to `Result<U, E>` by applying a function to the `Ok` value, 698 leaving `Err` untouched. 699 700 Example 701 ------- 702 ```py 703 ok: Result[str, int] = Ok("1") 704 ok.map(lambda c: int(c) + 1) # Ok(2) 705 706 err: Result[str, int] = Err(0) 707 err.map(str.upper) # Err(0) 708 ``` 709 """ 710 return self
712 def map_or(self, f: F[E, U], default: U, /) -> U: 713 """Returns the provided `default` if the contained value is `Err`, 714 715 Otherwise extracts the `Ok` value and maps it to `f()` 716 717 Example 718 ------- 719 ```py 720 x: Result[str, str] = Ok("foo") 721 assert x.map_or(lambda c: len(c), 42) == 3 722 723 x: Result[str, str] = Err("bar") 724 assert x.map_or(lambda c: len(c), 42) == 42 725 ``` 726 """ 727 return default
729 def map_or_else(self, f: F[T, U], default: F[E, U], /) -> U: 730 """Maps a Result<T, E> to U by applying fallback function `default` to a contained Err value, 731 or function `f` to a contained Ok value. 732 733 Example 734 ------- 735 ```py 736 x: Result[str, str] = Ok("four") 737 assert x.map_or_else( 738 lambda ok: 2 * len(ok), 739 default=lambda err: len(err) 740 ) == 8 741 742 x: Result[str, str] = Err("bar") 743 assert x.map_or_else( 744 lambda c: 2 * len(c), 745 lambda err: len(err) 746 ) == 3 747 ``` 748 """ 749 return default(self._inner)
Maps a Resultdefault
to a contained Err value,
or function f
to a contained Ok value.
Example
x: Result[str, str] = Ok("four")
assert x.map_or_else(
lambda ok: 2 * len(ok),
default=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
751 def map_err(self, f: F[E, U]) -> Err[U]: 752 """Maps a `Result[T, E]` to `Result[T, U]` by applying function `f`, leaving the `Ok` value untouched. 753 754 Example 755 ------- 756 ```py 757 x: Result[str, int] = Ok("blue") 758 x.map_err(lambda err: err + 1) # Ok("blue") 759 760 x: Result[str, int] = Err(5) 761 x.map_err(float) # Err(5.0) 762 ``` 763 """ 764 return Err(f(self._inner))
Maps a Result[T, E]
to Result[T, U]
by applying function f
, leaving the Ok
value 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)
770 def iter(self) -> _iter.Iterator[Never]: 771 """An iterator over the possible contained value. 772 773 If `self` was `Ok`, then the iterator will yield `T`, otherwise yields nothing. 774 775 Example 776 ------- 777 ```py 778 c: Result[str, int] = Ok("blue") 779 c.iter().next() == Some("blue") 780 781 c: Result[str, int] = Err(0) 782 c.iter().next() == Some(None) 783 ``` 784 """ 785 return self.__iter__()
An iterator over the possible contained value.
If self
was Ok
, then the iterator will yield T
, otherwise yields nothing.
Example
c: Result[str, int] = Ok("blue")
c.iter().next() == Some("blue")
c: Result[str, int] = Err(0)
c.iter().next() == Some(None)
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 A `Vec` may be compared with another `Vec` or a `list`, which is what this type is built on-top of. 127 any other type will return `False` 128 129 ```py 130 vec = Vec([1, 2, 3]) 131 assert vec == [1, 2, 3] # True 132 assert not vec == (1, 2, 3) 133 ``` 134 135 Zero-Copy 136 --------- 137 A vec that gets initialized from a `list` will *point* to it and doesn't copy it. 138 So any element that gets appended to the list will also get pushed into the vec 139 thats pointing to it. 140 141 ```py 142 cells: list[str] = [] 143 vec = Vec(cells) # This DOES NOT copy the `cells`. 144 145 cells.append("foo") 146 vec[0] == "foo" # True 147 ``` 148 149 The opposite of the above is to initialize the vec from either 150 an iterable or args, or copy the list. 151 152 ```py 153 from sain.collections import vec, Vec 154 155 # Creates a new vec and extend it with the elements. 156 from_args = vec.from_args("foo", "bar") 157 158 # inlined from another iterable. 159 from_iter = Vec(["foo", "bar"]) 160 161 # Copy the list into a vec. 162 vec = Vec(cells[:]) 163 cells.append("bar") 164 165 vec[1] # IndexError: "bar" doesn't exist in vec. 166 ``` 167 """ 168 169 __slots__ = ("_ptr", "_capacity") 170 171 @typing.overload 172 def __init__(self) -> None: ... 173 174 @typing.overload 175 def __init__(self, iterable: collections.Iterable[T]) -> None: ... 176 177 def __init__(self, iterable: collections.Iterable[T] | None = None) -> None: 178 # We won't allocate to build the list here. 179 # Instead, On first push or fist indexed set 180 # we allocate if it was None. 181 if isinstance(iterable, list): 182 # Calling `list()` on another list will copy it, So instead we just point to it. 183 self._ptr = iterable 184 elif isinstance(iterable, Vec): 185 self._ptr = iterable._ptr 186 # any other iterable that ain't a list needs to get copied into a new list. 187 else: 188 self._ptr: list[T] | None = list(iterable) if iterable else None 189 190 self._capacity: int | None = None 191 192 @classmethod 193 def with_capacity(cls, capacity: int) -> Vec[T]: 194 """Create a new `Vec` with at least the specified capacity. 195 This vec will be able to hold `capacity` elements without pushing further. 196 197 Check out `Vec.push_within_capacity` as well. 198 199 Example 200 ------- 201 ```py 202 vec = Vec.with_capacity(3) 203 assert vec.len() == 0 and vec.capacity() >= 3 204 205 vec.push(1) 206 vec.push(2) 207 vec.push(3) 208 print(vec.len()) # 3 209 210 # This won't push. 211 vec.push(4) 212 ``` 213 """ 214 v = cls() 215 v._capacity = capacity 216 return v 217 218 def as_ref(self) -> collections.Collection[T]: 219 """Return an immutable view over this vector elements. 220 221 This will *copy* `self` elements into a new tuple. 222 223 Example 224 ------- 225 ```py 226 vec = Vec((1,2,3)) 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() == 0 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 for element in vec.iter().map(str): 268 print(element) 269 ``` 270 """ 271 return _iter.Iter(self) 272 273 def is_empty(self) -> bool: 274 """Returns true if the vector contains no elements.""" 275 return not self._ptr 276 277 def split_off(self, at: int) -> Vec[T]: 278 """Split the vector off at the specified position, returning a new 279 vec at the range of `[at : len]`, leaving `self` at `[at : vec_len]`. 280 281 if this vec is empty, `self` is returned unchanged. 282 283 Example 284 ------- 285 ```py 286 origin = Vec((1, 2, 3, 4)) 287 split = vec.split_off(2) 288 289 print(origin, split) # [1, 2], [3, 4] 290 ``` 291 292 Raises 293 ------ 294 `RuntimeError` 295 This method will raise if `at` > `len(self)` 296 """ 297 len_ = self.len() 298 if at > len_: 299 raise RuntimeError( 300 f"Index `at` ({at}) should be <= than len of vector ({len_}) " 301 ) from None 302 303 # Either the list is empty or uninit. 304 if not self._ptr: 305 return self 306 307 split = self[at:len_] # split the items into a new vec. 308 del self._ptr[at:len_] # remove the items from the original list. 309 return split 310 311 def split_first(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 312 """Split the first and rest elements of the vector, If empty, `None` is returned. 313 314 Example 315 ------- 316 ```py 317 vec = Vec([1, 2, 3]) 318 split = vec.split_first() 319 assert split == Some((1, [2, 3])) 320 321 vec: Vec[int] = Vec() 322 split = vec.split_first() 323 assert split.is_none() 324 ``` 325 """ 326 if not self._ptr: 327 return _option.NOTHING # pyright: ignore 328 329 # optimized to only one element in the vector. 330 if self.len() == 1: 331 return _option.Some((self[0], ())) 332 333 first, *rest = self._ptr 334 return _option.Some((first, rest)) 335 336 def split_last(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 337 """Split the last and rest elements of the vector, If empty, `None` is returned. 338 339 Example 340 ------- 341 ```py 342 vec = Vec([1, 2, 3]) 343 last, rest = vec.split_last().unwrap() 344 assert (last, rest) == [3, [1, 2]] 345 ``` 346 """ 347 if not self._ptr: 348 return _option.NOTHING # pyright: ignore 349 350 # optimized to only one element in the vector. 351 if self.len() == 1: 352 return _option.Some((self[0], ())) 353 354 last = self[-1] 355 return _option.Some((last, [*self[:-1]])) 356 357 def split_at(self, mid: int) -> tuple[Vec[T], Vec[T]]: 358 """Divide `self` into two at an index. 359 360 The first will contain all elements from `[0:mid]` excluding `mid` it self. 361 and the second will contain the remaninig elements. 362 363 if `mid` > `self.len()`, Then all elements will be moved to the left, 364 returning an empty vec in right. 365 366 Example 367 ------- 368 ```py 369 buffer = Vec((1, 2, 3, 4)) 370 left, right = buffer.split_at(0) 371 assert left == [] and right == [1, 2, 3, 4] 372 373 left, right = buffer.split_at(2) 374 assert left == [1, 2] and right == [2, 3] 375 ``` 376 377 The is roughly the implementation 378 ```py 379 self[0:mid], self[mid:] 380 ``` 381 """ 382 return self[0:mid], self[mid:] 383 384 def swap(self, a: int, b: int): 385 """Swap two elements in the vec. 386 387 if `a` equals to `b` then it's guaranteed that elements won't change value. 388 389 Example 390 ------- 391 ```py 392 buf = Vec([1, 2, 3, 4]) 393 buf.swap(0, 3) 394 assert buf == [4, 2, 3, 1] 395 ``` 396 397 Raises 398 ------ 399 IndexError 400 If the positions of `a` or `b` are out of index. 401 """ 402 if self[a] == self[b]: 403 return 404 405 self[a], self[b] = self[b], self[a] 406 407 def swap_unchecked(self, a: int, b: int): 408 """Swap two elements in the vec. without checking if `a` == `b`. 409 410 If you care about `a` and `b` equality, see `Vec.swap`. 411 412 Example 413 ------- 414 ```py 415 buf = Vec([1, 2, 3, 1]) 416 buf.swap_unchecked(0, 3) 417 assert buf == [1, 2, 3, 1] 418 ``` 419 420 Raises 421 ------ 422 IndexError 423 If the positions of `a` or `b` are out of index. 424 """ 425 self[a], self[b] = self[b], self[a] 426 427 def first(self) -> _option.Option[T]: 428 """Get the first element in this vec, returning `None` if there's none. 429 430 Example 431 ------- 432 ```py 433 vec = Vec((1,2,3)) 434 first = vec.first() 435 assert ~first == 1 436 ``` 437 """ 438 return self.get(0) 439 440 def last(self) -> _option.Option[T]: 441 """Get the last element in this vec, returning `None` if there's none. 442 443 Example 444 ------- 445 ```py 446 vec = Vec([1, 2, 3, 4]) 447 first = vec.last() 448 assert ~first == 4 449 ``` 450 """ 451 return self.get(-1) 452 453 def truncate(self, size: int) -> None: 454 """Shortens the vec, keeping the first `size` elements and dropping the rest. 455 456 Example 457 ------- 458 ```py 459 vec = Vec([1,2,3]) 460 vec.truncate(1) 461 assert vec == [1] 462 ``` 463 """ 464 if not self._ptr: 465 return 466 467 del self._ptr[size:] 468 469 def retain(self, f: collections.Callable[[T], bool]) -> None: 470 """Remove elements from this vec in-place while `f()` returns `True`. 471 472 In other words, filter this vector based on `f()`. 473 474 Example 475 ------- 476 ```py 477 vec = Vec([1, 2, 3]) 478 vec.retain(lambda elem: elem > 1) 479 480 assert vec == [2, 3] 481 ``` 482 """ 483 if not self._ptr: 484 return 485 486 for idx, e in enumerate(self._ptr): 487 if f(e): 488 del self._ptr[idx] 489 490 def swap_remove(self, item: T) -> T: 491 """Remove the first appearance of `item` from this vector and return it. 492 493 Raises 494 ------ 495 * `ValueError`: if `item` is not in this vector. 496 * `MemoryError`: if this vector hasn't allocated, Aka nothing has been pushed to it. 497 498 Example 499 ------- 500 ```py 501 vec = Vec(('a', 'b', 'c')) 502 element = vec.remove('a') 503 assert vec == ['b', 'c'] and element == 'a' 504 ``` 505 """ 506 if self._ptr is None: 507 raise MemoryError("Vec is unallocated.") from None 508 509 return self._ptr.pop(self.index(item)) 510 511 def fill(self, value: T) -> None: 512 """Fill `self` with the given `value`. 513 514 Nothing happens if the vec is empty or unallocated. 515 516 Example 517 ```py 518 a = Vec([0, 1, 2, 3]) 519 a.fill(0) 520 assert a == [0, 0, 0, 0] 521 ``` 522 """ 523 if not self._ptr: 524 return 525 526 for n, _ in enumerate(self): 527 self[n] = value 528 529 def push(self, item: T) -> None: 530 """Push an element at the end of the vector. 531 532 Example 533 ------- 534 ```py 535 vec = Vec() 536 vec.push(1) 537 538 assert vec == [1] 539 ``` 540 """ 541 if self._capacity is not None: 542 self.push_within_capacity(item) 543 return 544 545 if self._ptr is None: 546 self._ptr = [] 547 548 self._ptr.append(item) 549 550 def push_within_capacity(self, x: T) -> Result[None, T]: 551 """Appends an element if there is sufficient spare capacity, otherwise an error is returned with the element. 552 553 Example 554 ------- 555 ```py 556 vec: Vec[int] = Vec.with_capacity(3) 557 for i in range(3): 558 match vec.push_within_capacity(i): 559 case Ok(_): 560 print("All good.") 561 case Err(elem): 562 print("Reached max cap :< cant push", elem) 563 ``` 564 565 Or you can also just call `Vec.push` and it will push if theres is sufficient capacity. 566 ```py 567 vec: Vec[int] = Vec.with_capacity(3) 568 569 vec.extend((1, 2, 3)) 570 vec.push(4) 571 572 assert vec.len() == 3 573 ``` 574 """ 575 if self._ptr is None: 576 self._ptr = [] 577 578 if self.len() == self._capacity: 579 return _result.Err(x) 580 581 self._ptr.append(x) 582 return _result.Ok(None) 583 584 def reserve(self, additional: int) -> None: 585 """Reserves capacity for at least additional more elements to be inserted in the given Vec<T>. 586 587 Example 588 ------- 589 ```py 590 vec = Vec.with_capacity(3) 591 is_vip = random.choice((True, False)) 592 593 for i in range(4): 594 match vec.push_within_capacity(i): 595 case Ok(_): 596 print("All good") 597 case Err(person): 598 # If the person is a VIP, then reserve for one more. 599 if is_vip: 600 vec.reserve(1) 601 continue 602 603 # is_vip was false. 604 print("Can't reserve for non-VIP members...", person) 605 break 606 ``` 607 """ 608 if self._capacity is not None: 609 self._capacity += additional 610 611 ########################## 612 # * Builtin Operations * 613 ########################## 614 615 # For people how are used to calling list.append 616 append = push 617 """An alias for `Vec.push` method.""" 618 619 def get(self, index: int) -> _option.Option[T]: 620 """Get the item at the given index, or `Some[None]` if its out of bounds. 621 622 Example 623 ------- 624 ```py 625 vec = Vec((1, 2, 3)) 626 vec.get(0) == Some(1) 627 vec.get(3) == Some(None) 628 ``` 629 """ 630 try: 631 return _option.Some(self[index]) 632 except IndexError: 633 return _option.NOTHING # pyright: ignore 634 635 def insert(self, index: int, value: T) -> None: 636 """Insert an element at the position `index`. 637 638 Example 639 -------- 640 ```py 641 vec = Vec((2, 3)) 642 vec.insert(0, 1) 643 assert vec == [1, 2, 3] 644 ``` 645 """ 646 self.__setitem__(index, value) 647 648 def pop(self, index: int = -1) -> _option.Option[T]: 649 """Removes the last element from a vector and returns it, or `sain.Some(None)` if it is empty. 650 651 Example 652 ------- 653 ```py 654 vec = Vec((1, 2, 3)) 655 assert vec.pop() == Some(3) 656 assert vec == [1, 2] 657 ``` 658 """ 659 if not self._ptr: 660 return _option.NOTHING # pyright: ignore 661 662 return _option.Some(self._ptr.pop(index)) 663 664 def pop_if(self, pred: collections.Callable[[T], bool]) -> _option.Option[T]: 665 """Removes the last element from a vector and returns it if `f` returns `True`, 666 or `None` if it is empty. 667 668 Example 669 ------- 670 ```py 671 vec = Vec((1, 2, 3)) 672 assert vec.pop_if(lambda num: num * 2 == 6) == Some(3) 673 assert vec == [1, 2] 674 ``` 675 """ 676 if not self._ptr: 677 return _option.NOTHING # pyright: ignore 678 679 if pred(self[-1]): 680 return self.pop() 681 682 return _option.NOTHING # pyright: ignore 683 684 def dedup(self) -> None: 685 """Removes duplicate elements from `self` in-place. 686 687 Example 688 ------- 689 ```py 690 vec = Vec([1, 2, 3, 3, 4, 1]) 691 vec.dedup() 692 assert vec == [1, 2, 3, 4] 693 """ 694 695 if not self._ptr: 696 return 697 698 seen: set[T] = set() 699 write_idx = 0 700 701 for read_idx, _ in enumerate(self._ptr): 702 if self._ptr[read_idx] not in seen: 703 seen.add(self._ptr[read_idx]) 704 self._ptr[write_idx] = self._ptr[read_idx] 705 write_idx += 1 706 707 del self._ptr[write_idx:] 708 709 def remove(self, item: T) -> None: 710 """Remove the first appearance of `item` from this vector. 711 712 Example 713 ------- 714 ```py 715 vec = Vec(('a', 'b', 'c')) 716 vec.remove('a') 717 assert vec == ['b', 'c'] 718 ``` 719 """ 720 if not self._ptr: 721 return 722 723 self._ptr.remove(item) 724 725 def extend(self, iterable: collections.Iterable[T]) -> None: 726 """Extend this vector from another iterable. 727 728 Example 729 ------- 730 ```py 731 vec = Vec((1, 2, 3)) 732 vec.extend((4, 5, 6)) 733 734 assert vec == [1, 2, 3, 4, 5, 6] 735 ``` 736 """ 737 if self._ptr is None: 738 self._ptr = [] 739 740 self._ptr.extend(iterable) 741 742 def copy(self) -> Vec[T]: 743 """Create a vector that copies all of its elements and place it into the new one. 744 745 Example 746 ------- 747 ```py 748 original = Vec((1,2,3)) 749 copy = original.copy() 750 copy.push(4) 751 752 print(original) # [1, 2, 3] 753 ``` 754 """ 755 return Vec(self._ptr[:]) if self._ptr else Vec() 756 757 def clear(self) -> None: 758 """Clear all elements of this vector. 759 760 Example 761 ------- 762 ```py 763 vec = Vec((1,2,3)) 764 vec.clear() 765 assert vec.len() == 0 766 ``` 767 """ 768 if not self._ptr: 769 return 770 771 self._ptr.clear() 772 773 def sort( 774 self, 775 *, 776 key: collections.Callable[[T], SupportsRichComparison] | None = None, 777 reverse: bool = False, 778 ) -> None: 779 """This method sorts the list in place, using only < comparisons between items. 780 781 Example 782 ------- 783 ```py 784 vec = Vec((2, 1, 3)) 785 vec.sort() 786 assert vec == [1, 2, 3] 787 ``` 788 """ 789 if not self._ptr: 790 return 791 792 # key can be `None` here just fine, idk why pyright is complaining. 793 self._ptr.sort(key=key, reverse=reverse) # pyright: ignore 794 795 def index( 796 self, item: T, start: typing.SupportsIndex = 0, end: int = _sys.maxsize 797 ) -> int: 798 # << Official documentation >> 799 """Return zero-based index in the vec of the first item whose value is equal to `item`. 800 Raises a ValueError if there is no such item. 801 802 Example 803 ------- 804 ```py 805 vec = Vec((1, 2, 3)) 806 assert vec.index(2) == 1 807 ``` 808 """ 809 if self._ptr is None: 810 raise ValueError from None 811 812 return self._ptr.index(item, start, end) 813 814 def count(self, item: T) -> int: 815 """Return the number of occurrences of `item` in the vec. 816 817 `0` is returned if the vector is empty or hasn't been initialized, as well if them item not found. 818 819 Example 820 -------- 821 ```py 822 vec = Vec((1, 2, 3, 3)) 823 assert vec.count(3) == 2 824 ``` 825 """ 826 if self._ptr is None: 827 return 0 828 829 return self._ptr.count(item) 830 831 def __len__(self) -> int: 832 return len(self._ptr) if self._ptr else 0 833 834 def __setitem__(self, index: int, value: T): 835 if not self._ptr: 836 raise IndexError from None 837 838 self._ptr[index] = value 839 840 @typing.overload 841 def __getitem__(self, index: slice) -> Vec[T]: ... 842 843 @typing.overload 844 def __getitem__(self, index: int) -> T: ... 845 846 def __getitem__(self, index: int | slice) -> T | Vec[T]: 847 if not self._ptr: 848 raise IndexError("Index out of range") 849 850 if isinstance(index, slice): 851 return Vec(self._ptr[index]) 852 853 return self._ptr[index] 854 855 def __delitem__(self, index: int) -> None: 856 if not self._ptr: 857 return 858 859 del self._ptr[index] 860 861 def __contains__(self, element: T) -> bool: 862 return element in self._ptr if self._ptr else False 863 864 def __iter__(self) -> collections.Iterator[T]: 865 if self._ptr is None: 866 return iter(()) 867 868 return self._ptr.__iter__() 869 870 def __repr__(self) -> str: 871 return _LIST_REPR if not self._ptr else repr(self._ptr) 872 873 def __eq__(self, other: object) -> bool: 874 if isinstance(other, Vec): 875 return self._ptr == other._ptr 876 877 elif isinstance(other, list): 878 return self._ptr == other 879 880 return NotImplemented 881 882 def __ne__(self, other: object) -> bool: 883 return not self.__eq__(other) 884 885 def __le__(self, other: list[T]) -> bool: 886 if not self._ptr: 887 return False 888 889 return self._ptr <= other 890 891 def __ge__(self, other: list[T]) -> bool: 892 if not self._ptr: 893 return False 894 895 return self._ptr >= other 896 897 def __lt__(self, other: list[T]) -> bool: 898 if not self._ptr: 899 return False 900 901 return self._ptr < other 902 903 def __gt__(self, other: list[T]) -> bool: 904 if not self._ptr: 905 return False 906 907 return self._ptr > other 908 909 def __bool__(self) -> bool: 910 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
A Vec
may be compared with another Vec
or a list
, which is what this type is built on-top of.
any other type will return False
vec = Vec([1, 2, 3])
assert vec == [1, 2, 3] # True
assert not 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 list will also get pushed into the vec
thats pointing to it.
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.
from sain.collections import vec, Vec
# 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[1] # IndexError: "bar" doesn't exist in vec.
177 def __init__(self, iterable: collections.Iterable[T] | None = None) -> None: 178 # We won't allocate to build the list here. 179 # Instead, On first push or fist indexed set 180 # we allocate if it was None. 181 if isinstance(iterable, list): 182 # Calling `list()` on another list will copy it, So instead we just point to it. 183 self._ptr = iterable 184 elif isinstance(iterable, Vec): 185 self._ptr = iterable._ptr 186 # any other iterable that ain't a list needs to get copied into a new list. 187 else: 188 self._ptr: list[T] | None = list(iterable) if iterable else None 189 190 self._capacity: int | None = None
192 @classmethod 193 def with_capacity(cls, capacity: int) -> Vec[T]: 194 """Create a new `Vec` with at least the specified capacity. 195 This vec will be able to hold `capacity` elements without pushing further. 196 197 Check out `Vec.push_within_capacity` as well. 198 199 Example 200 ------- 201 ```py 202 vec = Vec.with_capacity(3) 203 assert vec.len() == 0 and vec.capacity() >= 3 204 205 vec.push(1) 206 vec.push(2) 207 vec.push(3) 208 print(vec.len()) # 3 209 210 # This won't push. 211 vec.push(4) 212 ``` 213 """ 214 v = cls() 215 v._capacity = capacity 216 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)
218 def as_ref(self) -> collections.Collection[T]: 219 """Return an immutable view over this vector elements. 220 221 This will *copy* `self` elements into a new tuple. 222 223 Example 224 ------- 225 ```py 226 vec = Vec((1,2,3)) 227 assert vec.as_ref() == (1, 2, 3) 228 ``` 229 """ 230 return tuple(self)
Return an immutable view over this vector elements.
This will copy self
elements into a new tuple.
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() == 0 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() == 0
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 for element in vec.iter().map(str): 268 print(element) 269 ``` 270 """ 271 return _iter.Iter(self)
Return an iterator over this vector elements.
Example
vec = Vec([1 ,2, 3])
for element in vec.iter().map(str):
print(element)
273 def is_empty(self) -> bool: 274 """Returns true if the vector contains no elements.""" 275 return not self._ptr
Returns true if the vector contains no elements.
277 def split_off(self, at: int) -> Vec[T]: 278 """Split the vector off at the specified position, returning a new 279 vec at the range of `[at : len]`, leaving `self` at `[at : vec_len]`. 280 281 if this vec is empty, `self` is returned unchanged. 282 283 Example 284 ------- 285 ```py 286 origin = Vec((1, 2, 3, 4)) 287 split = vec.split_off(2) 288 289 print(origin, split) # [1, 2], [3, 4] 290 ``` 291 292 Raises 293 ------ 294 `RuntimeError` 295 This method will raise if `at` > `len(self)` 296 """ 297 len_ = self.len() 298 if at > len_: 299 raise RuntimeError( 300 f"Index `at` ({at}) should be <= than len of vector ({len_}) " 301 ) from None 302 303 # Either the list is empty or uninit. 304 if not self._ptr: 305 return self 306 307 split = self[at:len_] # split the items into a new vec. 308 del self._ptr[at:len_] # remove the items from the original list. 309 return split
Split the vector off at the specified position, returning a new
vec at the range of [at : len]
, leaving self
at [at : vec_len]
.
if this vec is empty, self
is returned unchanged.
Example
origin = Vec((1, 2, 3, 4))
split = vec.split_off(2)
print(origin, split) # [1, 2], [3, 4]
Raises
RuntimeError
: This method will raise ifat
>len(self)
311 def split_first(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 312 """Split the first and rest elements of the vector, If empty, `None` is returned. 313 314 Example 315 ------- 316 ```py 317 vec = Vec([1, 2, 3]) 318 split = vec.split_first() 319 assert split == Some((1, [2, 3])) 320 321 vec: Vec[int] = Vec() 322 split = vec.split_first() 323 assert split.is_none() 324 ``` 325 """ 326 if not self._ptr: 327 return _option.NOTHING # pyright: ignore 328 329 # optimized to only one element in the vector. 330 if self.len() == 1: 331 return _option.Some((self[0], ())) 332 333 first, *rest = self._ptr 334 return _option.Some((first, rest))
Split the first and rest elements of the vector, If empty, 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.is_none()
336 def split_last(self) -> _option.Option[tuple[T, collections.Sequence[T]]]: 337 """Split the last and rest elements of the vector, If empty, `None` is returned. 338 339 Example 340 ------- 341 ```py 342 vec = Vec([1, 2, 3]) 343 last, rest = vec.split_last().unwrap() 344 assert (last, rest) == [3, [1, 2]] 345 ``` 346 """ 347 if not self._ptr: 348 return _option.NOTHING # pyright: ignore 349 350 # optimized to only one element in the vector. 351 if self.len() == 1: 352 return _option.Some((self[0], ())) 353 354 last = self[-1] 355 return _option.Some((last, [*self[:-1]]))
Split the last and rest elements of the vector, If empty, None
is returned.
Example
vec = Vec([1, 2, 3])
last, rest = vec.split_last().unwrap()
assert (last, rest) == [3, [1, 2]]
357 def split_at(self, mid: int) -> tuple[Vec[T], Vec[T]]: 358 """Divide `self` into two at an index. 359 360 The first will contain all elements from `[0:mid]` excluding `mid` it self. 361 and the second will contain the remaninig elements. 362 363 if `mid` > `self.len()`, Then all elements will be moved to the left, 364 returning an empty vec in right. 365 366 Example 367 ------- 368 ```py 369 buffer = Vec((1, 2, 3, 4)) 370 left, right = buffer.split_at(0) 371 assert left == [] and right == [1, 2, 3, 4] 372 373 left, right = buffer.split_at(2) 374 assert left == [1, 2] and right == [2, 3] 375 ``` 376 377 The is roughly the implementation 378 ```py 379 self[0:mid], self[mid:] 380 ``` 381 """ 382 return self[0:mid], self[mid:]
Divide self
into two at an index.
The first will contain all elements from [0:mid]
excluding mid
it self.
and the second will contain the remaninig elements.
if mid
> self.len()
, Then all elements will be moved to the left,
returning an empty vec in right.
Example
buffer = Vec((1, 2, 3, 4))
left, right = buffer.split_at(0)
assert left == [] and right == [1, 2, 3, 4]
left, right = buffer.split_at(2)
assert left == [1, 2] and right == [2, 3]
The is roughly the implementation
self[0:mid], self[mid:]
384 def swap(self, a: int, b: int): 385 """Swap two elements in the vec. 386 387 if `a` equals to `b` then it's guaranteed that elements won't change value. 388 389 Example 390 ------- 391 ```py 392 buf = Vec([1, 2, 3, 4]) 393 buf.swap(0, 3) 394 assert buf == [4, 2, 3, 1] 395 ``` 396 397 Raises 398 ------ 399 IndexError 400 If the positions of `a` or `b` are out of index. 401 """ 402 if self[a] == self[b]: 403 return 404 405 self[a], self[b] = self[b], self[a]
Swap two elements in the vec.
if a
equals to b
then it's guaranteed that elements won't change value.
Example
buf = Vec([1, 2, 3, 4])
buf.swap(0, 3)
assert buf == [4, 2, 3, 1]
Raises
- IndexError: If the positions of
a
orb
are out of index.
407 def swap_unchecked(self, a: int, b: int): 408 """Swap two elements in the vec. without checking if `a` == `b`. 409 410 If you care about `a` and `b` equality, see `Vec.swap`. 411 412 Example 413 ------- 414 ```py 415 buf = Vec([1, 2, 3, 1]) 416 buf.swap_unchecked(0, 3) 417 assert buf == [1, 2, 3, 1] 418 ``` 419 420 Raises 421 ------ 422 IndexError 423 If the positions of `a` or `b` are out of index. 424 """ 425 self[a], self[b] = self[b], self[a]
Swap two elements in the vec. without checking if a
== b
.
If you care about a
and b
equality, see Vec.swap
.
Example
buf = Vec([1, 2, 3, 1])
buf.swap_unchecked(0, 3)
assert buf == [1, 2, 3, 1]
Raises
- IndexError: If the positions of
a
orb
are out of index.
427 def first(self) -> _option.Option[T]: 428 """Get the first element in this vec, returning `None` if there's none. 429 430 Example 431 ------- 432 ```py 433 vec = Vec((1,2,3)) 434 first = vec.first() 435 assert ~first == 1 436 ``` 437 """ 438 return self.get(0)
Get the first element in this vec, returning None
if there's none.
Example
vec = Vec((1,2,3))
first = vec.first()
assert ~first == 1
440 def last(self) -> _option.Option[T]: 441 """Get the last element in this vec, returning `None` if there's none. 442 443 Example 444 ------- 445 ```py 446 vec = Vec([1, 2, 3, 4]) 447 first = vec.last() 448 assert ~first == 4 449 ``` 450 """ 451 return self.get(-1)
Get the last element in this vec, returning None
if there's none.
Example
vec = Vec([1, 2, 3, 4])
first = vec.last()
assert ~first == 4
453 def truncate(self, size: int) -> None: 454 """Shortens the vec, keeping the first `size` elements and dropping the rest. 455 456 Example 457 ------- 458 ```py 459 vec = Vec([1,2,3]) 460 vec.truncate(1) 461 assert vec == [1] 462 ``` 463 """ 464 if not self._ptr: 465 return 466 467 del 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]
469 def retain(self, f: collections.Callable[[T], bool]) -> None: 470 """Remove elements from this vec in-place while `f()` returns `True`. 471 472 In other words, filter this vector based on `f()`. 473 474 Example 475 ------- 476 ```py 477 vec = Vec([1, 2, 3]) 478 vec.retain(lambda elem: elem > 1) 479 480 assert vec == [2, 3] 481 ``` 482 """ 483 if not self._ptr: 484 return 485 486 for idx, e in enumerate(self._ptr): 487 if f(e): 488 del self._ptr[idx]
Remove elements from this vec in-place 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]
490 def swap_remove(self, item: T) -> T: 491 """Remove the first appearance of `item` from this vector and return it. 492 493 Raises 494 ------ 495 * `ValueError`: if `item` is not in this vector. 496 * `MemoryError`: if this vector hasn't allocated, Aka nothing has been pushed to it. 497 498 Example 499 ------- 500 ```py 501 vec = Vec(('a', 'b', 'c')) 502 element = vec.remove('a') 503 assert vec == ['b', 'c'] and element == 'a' 504 ``` 505 """ 506 if self._ptr is None: 507 raise MemoryError("Vec is unallocated.") from None 508 509 return self._ptr.pop(self.index(item))
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 = Vec(('a', 'b', 'c'))
element = vec.remove('a')
assert vec == ['b', 'c'] and element == 'a'
511 def fill(self, value: T) -> None: 512 """Fill `self` with the given `value`. 513 514 Nothing happens if the vec is empty or unallocated. 515 516 Example 517 ```py 518 a = Vec([0, 1, 2, 3]) 519 a.fill(0) 520 assert a == [0, 0, 0, 0] 521 ``` 522 """ 523 if not self._ptr: 524 return 525 526 for n, _ in enumerate(self): 527 self[n] = value
Fill self
with the given value
.
Nothing happens if the vec is empty or unallocated.
Example
a = Vec([0, 1, 2, 3])
a.fill(0)
assert a == [0, 0, 0, 0]
529 def push(self, item: T) -> None: 530 """Push an element at the end of the vector. 531 532 Example 533 ------- 534 ```py 535 vec = Vec() 536 vec.push(1) 537 538 assert vec == [1] 539 ``` 540 """ 541 if self._capacity is not None: 542 self.push_within_capacity(item) 543 return 544 545 if self._ptr is None: 546 self._ptr = [] 547 548 self._ptr.append(item)
Push an element at the end of the vector.
Example
vec = Vec()
vec.push(1)
assert vec == [1]
550 def push_within_capacity(self, x: T) -> Result[None, T]: 551 """Appends an element if there is sufficient spare capacity, otherwise an error is returned with the element. 552 553 Example 554 ------- 555 ```py 556 vec: Vec[int] = Vec.with_capacity(3) 557 for i in range(3): 558 match vec.push_within_capacity(i): 559 case Ok(_): 560 print("All good.") 561 case Err(elem): 562 print("Reached max cap :< cant push", elem) 563 ``` 564 565 Or you can also just call `Vec.push` and it will push if theres is sufficient capacity. 566 ```py 567 vec: Vec[int] = Vec.with_capacity(3) 568 569 vec.extend((1, 2, 3)) 570 vec.push(4) 571 572 assert vec.len() == 3 573 ``` 574 """ 575 if self._ptr is None: 576 self._ptr = [] 577 578 if self.len() == self._capacity: 579 return _result.Err(x) 580 581 self._ptr.append(x) 582 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 if theres is sufficient capacity.
vec: Vec[int] = Vec.with_capacity(3)
vec.extend((1, 2, 3))
vec.push(4)
assert vec.len() == 3
584 def reserve(self, additional: int) -> None: 585 """Reserves capacity for at least additional more elements to be inserted in the given Vec<T>. 586 587 Example 588 ------- 589 ```py 590 vec = Vec.with_capacity(3) 591 is_vip = random.choice((True, False)) 592 593 for i in range(4): 594 match vec.push_within_capacity(i): 595 case Ok(_): 596 print("All good") 597 case Err(person): 598 # If the person is a VIP, then reserve for one more. 599 if is_vip: 600 vec.reserve(1) 601 continue 602 603 # is_vip was false. 604 print("Can't reserve for non-VIP members...", person) 605 break 606 ``` 607 """ 608 if self._capacity is not None: 609 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
529 def push(self, item: T) -> None: 530 """Push an element at the end of the vector. 531 532 Example 533 ------- 534 ```py 535 vec = Vec() 536 vec.push(1) 537 538 assert vec == [1] 539 ``` 540 """ 541 if self._capacity is not None: 542 self.push_within_capacity(item) 543 return 544 545 if self._ptr is None: 546 self._ptr = [] 547 548 self._ptr.append(item)
An alias for Vec.push
method.
619 def get(self, index: int) -> _option.Option[T]: 620 """Get the item at the given index, or `Some[None]` if its out of bounds. 621 622 Example 623 ------- 624 ```py 625 vec = Vec((1, 2, 3)) 626 vec.get(0) == Some(1) 627 vec.get(3) == Some(None) 628 ``` 629 """ 630 try: 631 return _option.Some(self[index]) 632 except IndexError: 633 return _option.NOTHING # pyright: ignore
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)
635 def insert(self, index: int, value: T) -> None: 636 """Insert an element at the position `index`. 637 638 Example 639 -------- 640 ```py 641 vec = Vec((2, 3)) 642 vec.insert(0, 1) 643 assert vec == [1, 2, 3] 644 ``` 645 """ 646 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]
648 def pop(self, index: int = -1) -> _option.Option[T]: 649 """Removes the last element from a vector and returns it, or `sain.Some(None)` if it is empty. 650 651 Example 652 ------- 653 ```py 654 vec = Vec((1, 2, 3)) 655 assert vec.pop() == Some(3) 656 assert vec == [1, 2] 657 ``` 658 """ 659 if not self._ptr: 660 return _option.NOTHING # pyright: ignore 661 662 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]
664 def pop_if(self, pred: collections.Callable[[T], bool]) -> _option.Option[T]: 665 """Removes the last element from a vector and returns it if `f` returns `True`, 666 or `None` if it is empty. 667 668 Example 669 ------- 670 ```py 671 vec = Vec((1, 2, 3)) 672 assert vec.pop_if(lambda num: num * 2 == 6) == Some(3) 673 assert vec == [1, 2] 674 ``` 675 """ 676 if not self._ptr: 677 return _option.NOTHING # pyright: ignore 678 679 if pred(self[-1]): 680 return self.pop() 681 682 return _option.NOTHING # pyright: ignore
Removes the last element from a vector and returns it if f
returns True
,
or None
if it is empty.
Example
vec = Vec((1, 2, 3))
assert vec.pop_if(lambda num: num * 2 == 6) == Some(3)
assert vec == [1, 2]
684 def dedup(self) -> None: 685 """Removes duplicate elements from `self` in-place. 686 687 Example 688 ------- 689 ```py 690 vec = Vec([1, 2, 3, 3, 4, 1]) 691 vec.dedup() 692 assert vec == [1, 2, 3, 4] 693 """ 694 695 if not self._ptr: 696 return 697 698 seen: set[T] = set() 699 write_idx = 0 700 701 for read_idx, _ in enumerate(self._ptr): 702 if self._ptr[read_idx] not in seen: 703 seen.add(self._ptr[read_idx]) 704 self._ptr[write_idx] = self._ptr[read_idx] 705 write_idx += 1 706 707 del self._ptr[write_idx:]
Removes duplicate elements from self
in-place.
Example
```py vec = Vec([1, 2, 3, 3, 4, 1]) vec.dedup() assert vec == [1, 2, 3, 4]
709 def remove(self, item: T) -> None: 710 """Remove the first appearance of `item` from this vector. 711 712 Example 713 ------- 714 ```py 715 vec = Vec(('a', 'b', 'c')) 716 vec.remove('a') 717 assert vec == ['b', 'c'] 718 ``` 719 """ 720 if not self._ptr: 721 return 722 723 self._ptr.remove(item)
Remove the first appearance of item
from this vector.
Example
vec = Vec(('a', 'b', 'c'))
vec.remove('a')
assert vec == ['b', 'c']
725 def extend(self, iterable: collections.Iterable[T]) -> None: 726 """Extend this vector from another iterable. 727 728 Example 729 ------- 730 ```py 731 vec = Vec((1, 2, 3)) 732 vec.extend((4, 5, 6)) 733 734 assert vec == [1, 2, 3, 4, 5, 6] 735 ``` 736 """ 737 if self._ptr is None: 738 self._ptr = [] 739 740 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]
742 def copy(self) -> Vec[T]: 743 """Create a vector that copies all of its elements and place it into the new one. 744 745 Example 746 ------- 747 ```py 748 original = Vec((1,2,3)) 749 copy = original.copy() 750 copy.push(4) 751 752 print(original) # [1, 2, 3] 753 ``` 754 """ 755 return Vec(self._ptr[:]) if self._ptr else Vec()
Create a vector that copies all of its elements and place it into the new one.
Example
original = Vec((1,2,3))
copy = original.copy()
copy.push(4)
print(original) # [1, 2, 3]
757 def clear(self) -> None: 758 """Clear all elements of this vector. 759 760 Example 761 ------- 762 ```py 763 vec = Vec((1,2,3)) 764 vec.clear() 765 assert vec.len() == 0 766 ``` 767 """ 768 if not self._ptr: 769 return 770 771 self._ptr.clear()
Clear all elements of this vector.
Example
vec = Vec((1,2,3))
vec.clear()
assert vec.len() == 0
773 def sort( 774 self, 775 *, 776 key: collections.Callable[[T], SupportsRichComparison] | None = None, 777 reverse: bool = False, 778 ) -> None: 779 """This method sorts the list in place, using only < comparisons between items. 780 781 Example 782 ------- 783 ```py 784 vec = Vec((2, 1, 3)) 785 vec.sort() 786 assert vec == [1, 2, 3] 787 ``` 788 """ 789 if not self._ptr: 790 return 791 792 # key can be `None` here just fine, idk why pyright is complaining. 793 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]
795 def index( 796 self, item: T, start: typing.SupportsIndex = 0, end: int = _sys.maxsize 797 ) -> int: 798 # << Official documentation >> 799 """Return zero-based index in the vec of the first item whose value is equal to `item`. 800 Raises a ValueError if there is no such item. 801 802 Example 803 ------- 804 ```py 805 vec = Vec((1, 2, 3)) 806 assert vec.index(2) == 1 807 ``` 808 """ 809 if self._ptr is None: 810 raise ValueError from None 811 812 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
814 def count(self, item: T) -> int: 815 """Return the number of occurrences of `item` in the vec. 816 817 `0` is returned if the vector is empty or hasn't been initialized, as well if them item not found. 818 819 Example 820 -------- 821 ```py 822 vec = Vec((1, 2, 3, 3)) 823 assert vec.count(3) == 2 824 ``` 825 """ 826 if self._ptr is None: 827 return 0 828 829 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
86@typing.runtime_checkable 87class Error(ToString, typing.Protocol): 88 """`Error` is an interface usually used for values that returns `sain.Result[T, E]` 89 90 where `E` is an implementation of this interface. 91 92 Example 93 ------- 94 ```py 95 import requests 96 from dataclasses import dataclass 97 98 from sain import Error, Option, Some 99 from sain import Result, Ok, Err 100 101 # Base error. 102 class HTTPError(Error): ... 103 104 @dataclass 105 class NotFound(HTTPError): 106 message = "The response returned [404]: not found." 107 http_status = 404 108 response: requests.Response 109 110 def description(self) -> str: 111 return "Couldn't find what you're looking for " + self.response.url 112 113 # It is not necessary to define this method, 114 # it just gives more context to the user handling this error. 115 def source(self) -> Option[type[HTTPError]]: 116 return Some(HTTPError) 117 118 @dataclass 119 class UserNotFound(NotFound): 120 user_id: int 121 122 def __post_init__(self) -> None: 123 request = self.response.request 124 self.message = f"User {self.user_id} fetched from {request.path_url} was not found." 125 126 # It is not necessary to define this method, 127 # it just gives more context to the user handling this error. 128 def source(self) -> Option[type[NotFound]]: 129 return Some(NotFound) 130 131 def description(self) -> str: 132 return f"Couldn't find the resource: {self.response.raw}." 133 134 # A simple request that handles [404] responses. 135 def request( 136 url: str, 137 resourceful: bool = False, 138 uid: int 139 ) -> Result[requests.Response, HTTPError]: 140 response = requests.get( 141 url, 142 json={"resourceful": True, "user_id": uid} 143 if resourceful else None 144 ) 145 146 if response.status_code == 404: 147 if resourceful: 148 return Err(UserNotFound(response, uid)) 149 return Err(NotFound(response)) 150 151 return Ok(response) 152 153 # Execute the request 154 match request("some-url.com', True, uid=0): 155 case Ok(response): 156 # Deal with the response 157 ... 158 case Err(why): 159 # Deal with the error. 160 print(why.message) 161 ``` 162 """ 163 164 __slots__ = ("message",) 165 166 def __init__(self, message: str = "") -> None: 167 self.message = message 168 """A basic error message.""" 169 170 def source(self) -> Option[type[Error]]: 171 """The source of this error, if any.""" 172 return _option.NOTHING # pyright: ignore 173 174 def description(self) -> str: 175 """Context for this error.""" 176 return "" 177 178 def to_string(self) -> str: 179 return self.__repr__() 180 181 def __repr__(self) -> str: 182 source = None if (src := self.source()).is_none() else src 183 return ( 184 f'{type(self).__qualname__}(message: "{self.message}, source: {source!r})' 185 ) 186 187 def __str__(self) -> str: 188 return self.message 189 190 # An error is always falsy. 191 def __bool__(self) -> typing.Literal[False]: 192 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)
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
170 def source(self) -> Option[type[Error]]: 171 """The source of this error, if any.""" 172 return _option.NOTHING # pyright: ignore
The source of this error, if any.
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 # pyright: ignore 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 # pyright: ignore 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()
115@typing.runtime_checkable 116class From(typing.Protocol[T_co]): 117 """Used to do value-to-value conversions while consuming the input value. It is the reciprocal of Into. 118 119 As the Rust documentation says, One should always prefer implementing From over Into 120 because implementing From automatically provides one with an implementation of Into. 121 122 But there's no such thing in Python, as it's impossible to auto-impl `Into<T>` for all types 123 that impl `From<T>`. 124 125 So for the sake of simplicity, You should implement whichever interface you want deal with, 126 Or simply, implement both as the same time. 127 128 Example 129 ------- 130 ```py 131 @dataclass 132 class Id(From[str]): 133 value: int 134 135 @classmethod 136 def from_t(cls, value: str) -> Self: 137 return cls(value=int(value)) 138 139 ``` 140 """ 141 142 __slots__ = () 143 144 @classmethod 145 def from_t(cls, value: T_co) -> Self: 146 """Perform the conversion.""" 147 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))
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
150@typing.runtime_checkable 151class TryFrom(typing.Protocol[T_co, E]): 152 """Simple and safe type conversions that may fail in a controlled way under some circumstances. 153 It is the reciprocal of `TryInto`. 154 155 It is useful to implement this when you know that the conversion may fail in some way. 156 157 Generic Implementations 158 ------------------- 159 This interface takes two type arguments, and return `Result<Self, E>` 160 161 * `T`: Which's the first generic `T` is the type that's being converted from. 162 * `E`: If the conversion fails in a way, this is what will return as the error. 163 * `Self`: Which's the instance of the class that is being converted into. 164 165 Example 166 ------- 167 ```py 168 @dataclass 169 class Id(TryFrom[str, str]): 170 value: int 171 172 @classmethod 173 def try_from(cls, value: str) -> Result[Self, str]: 174 if not value.isnumeric(): 175 # NaN 176 return Err(f"Couldn't convert: {value} to self") 177 # otherwise convert it to an Id instance. 178 return Ok(value=cls(int(value))) 179 ``` 180 """ 181 182 __slots__ = () 183 184 @classmethod 185 def try_from(cls, value: T_co) -> Result[Self, E]: 186 """Perform the conversion.""" 187 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)))
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
190@typing.runtime_checkable 191class Into(typing.Protocol[T_cov]): 192 """Conversion from `self`, which may or may not be expensive. 193 194 Example 195 ------- 196 ```py 197 @dataclass 198 class Id(Into[str]): 199 value: int 200 201 def into(self) -> str: 202 return str(self.value) 203 ``` 204 """ 205 206 __slots__ = () 207 208 def into(self) -> T_cov: 209 """Perform the conversion.""" 210 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)
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
213@typing.runtime_checkable 214class TryInto(typing.Protocol[T, E]): 215 """An attempted conversion from `self`, which may or may not be expensive. 216 217 It is useful to implement this when you know that the conversion may fail in some way. 218 219 Generic Implementations 220 ------------------- 221 This interface takes two type arguments, and return `Result<T, E>` 222 223 * `T`: The first generic `T` is the type that's being converted into. 224 * `E`: If the conversion fails in a way, this is what will return as the error. 225 226 Example 227 ------- 228 ```py 229 @dataclass 230 class Id(TryInto[int, str]): 231 value: str 232 233 def try_into(self) -> Result[int, str]: 234 if not self.value.isnumeric(): 235 return Err(f"{self.value} is not a number...") 236 return Ok(int(self.value)) 237 ``` 238 """ 239 240 __slots__ = () 241 242 def try_into(self) -> Result[T, E]: 243 """Perform the conversion.""" 244 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))
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
247@typing.runtime_checkable 248class ToString(typing.Protocol): 249 """A trait for explicitly converting a value to a `str`. 250 251 Example 252 ------- 253 ```py 254 class Value[T: bytes](ToString): 255 buffer: T 256 257 def to_string(self) -> str: 258 return self.buffer.decode("utf-8") 259 ``` 260 """ 261 262 __slots__ = () 263 264 def to_string(self) -> str: 265 """Converts the given value to a `str`. 266 267 Example 268 -------- 269 ```py 270 i = 5 # assume `int` implements `ToString` 271 five = "5" 272 assert five == i.to_string() 273 ``` 274 """ 275 raise NotImplementedError 276 277 def __str__(self) -> str: 278 return self.to_string()
A trait for explicitly converting a value to a str
.
Example
class Value[T: bytes](ToString):
buffer: T
def to_string(self) -> str:
return self.buffer.decode("utf-8")
1430def _no_init_or_replace_init(self, *args, **kwargs): 1431 cls = type(self) 1432 1433 if cls._is_protocol: 1434 raise TypeError('Protocols cannot be instantiated') 1435 1436 # Already using a custom `__init__`. No need to calculate correct 1437 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1438 if cls.__init__ is not _no_init_or_replace_init: 1439 return 1440 1441 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1442 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1443 # searches for a proper new `__init__` in the MRO. The new `__init__` 1444 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1445 # instantiation of the protocol subclass will thus use the new 1446 # `__init__` and no longer call `_no_init_or_replace_init`. 1447 for base in cls.__mro__: 1448 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1449 if init is not _no_init_or_replace_init: 1450 cls.__init__ = init 1451 break 1452 else: 1453 # should not happen 1454 cls.__init__ = object.__init__ 1455 1456 cls.__init__(self, *args, **kwargs)
264 def to_string(self) -> str: 265 """Converts the given value to a `str`. 266 267 Example 268 -------- 269 ```py 270 i = 5 # assume `int` implements `ToString` 271 five = "5" 272 assert five == i.to_string() 273 ``` 274 """ 275 raise NotImplementedError
Converts the given value to a str
.
Example
i = 5 # assume `int` implements `ToString`
five = "5"
assert five == i.to_string()