为什么 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% 的时间都在使用。
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% 的时间都在使用。