【问题标题】:How does one pass keyword args with MYPY?如何使用 MYPY 传递关键字参数?
【发布时间】:2021-05-02 12:06:52
【问题描述】:

假设:

def foo(biz: str = 'biz',baz: str = 'baz', *args:Any, **kwargs:Any)-> str:
   return biz + baz

我这样使用:

def bar(ness: str = 'ness', *args, **kwargs):
   return foo(biz=ness, *args, **kwargs)

请注意,这个目标(指定关键字并传递 args + kwargs)掩盖了我对 args 的一些其他误解:在 python 中,args 不能在具有默认值的 arg 之后传入(关键字 arg)已设置,如下正确指出..

然后mypy 0.812 版会抛出如下错误:

$: mypy --ignore-missing-imports --disallow-untyped-defs --disallow-incomplete-defs

foo/foo.py:6: error: "foo" gets multiple values for keyword argument "biz"

有没有办法解决这个问题,特别是在下面的返回行以外的任何地方进行重大更改?

   return foo(biz=ness, *args, **kwargs)

【问题讨论】:

    标签: python python-3.x types mypy


    【解决方案1】:

    尽管对foo 的调用有顺序,但args 中的值在考虑关键字参数biz 之前分配给位置参数。这意味着bar可以使用位置参数调用,可以在分配给ness之前给biz分配一个值。 p>

    也就是说,mypy 提前捕获了以下潜在的运行时错误:

    >>> bar("1", "2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in bar
    TypeError: foo() got multiple values for argument 'biz'
    

    字符串"1"被分配给ness,字符串"2"成为args的第一个元素。调用foo时,将args的元素赋值给位置参数bizbaz;在这种情况下,只有biz 得到一个值,即"2"。只有这样才会考虑关键字参数biz=ness,此时biz 已经被考虑在内。

    【讨论】:

    • @Chris:为什么你认为 mypy 不应该报告错误,而 runtime python 行为实际上也因同样的错误而失败?也就是说,如果我删除所有类型注释,问题仍然存在 - 调用 bar('1', '2') 将失败!
    • @JeanHominal 为什么在范围内?
    • @Chris:我的论点是上面的 python 代码不是完美的功能。只要bar 中的args 是一个空元组,它就可以工作。
    • 但是阅读您的回答,我不明白 - 您允许/愿意更改代码的哪一部分,您不愿意更改(至少现在不是)?
    【解决方案2】:

    根据您的回答,我假设您希望 linter 传递 bar 的定义,并且您愿意为此目的对 bar 的实现/声明进行更改。

    对我来说,根本问题是 bar 的实现是错误的 - 如果 *args 不为空,那么调用 foo(biz=ness, *args, **kwargs) 将失败,并与 mypy 报告的错误完全相同。

    def foo(biz: str = 'biz',baz: str = 'baz', *args:Any, **kwargs:Any)-> str:
        return biz + baz
    
    def bar_from_question(ness: str = 'ness', *args, **kwargs):
        return foo(biz=ness, *args, **kwargs)
    
    >>> bar_from_question("1")
    1baz
    >>> bar_from_question("1", "2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/jean/Projects/python/test-mypy/test.py", line 7, in bar_from_question
         return foo(biz=ness, *args, **kwargs)
    TypeError: foo() got multiple values for argument 'biz'
    

    对我来说,你有三个选择:

    1. 通过不再传递 args 来修复对 foo 的调用。这样的更改不会导致功能损失,因为在bar 中有一个非空的args 无论如何都会引发TypeError。在这种情况下,您还可以从 bar 的签名中删除 args(同样,不会丢失功能 - 您只需将调用 foo 触发的 TypeError 替换为 @ 调用中的 TypeError 987654338@)

      def bar_do_not_pass_args_to_foo(ness: str = 'ness', *args, **kwargs):
          return foo(biz=ness, **kwargs)
      
      def bar_remove_args_from_signature(ness: str = 'ness', **kwargs):
          return foo(biz=ness, **kwargs)
      
    2. 通过将biz 作为位置参数而不是命名参数传递来修复对foo 的调用:

      def bar_pass_biz_as_positional(ness: str = 'ness', *args, **kwargs):
          return foo(ness, *args, **kwargs)
      
    3. 向 mypy 隐藏问题(我从您的回答中了解到)

      def bar_hide_from_mypy(ness: str = 'ness', *args, **kwargs):
          kwargs['biz'] = ness
          return foo(*args, **kwargs)
      

      但是,如果目标只是防止 mypy 报告错误,则有以下语法:

      def bar_suppress_mypy(ness: str = 'ness', *args, **kwargs):
          return foo(biz=ness, *args, **kwargs)  # type: ignore
      

    现在,如果我在运行时解释器中比较每个提案的结果:

    >>> bar_do_not_pass_args_to_foo("1")
    '1baz'
    >>> bar_do_not_pass_args_to_foo("1", "2")
    '1baz'
    >>> bar_remove_args_from_signature("1")
    '1baz'
    >>> bar_remove_args_from_signature("1", "2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: bar_remove_args_from_signature() takes from 0 to 1 positional arguments but 2 were given
    >>> bar_pass_biz_as_positional("1")
    '1baz'
    >>> bar_pass_biz_as_positional("1", "2")
    '12'
    >>> bar_hide_from_mypy("1")
    '1baz'
    >>> bar_hide_from_mypy("1", "2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/jean/Projects/python/test-mypy/test.py", line 20, in bar_hide_from_mypy
        return foo(*args, **kwargs)
    TypeError: foo() got multiple values for argument 'biz'
    >>> bar_suppress_mypy("1")
    '1baz'
    >>> bar_suppress_mypy("1", "2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/jean/Projects/python/test-mypy/test.py", line 23, in bar_suppress_mypy
        return foo(biz=ness, *args, **kwargs)  # type: ignore
    TypeError: foo() got multiple values for argument 'biz'
    

    你可以看到:

    • 对于bar_from_question 返回结果的每种情况,所有其他bar_... 实现都返回相同的结果;
    • 除了bar_from_question 之外的所有实现都对 mypy 保持沉默,我认为这就是您想要的;

    在您的情况下,我会寻求解决根本问题的方法(即应用要点 1 或 2),而不是仅仅试图强制 mypy(要点 3)。

    【讨论】:

      【解决方案3】:

      这就是我一直在寻找的答案,最后:

      def bar(baz='ness', *args, **kwargs):
         return foo(*args, **dict(kwargs, baz=baz)
      
      >>> bar()
      bizness
      

      【讨论】:

      • 值得注意的是为什么这是有效的。与问题中的返回 (return foo(biz=ness, *args, **kwargs)) 不同,如果 biz 已存在于 **kwargs 中,则在调用 dict(kwargs, biz=ness) 时,它会被 ness 的值覆盖。因此biz不再需要单独传入,也不能传入两次。
      猜你喜欢
      • 2019-08-23
      • 2021-06-17
      • 1970-01-01
      • 2021-07-08
      • 1970-01-01
      • 2019-12-20
      • 2017-08-01
      • 1970-01-01
      相关资源
      最近更新 更多