Python - 什么时候可以按名称传递位置参数,什么时候不能?

Python - When can you pass a positional argument by name, and when can't you?

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 扩展)中实现强制执行此类行为的函数?

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

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

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

我猜如果您在 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

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

The first argument provides the initial value for the default_factory attribute; it defaults to None. All remaining arguments are treated the same as if they were passed to the dict constructor, including keyword arguments.

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

That having been said, this is the first time I've come across positional argument in Python that couldn't also be passed by name. Does this type of thing happen anywhere else? Is there a way to implement a function in pure Python (as opposed to a C extension) that enforces this type of behavior?

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

关于自己做这件事,

Functions implemented in modern Python can accept an arbitrary number of positional-only arguments, via the variadic *args parameter. However, there is no Python syntax to specify accepting a specific number of positional-only parameters. Put another way, there are many builtin functions whose signatures are simply not expressable with Python syntax.

可以做类似的事情

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

PEP 中提到了这一点:

Obviously one can simulate any of these in pure Python code by accepting (*args, **kwargs) and parsing the arguments by hand. But this results in a disconnect between the Python function's signature and what it actually accepts, not to mention the work of implementing said argument parsing.