为什么 python dict keys/values 不像鸭子一样嘎嘎叫?

Why don't python dict keys/values quack like a duck?

Python 是 duck typed,通常这可以避免在处理原始对象时抛出 faff。

The canonical example (and the reason behind the name) is the duck test: If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

然而,一个值得注意的例外是 dict keys/values,它看起来像鸭子并且像鸭子一样游泳,但值得注意的是不要像鸭子一样嘎嘎叫

>>> 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

谁能告诉我这是为什么?

字典键实际上实现了集合的接口而不是列表的接口,因此您可以直接对其他集合使用字典键执行集合操作:

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

但它没有实现 __getitem____setitem____delitem__insert 方法,这些方法需要像列表一样 "quack" , 因此如果不首先显式转换为列表,它就无法执行任何列表操作:

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

如果您进入 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']

在 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;
    }

解决此问题的一种方法是就地使用 extension/addition:

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

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

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

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