Reputation: 1
I would like to know if there is the possibility to set a variable type depending on the value of another variable.
I use pyliblo3, it allows to send OSC messages (using UDP, TCP or UNIX socket), when a message is received, we can read the message in a function written this way:
def _message_received(path: str, types: str, args: tuple):
# Each character of 'types' defines the type of args items
# for example if types == 'sifb'
# it means that 'args' is of type tuple[str, int, float, bytes]
...
so, of course, I can unpack args this way:
def _message_received(path: str, types: str, args: tuple):
if types == 'sif':
args: tuple[str, int, float]
value_str, value_int, value_f = args
I wonder if it is possible to automatize this process, for example defining a function
def typed_args(args: tuple, types: str):
ret_args = []
for i in range(len(types)):
type_ = types[i]
arg = args[i]
if type_ == 's':
ret_args.append(str(arg))
elif type_ == 'f':
ret_args.append(float(arg))
elif type_ == 'i':
ret_args.append(int(arg))
return tuple(ret_args)
def _message_received(path: str, types: str, args: tuple):
if types == 'sif':
args = typed_args(args, types)
# of course here, pylance can't know the type of 'args'.
# I wonder if there is a way to force it to execute 'typed args'
# with the fact it knows that types == 'sif'
Can I write a function in order that pylance knows the types of the 'args' variable ? All I can know for the moment is that args is of type tuple[str | int | float | bytes]
, but pylance can't know nor its length, nor the types of its tuple items. It would be convenient when unpacking them.
Upvotes: 0
Views: 76
Reputation: 40833
You can write an overloaded TypeIs guard, but it is a lot of extra code. There will be one overload for each distinct type format. So if this is a frequently used idiom in the code it might make sense. And if there is ever a bug in the code, the guard can be configured to be strict about checking types, and not just trusting the type format.
from typing import overload, reveal_type, Any, Literal
from typing_extensions import TypeIs
RUNTIME_TYPE_CHECKING = True
@overload
def is_type(obj: Any, types: str, fmt: Literal['i']) -> TypeIs[int]: ...
@overload
def is_type(obj: Any, types: str, fmt: Literal['f']) -> TypeIs[float]: ...
@overload
def is_type(obj: Any, types: str, fmt: Literal['s']) -> TypeIs[str]: ...
@overload
def is_type(
obj: Any, types: str, fmt: Literal['ifs']
) -> TypeIs[tuple[int, float, str]]: ...
def is_type[T](obj: Any, types: str, fmt: str) -> TypeIs[T]:
if not RUNTIME_TYPE_CHECKING:
return types == fmt
match fmt:
case 'i':
return isinstance(obj, int)
case 'f':
return isinstance(obj, float)
case 's':
return isinstance(obj, str)
case _:
if isinstance(obj, tuple) and len(fmt) == len(obj):
for item, char_types, char_fmt in zip(obj, types, fmt):
# char_fmt is not a literal, so type checkers will not like
# thiw following check. However, it will work as expected
# when strictly checking types. So just tell type checker
# to ignore this line.
if not is_type(item, char_types, char_fmt): # type: ignore
return False
return True
else:
return False
def get_data() -> tuple[tuple[int | float | str, ...], str]:
return (1, 2.5, 'string'), 'ifs'
def main() -> None:
obj, types = get_data()
reveal_type(obj) # Revealed type is "tuple[Union[int, float, str], ...]"
# this guard will narrow the type according to the type format
if is_type(obj, types, 'ifs'):
x, y, z = obj
reveal_type(obj) # Revealed type is "tuple[int, float, str]"
reveal_type(x) # Revealed type is "int"
reveal_type(y) # Revealed type is "float"
reveal_type(z) # Revealed type is "str"
print(obj)
# after guard, the type widens to its previous value
reveal_type(obj) # Revealed type is "tuple[Union[int, float, str], ...]"
Upvotes: 0