如何使用mypy传递关键字参数?



假设:

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 中,在设置了具有默认值(关键字 arg)的 arg 后,无法传入 args,如下所述。

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

尽管在调用foo时有排序,但args中的值在考虑关键字参数biz之前被分配给位置参数。这意味着可以使用位置参数调用的bar可以在ness赋值之前将值分配给biz

也就是说,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已经被考虑在内。

从你的回答中,我会假设你希望 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替换为调用bar中的TypeError)

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

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

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

最新更新