为什么字典键支持列表减法而不支持元组减法?
问题内容:
推测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_keys
为set
,然后对其进行调用.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 tuple
of str
work(错误地)- ('abc', '123')
执行等同于以下操作的原因的原因:
result.difference_update(*('abc', '123'))
# or without unpacking:
result.difference_update('abc', '123')
而且由于str
s为他们的角色的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
因此更好。