Почему ключи/значения python dict не крякают, как утка?

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

Может кто-нибудь просветить меня, почему это так?


person user31415629    schedule 16.10.2018    source источник
comment
en.wikipedia.org/wiki/Duck_typing   -  person Isma    schedule 16.10.2018
comment
В более старых версиях Python dict.keys() был списком, но это изменилось, и теперь это объект представления dict: Из справки: D.keys() -> a set-like object providing a view on D's keys   -  person progmatico    schedule 16.10.2018
comment
Python ясно дает понять, что это не список: >>>d.keys() возвращает dict_keys(['foo'])   -  person tgikal    schedule 16.10.2018


Ответы (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% случаев.

person Jean-François Fabre    schedule 16.10.2018

Ключи Dict фактически реализуют интерфейс набора, а не списка, поэтому вы можете выполнять операции над наборами с ключами dict напрямую с другими наборами:

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

Но он не реализует методы __getitem__, __setitem__, __delitem__ и insert, которые необходимы для "шарлатанства" подобно списку, поэтому он не может выполнять ни одну из операций со списком без предварительного явного преобразования в список:

ls + list(d.keys()) # returns ['hello', 'foo']
person blhsing    schedule 16.10.2018
comment
но вы не можете объединить list + dict и dict реализует __getitem__: >>> d.__getitem__ <built-in method __getitem__ of dict object at 0x0000000002FD9048>. Тот факт, что __getitem__ не определен, не является причиной. - person Jean-François Fabre; 16.10.2018
comment
@Jean-FrançoisFabre: в dict отсутствуют другие методы Sequence, которые необходимы для обработки объекта как списка (например, __reversed__). Таблица на docs.python.org/3/library / — полезная ссылка. - person Alex Riley; 16.10.2018
comment
если вы проверите исходный код, это действительно более просто. это просто должно быть list - person Jean-François Fabre; 16.10.2018
comment
PyList_Check? Да, вы правы, я думаю, на самом деле это более просто: CPython проверяет, установлен ли бит Py_TPFLAGS_LIST_SUBCLASS в tp_flags типа объекта. Таким образом, этот ответ объясняет, почему объект dict_keys крякает как список с точки зрения ABC, но более прямой ответ, вероятно, заключается в том, что dict_keys не наследуется от списка и терпит неудачу PyList_Check. - person Alex Riley; 16.10.2018
comment
так что этот высоко оцененный ответ неправильный. Я предполагаю, что люди были впечатлены использованием методов Дандера. - person Jean-François Fabre; 16.10.2018

Если вы углубитесь в определение 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']
person argo    schedule 16.10.2018