sain.convert
Protocols for conversions between types.
The protocols in this module provide a way to convert from one type to another type. Each trait serves a different purpose:
- Implement the From trait for consuming value-to-value conversions
- Implement the Into trait for consuming value-to-value conversions to types outside the current crate
- The TryFrom and TryInto traits behave like From and Into, but should be implemented when the conversion can fail.
- Implement the
ToString
trait for explicitly converting objects to string.
Example
@dataclass
class Id(From[UUID], Into[int]):
id: int | float
@classmethod
def from_value(cls, value: UUID) -> Self:
# Keep in mind, this stores a 128 bit <long> integer.
return cls(int(value))
def into(self) -> int:
return int(self.id)
# Simply perform conversions.
from_uuid = Id.from_value(uuid4())
into_int = from_uuid.into()
For type conversions that may fail, two safe interfaces, TryInto
and TryFrom
exist which deal with that.
This is useful when you are doing a type conversion that may trivially succeed but may also need special handling.
@dataclass
class Message(Into[bytes], TryFrom[bytes, None]):
content: str
id: int
def into(self) -> bytes:
return json.dumps(self.__dict__).encode()
@classmethod
def try_from(cls, value: bytes) -> Result[Self, None]:
try:
payload = json.loads(value)
return Ok(cls(content=payload['content'], id=payload['id']))
except (json.decoder.JSONDecodeError, KeyError):
# Its rare to see a JSONDecodeError raised, but usually
# keys goes missing, which raises a KeyError.
return Err(None)
message_bytes = b'{"content": "content", "id": 0}'
match Message.try_from(message_bytes):
case Ok(message):
print("Successful conversion", message)
case Err(invalid_bytes):
print("Invalid bytes:", invalid_bytes)
payload = Message(content='content', id=0)
assert payload.into() == message_bytes
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""" 32Protocols for conversions between types. 33 34The protocols in this module provide a way to convert from one type to another type. Each trait serves a different purpose: 35 36* Implement the From trait for consuming value-to-value conversions 37* Implement the Into trait for consuming value-to-value conversions to types outside the current crate 38* The TryFrom and TryInto traits behave like From and Into, but should be implemented when the conversion can fail. 39* Implement the `ToString` trait for explicitly converting objects to string. 40 41Example 42-------- 43```py 44@dataclass 45class Id(From[UUID], Into[int]): 46 id: int | float 47 48 @classmethod 49 def from_value(cls, value: UUID) -> Self: 50 # Keep in mind, this stores a 128 bit <long> integer. 51 return cls(int(value)) 52 53 def into(self) -> int: 54 return int(self.id) 55 56# Simply perform conversions. 57from_uuid = Id.from_value(uuid4()) 58into_int = from_uuid.into() 59``` 60 61For type conversions that may fail, two safe interfaces, `TryInto` and `TryFrom` exist which deal with that. 62 63This is useful when you are doing a type conversion that may trivially succeed but may also need special handling. 64 65```py 66@dataclass 67class Message(Into[bytes], TryFrom[bytes, None]): 68 content: str 69 id: int 70 71 def into(self) -> bytes: 72 return json.dumps(self.__dict__).encode() 73 74 @classmethod 75 def try_from(cls, value: bytes) -> Result[Self, None]: 76 try: 77 payload = json.loads(value) 78 return Ok(cls(content=payload['content'], id=payload['id'])) 79 except (json.decoder.JSONDecodeError, KeyError): 80 # Its rare to see a JSONDecodeError raised, but usually 81 # keys goes missing, which raises a KeyError. 82 return Err(None) 83 84message_bytes = b'{"content": "content", "id": 0}' 85 86match Message.try_from(message_bytes): 87 case Ok(message): 88 print("Successful conversion", message) 89 case Err(invalid_bytes): 90 print("Invalid bytes:", invalid_bytes) 91 92payload = Message(content='content', id=0) 93assert payload.into() == message_bytes 94``` 95""" 96 97from __future__ import annotations 98 99__slots__ = ("Into", "TryInto", "From", "TryFrom", "ToString") 100 101import typing 102 103from sain.macros import rustc_diagnostic_item 104 105if typing.TYPE_CHECKING: 106 from typing_extensions import Self 107 108 from sain import Result 109 110T = typing.TypeVar("T") 111T_co = typing.TypeVar("T_co", contravariant=True) 112T_cov = typing.TypeVar("T_cov", covariant=True) 113E = typing.TypeVar("E") 114 115 116@rustc_diagnostic_item("convert_identity") 117def identity(x: T) -> T: 118 """The identity function. 119 120 This is a function that returns the same value as the input. 121 122 While it might seem strange to have a function that just returns back the input, there are some interesting uses. 123 124 Example 125 ------- 126 Using `identity` to do nothing in a sequence of operations. 127 ```py 128 from sain.convert import identity 129 130 def mangle(x: int) -> int: 131 return x + 1 132 133 arr = [identity, mangle] 134 ``` 135 136 Using `identity` to do nothing conditionally. 137 ```py 138 from sain.convert import identity 139 140 do_stuff = identity if condition else mangle 141 results = do_stuff(5) 142 ``` 143 """ 144 return x 145 146 147@rustc_diagnostic_item("From") 148@typing.runtime_checkable 149class From(typing.Protocol[T_co]): 150 """Used to do value-to-value conversions while consuming the input value. It is the reciprocal of Into. 151 152 As the Rust documentation says, One should always prefer implementing From over Into 153 because implementing From automatically provides one with an implementation of Into. 154 155 But there's no such thing in Python, as it's impossible to auto-impl `Into<T>` for all types 156 that impl `From<T>`. 157 158 So for the sake of simplicity, You should implement whichever interface you want deal with, 159 Or simply, implement both as the same time. 160 161 Example 162 ------- 163 ```py 164 @dataclass 165 class Id(From[str]): 166 value: int 167 168 @classmethod 169 def from_value(cls, value: str) -> Self: 170 return cls(value=int(value)) 171 172 ``` 173 """ 174 175 __slots__ = () 176 177 @classmethod 178 def from_value(cls, value: T_co) -> Self: 179 """Perform the conversion.""" 180 raise NotImplementedError 181 182 183@rustc_diagnostic_item("TryFrom") 184@typing.runtime_checkable 185class TryFrom(typing.Protocol[T_co, E]): 186 """Simple and safe type conversions that may fail in a controlled way under some circumstances. 187 It is the reciprocal of `TryInto`. 188 189 It is useful to implement this when you know that the conversion may fail in some way. 190 191 Generic Implementations 192 ------------------- 193 This interface takes two type arguments, and return `Result<Self, E>` 194 195 * `T`: Which's the first generic `T` is the type that's being converted from. 196 * `E`: If the conversion fails in a way, this is what will return as the error. 197 * `Self`: Which's the instance of the class that is being converted into. 198 199 Example 200 ------- 201 ```py 202 @dataclass 203 class Id(TryFrom[str, str]): 204 value: int 205 206 @classmethod 207 def try_from(cls, value: str) -> Result[Self, str]: 208 if not value.isnumeric(): 209 # NaN 210 return Err(f"Couldn't convert: {value} to self") 211 # otherwise convert it to an Id instance. 212 return Ok(value=cls(int(value))) 213 ``` 214 """ 215 216 __slots__ = () 217 218 @classmethod 219 def try_from(cls, value: T_co) -> Result[Self, E]: 220 """Perform the conversion.""" 221 raise NotImplementedError 222 223 224@rustc_diagnostic_item("TryFrom") 225@typing.runtime_checkable 226class Into(typing.Protocol[T_cov]): 227 """Conversion from `self`, which may or may not be expensive. 228 229 Example 230 ------- 231 ```py 232 @dataclass 233 class Id(Into[str]): 234 value: int 235 236 def into(self) -> str: 237 return str(self.value) 238 ``` 239 """ 240 241 __slots__ = () 242 243 def into(self) -> T_cov: 244 """Perform the conversion.""" 245 raise NotImplementedError 246 247 248@rustc_diagnostic_item("TryInto") 249@typing.runtime_checkable 250class TryInto(typing.Protocol[T, E]): 251 """An attempted conversion from `self`, which may or may not be expensive. 252 253 It is useful to implement this when you know that the conversion may fail in some way. 254 255 Generic Implementations 256 ------------------- 257 This interface takes two type arguments, and return `Result<T, E>` 258 259 * `T`: The first generic `T` is the type that's being converted into. 260 * `E`: If the conversion fails in a way, this is what will return as the error. 261 262 Example 263 ------- 264 ```py 265 @dataclass 266 class Id(TryInto[int, str]): 267 value: str 268 269 def try_into(self) -> Result[int, str]: 270 if not self.value.isnumeric(): 271 return Err(f"{self.value} is not a number...") 272 return Ok(int(self.value)) 273 ``` 274 """ 275 276 __slots__ = () 277 278 def try_into(self) -> Result[T, E]: 279 """Perform the conversion.""" 280 raise NotImplementedError 281 282 283@rustc_diagnostic_item("ToString") 284@typing.runtime_checkable 285class ToString(typing.Protocol): 286 """A trait for explicitly converting a value to a `str`. 287 288 Example 289 ------- 290 ```py 291 class Value[T: bytes](ToString): 292 buffer: T 293 294 def to_string(self) -> str: 295 return self.buffer.decode("utf-8") 296 ``` 297 """ 298 299 __slots__ = () 300 301 def to_string(self) -> str: 302 """Converts the given value to a `str`. 303 304 Example 305 -------- 306 ```py 307 i = 5 # assume `int` implements `ToString` 308 five = "5" 309 assert five == i.to_string() 310 ``` 311 """ 312 raise NotImplementedError 313 314 def __str__(self) -> str: 315 return self.to_string()
117@rustc_diagnostic_item("convert_identity") 118def identity(x: T) -> T: 119 """The identity function. 120 121 This is a function that returns the same value as the input. 122 123 While it might seem strange to have a function that just returns back the input, there are some interesting uses. 124 125 Example 126 ------- 127 Using `identity` to do nothing in a sequence of operations. 128 ```py 129 from sain.convert import identity 130 131 def mangle(x: int) -> int: 132 return x + 1 133 134 arr = [identity, mangle] 135 ``` 136 137 Using `identity` to do nothing conditionally. 138 ```py 139 from sain.convert import identity 140 141 do_stuff = identity if condition else mangle 142 results = do_stuff(5) 143 ``` 144 """ 145 return x
The identity function.
This is a function that returns the same value as the input.
While it might seem strange to have a function that just returns back the input, there are some interesting uses.
Example
Using identity
to do nothing in a sequence of operations.
from sain.convert import identity
def mangle(x: int) -> int:
return x + 1
arr = [identity, mangle]
Using identity
to do nothing conditionally.
from sain.convert import identity
do_stuff = identity if condition else mangle
results = do_stuff(5)
Implementations
This function implements convert_identity in Rust.
148@rustc_diagnostic_item("From") 149@typing.runtime_checkable 150class From(typing.Protocol[T_co]): 151 """Used to do value-to-value conversions while consuming the input value. It is the reciprocal of Into. 152 153 As the Rust documentation says, One should always prefer implementing From over Into 154 because implementing From automatically provides one with an implementation of Into. 155 156 But there's no such thing in Python, as it's impossible to auto-impl `Into<T>` for all types 157 that impl `From<T>`. 158 159 So for the sake of simplicity, You should implement whichever interface you want deal with, 160 Or simply, implement both as the same time. 161 162 Example 163 ------- 164 ```py 165 @dataclass 166 class Id(From[str]): 167 value: int 168 169 @classmethod 170 def from_value(cls, value: str) -> Self: 171 return cls(value=int(value)) 172 173 ``` 174 """ 175 176 __slots__ = () 177 178 @classmethod 179 def from_value(cls, value: T_co) -> Self: 180 """Perform the conversion.""" 181 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_value(cls, value: str) -> Self:
return cls(value=int(value))
Implementations
This class implements From in Rust.
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
184@rustc_diagnostic_item("TryFrom") 185@typing.runtime_checkable 186class TryFrom(typing.Protocol[T_co, E]): 187 """Simple and safe type conversions that may fail in a controlled way under some circumstances. 188 It is the reciprocal of `TryInto`. 189 190 It is useful to implement this when you know that the conversion may fail in some way. 191 192 Generic Implementations 193 ------------------- 194 This interface takes two type arguments, and return `Result<Self, E>` 195 196 * `T`: Which's the first generic `T` is the type that's being converted from. 197 * `E`: If the conversion fails in a way, this is what will return as the error. 198 * `Self`: Which's the instance of the class that is being converted into. 199 200 Example 201 ------- 202 ```py 203 @dataclass 204 class Id(TryFrom[str, str]): 205 value: int 206 207 @classmethod 208 def try_from(cls, value: str) -> Result[Self, str]: 209 if not value.isnumeric(): 210 # NaN 211 return Err(f"Couldn't convert: {value} to self") 212 # otherwise convert it to an Id instance. 213 return Ok(value=cls(int(value))) 214 ``` 215 """ 216 217 __slots__ = () 218 219 @classmethod 220 def try_from(cls, value: T_co) -> Result[Self, E]: 221 """Perform the conversion.""" 222 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)))
Implementations
This class implements TryFrom in Rust.
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
225@rustc_diagnostic_item("TryFrom") 226@typing.runtime_checkable 227class Into(typing.Protocol[T_cov]): 228 """Conversion from `self`, which may or may not be expensive. 229 230 Example 231 ------- 232 ```py 233 @dataclass 234 class Id(Into[str]): 235 value: int 236 237 def into(self) -> str: 238 return str(self.value) 239 ``` 240 """ 241 242 __slots__ = () 243 244 def into(self) -> T_cov: 245 """Perform the conversion.""" 246 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)
Implementations
This class implements TryFrom in Rust.
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
249@rustc_diagnostic_item("TryInto") 250@typing.runtime_checkable 251class TryInto(typing.Protocol[T, E]): 252 """An attempted conversion from `self`, which may or may not be expensive. 253 254 It is useful to implement this when you know that the conversion may fail in some way. 255 256 Generic Implementations 257 ------------------- 258 This interface takes two type arguments, and return `Result<T, E>` 259 260 * `T`: The first generic `T` is the type that's being converted into. 261 * `E`: If the conversion fails in a way, this is what will return as the error. 262 263 Example 264 ------- 265 ```py 266 @dataclass 267 class Id(TryInto[int, str]): 268 value: str 269 270 def try_into(self) -> Result[int, str]: 271 if not self.value.isnumeric(): 272 return Err(f"{self.value} is not a number...") 273 return Ok(int(self.value)) 274 ``` 275 """ 276 277 __slots__ = () 278 279 def try_into(self) -> Result[T, E]: 280 """Perform the conversion.""" 281 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))
Implementations
This class implements TryInto in Rust.
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
284@rustc_diagnostic_item("ToString") 285@typing.runtime_checkable 286class ToString(typing.Protocol): 287 """A trait for explicitly converting a value to a `str`. 288 289 Example 290 ------- 291 ```py 292 class Value[T: bytes](ToString): 293 buffer: T 294 295 def to_string(self) -> str: 296 return self.buffer.decode("utf-8") 297 ``` 298 """ 299 300 __slots__ = () 301 302 def to_string(self) -> str: 303 """Converts the given value to a `str`. 304 305 Example 306 -------- 307 ```py 308 i = 5 # assume `int` implements `ToString` 309 five = "5" 310 assert five == i.to_string() 311 ``` 312 """ 313 raise NotImplementedError 314 315 def __str__(self) -> str: 316 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")
Implementations
This class implements ToString in Rust.
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
302 def to_string(self) -> str: 303 """Converts the given value to a `str`. 304 305 Example 306 -------- 307 ```py 308 i = 5 # assume `int` implements `ToString` 309 five = "5" 310 assert five == i.to_string() 311 ``` 312 """ 313 raise NotImplementedError
Converts the given value to a str
.
Example
i = 5 # assume `int` implements `ToString`
five = "5"
assert five == i.to_string()