【问题标题】:Type annotations for *args and **kwargs*args 和 **kwargs 的类型注释
【发布时间】:2016-08-30 03:20:41
【问题描述】:

我正在尝试使用抽象基类来编写 Python 的类型注释来编写一些接口。有没有办法注释*args**kwargs 的可能类型?

例如,如何表示函数的合理参数是一个int 或两个ints? type(args) 给出了Tuple 所以我的猜测是将类型注释为Union[Tuple[int, int], Tuple[int]],但这不起作用。

from typing import Union, Tuple

def foo(*args: Union[Tuple[int, int], Tuple[int]]):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

# ok
print(foo((1,)))
print(foo((1, 2)))
# mypy does not like this
print(foo(1))
print(foo(1, 2))

来自 mypy 的错误消息:

t.py: note: In function "foo":
t.py:6: error: Unsupported operand types for + ("tuple" and "Union[Tuple[int, int], Tuple[int]]")
t.py: note: At top level:
t.py:12: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:14: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 2 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"

mypy 不喜欢这个函数调用是有道理的,因为它希望调用本身有一个tuple。解压后的加法也出现了我看不懂的打字错误。

如何注释*args**kwargs 的合理类型?

【问题讨论】:

    标签: python type-hinting python-typing


    【解决方案1】:

    对于可变位置参数 (*args) 和可变关键字参数 (**kw),您只需指定 一个 此类参数的预期值。

    来自类型提示 PEP 的Arbitrary argument lists and default argument values section

    任意参数列表也可以进行类型注释,以便定义:

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

    是可以接受的,这意味着,例如,以下所有的函数调用都代表具有有效参数类型的函数调用:

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

    所以你想像这样指定你的方法:

    def foo(*args: int):
    

    但是,如果您的函数只能接受一个或两个整数值,则根本不应该使用*args,而是使用一个显式位置参数和第二个关键字参数:

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

    现在您的函数实际上仅限于一个或两个参数,并且如果指定,两者都必须是整数。 *args 总是 表示 0 或更多,并且不能被类型提示限制到更具体的范围。

    【讨论】:

    • 只是好奇,为什么要添加Optional? Python 发生了什么变化,还是你改变了主意?由于None 默认值,它仍然不是绝对必要的吗?
    • @Praxeolitic 是的,实际上,当您使用 None 作为默认值时,自动隐含的 Optional 注释会使某些用例变得更加困难,现在已从 PEP 中删除。
    • Here is a link discussing this 对于那些感兴趣的人。这听起来确实像是将来需要明确的Optional
    • Callable 实际上不支持这个:github.com/python/mypy/issues/5876
    • @ShitalShah:这不是那个问题的真正意义所在。 Callable 不支持任何提及*args**kwargs 句号 的类型提示。该特定问题是关于标记接受特定参数加上任意数量的其他参数的可调用对象,因此使用*args: Any, **kwargs: Any,这是两个包罗万象的非常具体的类型提示。对于将*args 和/或**kwargs 设置为更具体的情况,您可以使用Protocol
    【解决方案2】:

    正确的做法是使用@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))
    

    请注意,您不要在实际实现中添加@overload 或类型注释,这必须在最后。

    您需要新版本的 typing 和 mypy 才能获得对 @overload outside of stub files 的支持。

    您还可以使用它来改变返回的结果,以明确哪些参数类型对应于哪个返回类型。例如:

    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))
    

    【讨论】:

    • 我喜欢这个答案,因为它解决了更一般的情况。回想起来,我不应该使用 (type1)(type1, type1) 函数调用作为我的示例。也许(type1) vs (type2, type1) 会是一个更好的例子,并说明我为什么喜欢这个答案。这也允许不同的返回类型。但是,在您只有一种返回类型并且您的 *args*kwargs 都是相同类型的特殊情况下,Martjin 答案中的技术更有意义,因此两个答案都很有用。
    • 使用*args,其中有最大数量的参数(这里是 2 个)仍然是错误的
    • @MartijnPieters 为什么*args 在这里一定是错的?如果预期的调用是 (type1)(type2, type1),那么参数的数量是可变的,并且尾随参数没有适当的默认值。为什么有一个最大值很重要?
    • *args 确实存在 零个或多个、无上限、同质的论点,用于“将这些原封不动地传递”包罗万象。您有一个必需参数和一个可选参数。这是完全不同的,通常通过给第二个参数一个哨兵默认值来检测它是否被省略来处理。
    • 查看 PEP 后,这显然不是 @overload 的预期用途。虽然这个答案显示了一种单独注释 *args 类型的有趣方式,但更好的答案是,这根本不是应该做的事情。
    【解决方案3】:

    作为对上一个答案的简短补充,如果您尝试在 Python 2 文件上使用 mypy 并且需要使用 cmets 添加类型而不是注释,则需要为 argskwargs 的类型添加前缀分别与***

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

    mypy 将其视为与以下相同,Python 3.5 版本的foo

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

    【讨论】:

      【解决方案4】:

      还没有真正支持

      虽然您可以使用类型注释可变参数,但我觉得它不是很有用,因为它假定所有参数都是相同的类型。

      mypy 尚不支持 *args**kwargs 的正确类型注释,允许单独指定每个可变参数。有人提议在 mypy_extensions 模块上添加一个 Expand 助手,它的工作原理如下:

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

      GitHub issue 于 2018 年 1 月开业,但仍未关闭。请注意,虽然问题与**kwargs 有关,但Expand 语法也可能用于*args

      【讨论】:

        【解决方案5】:

        在某些情况下,**kwargs 的内容可以是多种类型。

        这似乎对我有用:

        from typing import Any
        
        def testfunc(**kwargs: Any) -> None:
            print(kwargs)
        

        from typing import Any, Optional
        
        def testfunc(**kwargs: Optional[Any]) -> None:
            print(kwargs)
        

        如果您觉得需要限制**kwargs 中的类型,我建议创建一个类似结构的对象并在那里添加类型。这可以通过数据类或 pydantic 来完成。

        from dataclasses import dataclass
        
        @dataclass
        class MyTypedKwargs:
           expected_variable: str
           other_expected_variable: int
        
        
        def testfunc(expectedargs: MyTypedKwargs) -> None:
            pass
        

        【讨论】:

        • 这实际上禁用了类型检查,不是吗?这就像完全省略了 kwargs 的注释。
        • **kwargs 是设计使然,技术上可以是任何东西。如果您知道自己得到了什么,我建议将其定义为类型化参数。这里的好处是,对于使用**kwargs 是可以接受/预期的情况,在 ides/tools 中,比如 pycharm,它不会给你一个类型不正确的通知。
        • 我部分不同意。我认为在某些情况下限制 **kwargs 或 *args 的类型是合理的。但我也看到类型检查和 **kwargs 不能很好地结合在一起(至少对于当前的 Python 版本)。也许您想将此添加到您的答案中,以更好地解决 OP 问题。
        • 是的,可能有用于键入 kwargs 的用例,但我倾向于使您的输入更清晰,而不是将它们归为 kwargs。
        【解决方案6】:

        如果想描述 kwargs 中预期的特定命名参数,可以改为传入 TypedDict(定义必需和可选参数)。可选参数是 kwargs。 注意:TypedDict 在 python >= 3.8 看这个例子:

        import typing
        
        class RequiredProps(typing.TypedDict):
            # all of these must be present
            a: int
            b: str
        
        class OptionalProps(typing.TypedDict, total=False):
            # these can be included or they can be omitted
            c: int
            d: int
        
        class ReqAndOptional(RequiredProps, OptionalProps):
            pass
        
        def hi(req_and_optional: ReqAndOptional):
            print(req_and_optional)
        

        【讨论】:

        • 有时你会沉迷于一种做某事的方式,而忘记了简单的方式。谢谢你。
        猜你喜欢
        • 2020-08-17
        • 1970-01-01
        • 2017-11-03
        • 2020-09-16
        • 2017-01-28
        • 2021-07-26
        • 2022-09-22
        • 1970-01-01
        • 2022-12-03
        相关资源
        最近更新 更多