【问题标题】:Why don't python dict keys/values quack like a duck?为什么 python dict 键/值不像鸭子一样嘎嘎叫?
【发布时间】:2023-03-24 22:30:01
【问题描述】:

Python 是duck typed,这通常可以避免在处理原始对象时强制转换 faff。

典型的例子(以及名称背后的原因)是鸭子测试:如果它看起来像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那么它很可能是鸭子。

然而,一个值得注意的例外是 dict 键/值,它看起来像鸭子,像鸭子一样游泳,但值得注意的是 不像鸭子一样嘎嘎作响

>>> ls = ['hello']
>>> d = {'foo': 'bar'}
>>> for key in d.keys():
..      print(key)
..
'foo'
>>> ls + d.keys()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "dict_keys") to list

谁能告诉我这是为什么?

【问题讨论】:

  • ls + list(d.keys())?
  • I在较旧的 Python 版本中,dict.keys() 是一个列表,但它发生了变化,现在它是一个 dict 视图对象:来自帮助:D.keys() -&gt; a set-like object providing a view on D's keys
  • Python 很清楚它不是一个列表:&gt;&gt;&gt;d.keys() 返回dict_keys(['foo'])

标签: python language-design


【解决方案1】:

字典键实际上实现了集合的接口而不是列表的接口,所以你可以直接用字典键对其他集合进行集合操作:

d.keys() & {'foo', 'bar'} # returns {'foo'}

但它没有实现__getitem____setitem____delitem__insert方法,这些方法需要像列表一样“嘎嘎”,所以它不能执行任何列表操作而不是首先显式转换为列表:

ls + list(d.keys()) # returns ['hello', 'foo']

【讨论】:

  • 但是你不能连接列表+字典和字典实现__getitem__:&gt;&gt;&gt; d.__getitem__ &lt;built-in method __getitem__ of dict object at 0x0000000002FD9048&gt;__getitem__ 未定义的事实不是原因。
  • @Jean-FrançoisFabre:字典缺少将对象视为列表所需的其他序列方法(例如__reversed__)。 docs.python.org/3/library/… 的表格是一个有用的参考。
  • 如果你检查源代码,它真的比这更基础。它只是必须list
  • PyList_Check?是的,你是对的,我想它在现实中更基本:CPython 检查对象类型的tp_flags 上是否设置了Py_TPFLAGS_LIST_SUBCLASS 位。所以这个答案解决了为什么 dict_keys 对象从 ABC 角度确实像列表一样嘎嘎作响,但更直接的答案可能是 dict_keys 不从列表继承并且失败PyList_Check
  • 所以这个投票率很高的答案是错误。我想人们对 dunder 方法的使用印象深刻。
【解决方案2】:

如果您进入d.keys() 的定义,您可以看到以下内容。

def keys(self): # real signature unknown; restored from __doc__
    """ D.keys() -> a set-like object providing a view on D's keys """
    pass

或者使用这种说法:

print(d.keys.__doc__)

它清楚地提到输出是set-like 对象。

现在您正尝试将一个集合附加到一个列表中。

您需要将集合转换为列表,然后附加它。

x = ls + list(d.keys())
print(x)
# ['hello', 'foo']

【讨论】:

    【解决方案3】:

    在 python 源代码中对list 类型(或其子类型)进行了显式检查(因此即使tuple 也不符合条件):

    static PyObject *
    list_concat(PyListObject *a, PyObject *bb)
    {
        Py_ssize_t size;
        Py_ssize_t i;
        PyObject **src, **dest;
        PyListObject *np;
        if (!PyList_Check(bb)) {
            PyErr_Format(PyExc_TypeError,
                      "can only concatenate list (not \"%.200s\") to list",
                      bb->ob_type->tp_name);
            return NULL;
        }
    

    因此 python 可以非常快速地计算大小并重新分配结果,而无需尝试所有容器或在右手边迭代以找出,提供非常快速的列表添加。

    #define b ((PyListObject *)bb)
        size = Py_SIZE(a) + Py_SIZE(b);
        if (size < 0)
            return PyErr_NoMemory();
        np = (PyListObject *) PyList_New(size);
        if (np == NULL) {
            return NULL;
        }
    

    解决此问题的一种方法是使用就地扩展/添加:

    my_list += my_dict  # adding .keys() is useless
    

    因为在这种情况下,就地添加在右侧进行迭代:所以每个集合都符合条件。

    (或者当然是强制右手迭代:+ list(my_dict)

    所以它可以接受任何类型,但我怀疑 python 的制造者认为它不值得,并且对 99% 的时间使用的简单快速的实现感到满意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-06
      • 1970-01-01
      • 2015-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-17
      相关资源
      最近更新 更多