Type annotations for *args and **kwargs

后端 未结 4 1498
無奈伤痛
無奈伤痛 2020-12-04 09:05

I\'m trying out Python\'s type annotations with abstract base classes to write some interfaces. Is there a way to annotate the possible types of *args and

相关标签:
4条回答
  • 2020-12-04 09:31

    For variable positional arguments (*args) and variable keyword arguments (**kw) you only need to specify the expected value for one such argument.

    From the Arbitrary argument lists and default argument values section of the Type Hints PEP:

    Arbitrary argument lists can as well be type annotated, so that the definition:

    def foo(*args: str, **kwds: int): ...
    

    is acceptable and it means that, e.g., all of the following represent function calls with valid types of arguments:

    foo('a', 'b', 'c')
    foo(x=1, y=2)
    foo('', z=0)
    

    So you'd want to specify your method like this:

    def foo(*args: int):
    

    However, if your function can only accept either one or two integer values, you should not use *args at all, use one explicit positional argument and a second keyword argument:

    def foo(first: int, second: Optional[int] = None):
    

    Now your function is actually limited to one or two arguments, and both must be integers if specified. *args always means 0 or more, and can't be limited by type hints to a more specific range.

    0 讨论(0)
  • 2020-12-04 09:43

    As a short addition to the previous answer, if you're trying to use mypy on Python 2 files and need to use comments to add types instead of annotations, you need to prefix the types for args and kwargs with * and ** respectively:

    def foo(param, *args, **kwargs):
        # type: (bool, *str, **int) -> None
        pass
    

    This is treated by mypy as being the same as the below, Python 3.5 version of foo:

    def foo(param: bool, *args: str, **kwargs: int) -> None:
        pass
    
    0 讨论(0)
  • 2020-12-04 09:45

    The proper way to do this is using @overload

    from typing import overload
    
    @overload
    def foo(arg1: int, arg2: int) -> int:
        ...
    
    @overload
    def foo(arg: int) -> int:
        ...
    
    def foo(*args):
        try:
            i, j = args
            return i + j
        except ValueError:
            assert len(args) == 1
            i = args[0]
            return i
    
    print(foo(1))
    print(foo(1, 2))
    

    Note that you do not add @overload or type annotations to the actual implementation, which must come last.

    You'll need a newish version of both typing and mypy to get support for @overload outside of stub files.

    You can also use this to vary the returned result in a way that makes explicit which argument types correspond with which return type. e.g.:

    from typing import Tuple, overload
    
    @overload
    def foo(arg1: int, arg2: int) -> Tuple[int, int]:
        ...
    
    @overload
    def foo(arg: int) -> int:
        ...
    
    def foo(*args):
        try:
            i, j = args
            return j, i
        except ValueError:
            assert len(args) == 1
            i = args[0]
            return i
    
    print(foo(1))
    print(foo(1, 2))
    
    0 讨论(0)
  • 2020-12-04 09:53

    Not really supported yet

    While you can annotate variadic arguments with a type, I don't find it very useful because it assumes that all arguments are of the same type.

    The proper type annotation of *args and **kwargs that allows specifying each variadic argument separately is not supported by mypy yet. There is a proposal for adding an Expand helper on mypy_extensions module, it would work like this:

    class Options(TypedDict):
        timeout: int
        alternative: str
        on_error: Callable[[int], None]
        on_timeout: Callable[[], None]
        ...
    
    def fun(x: int, *, **options: Expand[Options]) -> None:
        ...
    

    The GitHub issue was opened on January 2018 but it's still not closed. Note that while the issue is about **kwargs, the Expand syntax will likely be used for *args as well.

    0 讨论(0)
提交回复
热议问题