4

我在 Python 中发现了一种让我感到困惑和恼火的行为,我想知道我做错了什么。

我有一个函数,它应该接受任意数量的参数和关键字,但另外应该有一些默认值的关键字来构成它的实际接口:

def foo(my_keyword0 = None, my_keyword1 = 'default', *args, **kws):
    for argument in args:
        print argument

问题是,如果我尝试调用,foo(1, 2, 3)我只会得到 3 的打印输出,而值 1 和 2 将覆盖我的关键字参数。

另一方面,如果我尝试在 the 之后*args或之后移动关键字**kws,则会导致语法错误。我发现该问题的唯一解决方案是从中提取关键字参数**kws并为其设置默认值:

def foo(*args, **kws):
    my_keyword0 = None if 'my_keyword0' not in kws else kws.pop('my_keyword0')
    my_keyword0 = 'default' if 'my_keyword1' not in kws else kws.pop('my_keyword1')
    for argument in args:
        print argument

这太可怕了,因为它迫使我添加毫无意义的代码,并且因为函数签名变得更难理解——你必须实际阅读函数代码,而不仅仅是查看它的接口。

我错过了什么?难道没有更好的方法来做到这一点吗?

4

3 回答 3

6

具有默认值的函数参数仍然是位置参数,因此您看到的结果是正确的。当您为参数指定默认值时,您并没有创建关键字参数。当函数调用未提供参数时,仅使用默认值。

>>> def some_function(argument="Default"):
...     # argument can be set either using positional parameters or keywords
...     print argument
... 
>>> some_function()    # argument not provided -> uses default value
Default
>>> some_function(5)    # argument provided, uses it
5
>>> some_function(argument=5)    # equivalent to the above one
5
>>> def some_function(argument="Default", *args):
...     print (argument, args)
... 
>>> some_function()   #argument not provided, so uses the default and *args is empty
('Default', ())
>>> some_function(5)   # argument provided, and thus uses it. *args are empty
(5, ())
>>> some_function(5, 1, 2, 3)   # argument provided, and thus uses it. *args not empty
(5, (1, 2, 3))
>>> some_function(1, 2, 3, argument=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: some_function() got multiple values for keyword argument 'argument'

请注意最后一条错误消息:如您所见,1被分配到argument,然后 python 再次发现了keyword引用argument,因此引发了错误。仅在*args分配所有可能的位置参数后才分配。

在 python2 中,除了 using 之外,没有其他方法可以定义仅限关键字的值**kwargs。作为一种解决方法,您可以执行以下操作:

def my_function(a,b,c,d,*args, **kwargs):
    default_dict = {
        'my_keyword1': TheDefaultValue,
        'my_keyword2': TheDefaultValue2,
    }
    default_dict.update(kwargs)    #overwrite defaults if user provided them
    if not (set(default_dict) <= set('all', 'the', 'possible', 'keywords')):
        # if you want to do error checking on kwargs, even though at that
        # point why use kwargs at all?
        raise TypeError('Invalid keywords')
    keyword1 = default_dict['keyword1']
    # etc.

在 python3 中,您可以定义仅限关键字的参数:

def my_function(a,b,c,*args, keyword, only=True): pass
    # ... 

请注意,仅关键字并不意味着它应该具有默认值。

于 2013-02-02T10:08:02.143 回答
3

在 Python 中,非关键字参数不能出现在关键字参数之后,因此您尝试使用的函数签名是不可能的:

foo(my_keyword0="baa", my_keyword1="boo", 1, 2, 3, bar="baz", spam="eggs")  # won't work

如果你考虑一下,这个限制有非常正当的理由(提示:关键字参数可以以任何顺序呈现,位置参数是......好吧,位置)

文档中:

在函数调用中,关键字参数必须跟在位置参数之后。所有传递的关键字参数必须与函数接受的参数之一匹配(例如,actor 不是 parrot 函数的有效参数),它们的顺序并不重要。这还包括非可选参数

*args**kwargs包含与现有形式参数不匹配的参数,因此一旦您spam=None在参数列表中定义,它将不再被传递给**kwargs

当表单名称的最终形式参数存在时,它会收到一个字典(请参阅映射类型 - dict),其中包含所有关键字参数**除了与形式参数相对应的那些。这可以与 *name 形式的形式参数(在下一小节中描述)相结合,该形式参数接收包含超出形式参数列表的位置参数的元组。(*名称必须出现在**名称之前。)

与您的(不可能的)语法最接近的近似值是

def foo(my_keyword0=None, my_keyword1='default', numbers=None, **kwargs)

您将用作

foo(my_keyword0="bar", my_keyword1="baz", numbers=(1,2,3), spam='eggs')
foo("bar", "baz", numbers=(1,2,3))
foo("bar", "baz", (1,2,3))
foo("bar", "baz", (1,2,3), spam="eggs")
foo(numbers=(1,2,3))
foo(spam="eggs")
etc.

可以说,这可能比您最初的想法更具可读性且不那么令人惊讶。

于 2013-02-02T10:28:21.947 回答
0
def foo(my_keyword0 = None, my_keyword1 = 'default', *args, **kws):
    all_args = [my_keyword0, my_keyword1] + args + kws.values()
    for argument in all_args:
        print argument
于 2013-02-02T09:44:08.247 回答