【问题标题】:Why do dict keys support list subtraction but not tuple subtraction?为什么dict键支持列表减法但不支持元组减法?
【发布时间】:2016-03-03 22:23:19
【问题描述】:

大概 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'}

【问题讨论】:

  • 元组是不可变的,因此可能是字典本身的键,在这种情况下,这将是模棱两可的——如果不是在语法上,那么至少在程序员的脑海中是这样。
  • @L3viathan 我不相信,因为dict.fromkeys('0123').keys() - '02' 仍然有效
  • 只有tuples 和ints 是特殊的吗? dict.fromkeys('0123').keys() - ('0', '2'){'1', '3'}
  • 有趣的是,字典视图映射应该是类似“set”的,但 set 不支持这一点:set((0,1,2,3)) - [0,2] 导致 'set' 和 'str' 的操作数不受支持.

标签: python python-3.x


【解决方案1】:

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

看起来他们误用了_PyObject_CallMethodIdPyObject_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*,它所做的只是增加引用计数)。

虽然我还没有找到它可能在哪里这样做,但我怀疑在内部的某个地方它正在识别不会产生 tupleCallMethod 调用并将它们包装成一个元素 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

这就是为什么str 中的tuple 起作用(不正确),- ('abc', '123') 执行的调用相当于:

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

由于strs 是其字符的可迭代对象,它只是像您预期的那样愉快地删除了'a''b''c' 等的条目,而不是'abc''123'

基本上,这是一个错误; it's filed against the CPython folks 并在 3.6.0(以及更高版本的 2.7、3.4 和 3.5)中修复。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    • 2012-12-19
    • 2014-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多