【问题标题】:Python - When can you pass a positional argument by name, and when can't you?Python - 你什么时候可以按名称传递位置参数,什么时候不能?
【发布时间】:2015-03-04 16:44:58
【问题描述】:

Python 2.7.5 collections.defaultdict 似乎仅在您将 default_factory 作为位置参数传递时才有效——当您将其作为命名参数传递时它会中断。

如果您运行以下代码,您会看到 default_dict_success() 运行良好,但 default_dict_failure() 会抛出 KeyError

from collections import defaultdict

test_data = [
    ('clay', 'happy'),
    ('jason', 'happy'),
    ('aj', 'sad'),
    ('eric', 'happy'),
    ('sophie', 'sad')
]

def default_dict_success():
    results = defaultdict(list)
    for person, mood in test_data:
        results[mood].append(person)
    print results


def default_dict_failure():
    results = defaultdict(default_factory=list)
    for person, mood in test_data:
        results[mood].append(person)
    print results


default_dict_success()
default_dict_failure()

输出是

# First function succeeds
defaultdict(<type 'list'>, {'sad': ['aj', 'sophie'], 'happy': ['clay', 'jason', 'eric']})

# Second function fails
Traceback (most recent call last):
  File "test_default_dict.py", line 26, in <module>
    default_dict_failure()
  File "test_default_dict.py", line 21, in default_dict_failure
    results[mood].append(person)
KeyError: 'happy'

有人知道怎么回事吗?

编辑:最初我以为我正在查看一些 Python 源代码,这些源代码表明我正在尝试做的事情是可能的,但评论者指出我错了,因为这个对象是用 C 实现,因此没有 Python 源代码。所以它并不像我想象的那样相当神秘。

话虽如此,这是我第一次在 Python 中遇到不能通过名称传递的位置参数。这种事情在其他地方会发生吗?有没有办法在纯 Python(而不是 C 扩展)中实现强制这种行为的函数?

【问题讨论】:

  • 你在哪里找到这个__init__方法? defaultdictimplemented in C
  • 从 Python 2 和 3 的文档中也可以清楚地看出它是位置参数:class collections.defaultdict([default_factory[, ...]])
  • @AshwiniChaudhary -- 这是真的。我认为这个问题可以更笼统一些——在什么情况下你可以通过关键字调用位置参数(因为有时你可以:-)。
  • @AshwiniChaudhary 我在我的 IDE (PyCharm) 中使用了“Go To Declaration”...看起来该文件可能是通过 PyCharm 对可执行 C 代码的内省生成的——这是在顶部文件: # from path/to/lib/python2.7/lib-dynload/_collections.so # by generator 1.130
  • @mgilson 我猜不可能用 C 级别编写的函数,通常是那些接受 PyObject *args 和/或 PyObject *kwds 的函数。

标签: python parameters arguments defaultdict


【解决方案1】:

在 Modules/_collectionsmodule.c 中,defdict_init() 接受一个 kwargs,但除了将其传递给 PyDict_Type.tp_init() 之外,没有做任何其他事情。

IOW,defaultdict 被记录为接受命名参数,但实现不接受,因此命名参数被传递而不是被使用。

这可能可以使用 PyArg_ParseTupleAndKeywords 来解决,而不是将其参数视为简单的元组。同一个模块中的双端队列类型是如何完成它的一个示例,因为它接受几个命名参数。

我猜如果你在 Python 问题跟踪器中提交错误,要么更改文档以匹配实现,要么更改实现以匹配文档。

支持细节 - 当你创建一个带有 default_factory 命名参数的 defaultdict 时,你会得到一个预先创建的以 default_factory 作为键的字典:

>>> import collections
>>> dd = collections.defaultdict(default_factory=int)
>>> dd
defaultdict(None, {'default_factory': <class 'int'>})
>>> dd2 = collections.defaultdict(int)
>>> dd2
defaultdict(<class 'int'>, {})
>>>

HTH

【讨论】:

    【解决方案2】:

    我认为the docs 尝试说这就是将会发生的事情,尽管他们并不是特别清楚:

    第一个参数提供default_factory属性的初始值;它默认为无。 所有剩余参数被视为与传递给 dict 构造函数一样,包括关键字参数

    强调我的。 “第一个参数”不会是关键字参数(它们没有顺序)。也就是说,提交文档错误并不是一个坏主意。

    话虽如此,这是我第一次在 Python 中遇到不能通过名称传递的位置参数。这种事情在其他地方会发生吗?有没有办法在纯 Python(而不是 C 扩展)中实现强制这种行为的函数?

    这实际上很常见,有一个关于它的whole PEP。以range 为例。

    关于自己做这件事,

    现代 Python 中实现的函数可以通过可变参数 *args 接受任意数量的仅位置参数。但是,没有 Python 语法来指定接受特定数量的仅位置参数。换句话说,有许多内置函数的签名根本无法用 Python 语法表达。

    可以做类似的事情

    def foo(*args):
        a, b, c = args
    

    PEP 中提到了这一点:

    显然,可以通过接受(*args, **kwargs) 并手动解析参数,在纯 Python 代码中模拟任何这些。但这会导致 Python 函数的签名与其实际接受的内容之间存在脱节,更不用说实现所述参数解析的工作了。

    【讨论】:

      猜你喜欢
      • 2011-02-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-31
      • 1970-01-01
      • 2013-10-24
      • 1970-01-01
      • 2011-09-20
      相关资源
      最近更新 更多