最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Python typing: Is there a way to type a variable with the value of another variable - Stack Overflow

programmeradmin4浏览0评论

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.

Share Improve this question edited Feb 16 at 10:58 InSync 10.6k4 gold badges15 silver badges52 bronze badges asked Feb 16 at 10:50 Houston4444Houston4444 12 bronze badges New contributor Houston4444 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 1
  • 1 "so, of course, I can unpack args this way:" Yes, but only once. You can't change the static type of args from branch to branch, and the if statement really has nothing to do with it. The first type hint for args in the function scope sets its static type for the entire function, not just the block entered by types equalling a particular value. – chepner Commented Feb 16 at 18:34
Add a comment  | 

1 Answer 1

Reset to default 0

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.

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], ...]"
发布评论

评论列表(0)

  1. 暂无评论