为什么dict键支持列表减法但不支持元组减法?

时间:2016-03-03 22:23:20

标签: python python-3.x

据推测,dict_keys应该表现为类似集合的对象,但它们缺少difference方法,并且减法行为似乎有所不同。

>>> d = {0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> d.keys() - [0, 2]
{1, 3}
>>> d.keys() - (0, 2)
TypeError: 'int' object is not iterable

为什么dict_keys类尝试在这里迭代一个整数?这不违反鸭子打字吗?

>>> dict.fromkeys(['0', '1', '01']).keys() - ('01',)
{'01'}
>>> dict.fromkeys(['0', '1', '01']).keys() - ['01',]
{'1', '0'}

1 个答案:

答案 0 :(得分:17)

这看起来像是一个错误。 The implementation is to convert the dict_keys to a set, then call .difference_update(arg) on it.

通过传递仅_PyObject_CallMethodId的格式字符串,他们似乎误用了PyObject_CallMethod"O"的优化变体)。 Thing is, PyObject_CallMethod and friends are documented to require a Py_BuildValue format string that "should produce a tuple"。如果使用多个格式代码,它会自动将值包装在tuple中,但只有一个格式代码,它不会tuple,它只会创建值(在这种情况下,因为它已经是PyObject*,它只是递增引用计数。)

虽然我没有追踪它可能会在哪里做这个,但我怀疑在内部的某个地方,它正在识别CallMethod不会产生tuple的调用并将它们包装成一个元素tuple所以被调用的函数实际上可以接收预期格式的参数。减去tuple时,它已经是tuple,此修正代码永远不会激活;传递list时,它会成为包含tuple的一个元素list

difference_update接受varargs(就好像它被声明为def difference_update(self, *args))。因此,当它收到未解包的tuple时,它认为它应该从tuple中的每个条目中减去元素,而不是将所述条目视为要减去自己的值。举例说明:

mydict.keys() - (1, 2)

该错误导致它(粗略地):

result = set(mydict)
# We've got a tuple to pass, so all's well...
result.difference_update(*(1, 2)) # Unpack behaves like difference_update(1, 2)
# OH NO!

虽然:

mydict.keys() - [1, 2]

确实

result = set(mydict)
# [1, 2] isn't a tuple, so wrap
result.difference_update(*([1, 2],)) # Behaves like difference_update([1, 2])
# All's well

这就是tuple str工作(错误)的原因,- ('abc', '123')正在执行相当于以下的调用:

result.difference_update(*('abc', '123'))
# or without unpacking:
result.difference_update('abc', '123')

由于str是其字符的可迭代,因此它只是轻松删除'a''b''c'等的条目,而不是'abc'和你期望的'123'一样。

基本上,这是一个错误(当我有机会的时候),我会把它提交给CPython人员。

可能应该调用正确的行为(假设此API存在此Id变体):

_PyObject_CallMethodObjArgsId(result, &PyId_difference_update, other, NULL);

根本没有包装问题,并且可以更快地启动;最小的变化是将格式字符串更改为"(O)"以强制tuple创建,即使对于单个项目也是如此,但由于格式字符串没有任何效果,_PyObject_CallMethodObjArgsId更好。