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.
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.
1 Answer
Reset to default 0You 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.
Example
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], ...]"
args
from branch to branch, and theif
statement really has nothing to do with it. The first type hint forargs
in the function scope sets its static type for the entire function, not just the block entered bytypes
equalling a particular value. – chepner Commented Feb 16 at 18:34