这看起来是一个错误。 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*
,它所做的只是增加参考计数)。
虽然我还没有追查它可能会做这个,我怀疑的地方在它的识别不产生tuple
CallMethod
电话和包裹它们的内部,使一个元素tuple
所以调用的函数实际上可以以预期格式接收参数。当减去tuple
时,它已经是tuple
,并且此修复代码从未激活;当通过list
时,它成为包含list
的一个元素tuple
。
difference_update
需要可变参数(好像它被宣布为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
s为他们的角色的iterables,它只是轻率地删除条目'a'
,'b'
,'c'
等代替'abc'
和'123'
像你期望的那样。
基本上,这是一个错误,并且(当我有机会的时候),我会将它提交给CPython人员。
正确的行为或许应该已经调用(假设这Id
变种存在此API):
_PyObject_CallMethodObjArgsId(result, &PyId_difference_update, other, NULL);
这不会有问题的包装在所有的,并会跑得更快引导;最小的变化是将格式字符串更改为"(O)"
以强制tuple
即使对于单个项目也是如此,但由于格式字符串没有收益,因此_PyObject_CallMethodObjArgsId
更好。
元组是不可变的,因此可能是字典本身的关键字,在这种情况下,这将是不明确的 - 如果没有语法上的话,至少在程序员的脑海中是这样。 – L3viathan
@ L3viathan我不相信,因为'dict.fromkeys('0123')。keys() - '02''仍然有效 – wim
是的,好点。 – L3viathan