为什么python dict dict键/值不像鸭子一样嘎嘎作响?

时间:2018-10-16 13:03:27

标签: python language-design

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

有人能启发我为什么吗?

3 个答案:

答案 0 :(得分:7)

Dict键实际上是实现集合的界面而不是列表的接口,因此您可以直接与其他集合一起使用dict键执行集合操作:

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

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

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

答案 1 :(得分:1)

在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%的时间都使用的简单,快速的实现感到满意。 / p>

答案 2 :(得分:0)

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