python-3.x 如何用mypy传递关键字args?

aurhwmvo  于 2023-04-22  发布在  Python
关注(0)|答案(3)|浏览(120)

假设:

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)后传入,如下所述。
然后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)
unftdfkk

unftdfkk1#

尽管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已经被考虑。

pgccezyw

pgccezyw2#

根据你的回答,我假设你希望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)

1.通过将biz作为位置参数而不是命名参数传递来修复对foo的调用:

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

1.对我隐瞒这个问题(我从你的回答中理解)

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),而不仅仅是试图强迫我(要点3)。

uqcuzwp8

uqcuzwp83#

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

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

>>> bar()
bizness

相关问题