为什么字典键支持列表减法而不支持元组减法?


问题内容

推测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'}

问题答案:

这似乎是一个错误。实现是将转换dict_keysset,然后对其进行调用.difference_update(arg)

通过传递格式字符串just
,看来它们被滥用_PyObject_CallMethodId(是的优化变体PyObject_CallMethod"O"事实是这样,PyObject_CallMethod并且记录在案的朋友要求使用Py_BuildValue“应该产生一个tuple”的格式字符串。使用一个以上的格式代码,它会tuple自动将值包装,而使用一个格式的代码,它不会tuple,它只是创建值(在这种情况下,因为它已经PyObject*,它所做的就是增加引用计数)


虽然我还没有找到执行此操作的位置,但我怀疑内部结构中的某个位置正在识别CallMethod不会产生a的调用tuple并将它们包装成一个元素,tuple因此被调用函数实际上可以按预期格式接收参数。减去时tuple,它已经是tuple,并且此修复代码永远不会激活;传递a时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

这就是a tupleof strwork(错误地)- ('abc', '123')执行等同于以下操作的原因的原因:

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

而且由于strs为他们的角色的iterables,它只是轻率地删除条目'a''b''c',等来代替'abc',并'123'令您满意。

基本上,这是一个错误;它是针对CPython的人员提出的,并已在3.6.0(以及2.7、3.4和3.5的更高版本)中修复。

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

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

完全没有打包问题,并且启动速度更快;最小的更改是将格式字符串更改为即使是单个项目也"(O)"可以强制tuple创建,但是由于格式字符串没有任何收益,_PyObject_CallMethodObjArgsId因此更好。