Python является утка набран, и, как правило это позволяет избежать литья 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
Может кто-нибудь просветить меня, почему это так?
Существует явная проверка типа list
(или его дочерних элементов) в исходном коде на языке python (так что даже 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% случаев.
Клавиши Dict на самом деле реализуют заданный интерфейс, а не список, поэтому вы можете выполнять заданные операции с ключами dict непосредственно с другими наборами:
d.keys() & {'foo', 'bar'} # returns {'foo'}
Но он не реализует __getitem__
, __setitem__
, __delitem__
и insert
, которые необходимы для "quack" как список, поэтому он не может выполнять какие-либо операции с списком, не будучи явно преобразованным в список:
ls + list(d.keys()) # returns ['hello', 'foo']
__getitem__
list + dict и dict реализует __getitem__
: >>> d.__getitem__ <built-in method __getitem__ of dict object at 0x0000000002FD9048>
. Тот факт, что __getitem__
не определен, не является причиной.
__reversed__
). Таблица на docs.python.org/3/library/… является полезной ссылкой.
Если вы перейдете к определению d.keys()
вы увидите следующее.
def keys(self): # real signature unknown; restored from __doc__
""" D.keys() -> a set-like object providing a view on D keys """
pass
Или используйте это утверждение:
print(d.keys.__doc__)
В нем четко говорится, что выход, set-like
объект.
Теперь вы пытаетесь добавить набор в список.
Вам нужно преобразовать набор в список, а затем добавить его.
x = ls + list(d.keys())
print(x)
# ['hello', 'foo']
ls + list(d.keys())
?