Добавление словаря в список с использованием нотации __iadd__
, по-видимому, добавляет ключи словаря в качестве элементов в список. Почему? Например
a = []
b = {'hello':'world'}
a += b
>> a now stores ['hello']
Документация для плюс-равно для коллекций не подразумевает, что это должно происходить:
Например, для выполнения оператора x += y, где x — экземпляр класса, имеющего метод
__iadd__
(), вызываетсяx.__iadd__(y)
. Если x является экземпляром класса, который не определяет метод__iadd__()
, рассматриваютсяx.__add__(y)
иy.__radd__(x)
, как и при оценкеx + y
Но по логике оба
a + b # Исключение TypeError
и
b + a # Исключение TypeError
Не определяются. Кроме того, b+=a
также вызывает TypeError. Я не вижу какой-то специальной реализации в исходниках, которая бы что-то объясняла, но я не уверен на 100%, где искать.
Самый близкий вопрос на SO, который я нашел, это этот , спрашивая о += в словарях, но это просто вопрос о структуре данных с самим собой. У этого было многообещающее название о самостоятельном добавлении списков, но в нем утверждается, что «__add__
» применяется внутри, что не должно определяться между списками и словарями.
Насколько я понимаю, __iadd__
вызывает расширение, которое определено здесь, а затем пытается перебрать словарь, который, в свою очередь, дает свои ключи. Но это кажется... странным? И я не вижу никакой интуиции в том, что исходит из документов.
когда вы используете for key in b
, тогда словарь также дает вам keys
, а не values
. И list(b)
также дает вам keys
, а не values
. Так что, возможно, это работает как a += list(b)
@assembly Из документов по коллекциям я ожидал, что += завершится ошибкой всякий раз, когда + не определено (я думаю об этом как о сокращении для a = a + b, поэтому успех там, где это не удается, меня удивляет). Это просто причуда их оптимизации или что-то в этом роде, или есть кто-то более очевидный, чтобы посмотреть, где они объясняют, что делают операторы? Наверное, я просто неправильно понял, что такое контракт для оператора.
@furas, это может быть, хотя я предполагаю, что он явно не вызывает 'a += list(b)' - если это так, и вы знаете пример исходного кода или какой-то PEP, поддерживающий его, я' я определенно приму этот ответ!
это не явный вызов list(b)
, но кто-то решил, что b
будет возвращать только ключи, когда вы используете его с for
-loop, list()
и то же самое в +=
. Кто-то решил, что ключи могут быть полезнее values
@furas Почему тогда это не делает то же самое для a + b
? Поведение итератора задокументировано, но, похоже, это не так.
+
и +=
могут не использовать один и тот же код — и, возможно, у них нет времени написать его одинаково.
Мое предположение состоит в том, что iadd вызывает расширение, определенное здесь, а затем пытается перебрать словарь, который, в свою очередь, дает свои ключи. Но это кажется... странным? И я не вижу никакой интуиции в том, что исходит из документов.
Это правильный ответ, почему это происходит. Я нашел соответствующие документы, в которых говорится следующее:
В документах видно, что на самом деле __iadd__
эквивалентно .extend()
, а здесь написано:
list.extend(iterable): Расширьте список, добавив все элементы из итерируемого.
В части про диктовки сказано:
Выполнение list(d) в словаре возвращает список всех ключей, используемых в словаре.
Подводя итог, a_list += a_dict
эквивалентен a_list.extend(iter(a_dict))
, что эквивалентно a_list.extend(a_dict.keys())
, что расширит список списком ключей в словаре.
Возможно, мы можем обсудить, почему так обстоят дела, но я не думаю, что мы найдем четкий ответ. Я думаю, что +=
— очень полезное сокращение для .extend
, а также то, что словарь должен быть повторяемым (лично я бы предпочел, чтобы он возвращал .items()
, ну да ладно)
Обновлено: похоже, вас интересует фактическая реализация CPython, поэтому вот несколько указателей кода:
Итератор dict возвращает ключи:
static PyObject *
dict_iter(PyDictObject *dict)
{
return dictiter_new(dict, &PyDictIterKey_Type);
}
list.extend(iterable) вызывает iter() для своего аргумента:
static PyObject *
list_extend(PyListObject *self, PyObject *iterable)
{
...
it = PyObject_GetIter(iterable);
...
}
+= эквивалентно list.extend():
static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
...
result = list_extend(self, other);
...
}
а затем этот метод, по-видимому, упоминается выше внутри структуры PySequenceMethods
, которая, по-видимому, является абстракцией последовательностей, определяющих общие действия, такие как объединение на месте и обычное объединение (которое определяется как list_concat
в том же файле, и вы можете видеть не такой же).
А, вы нашли эти дополнительные разделы в списках. Хорошо, я покупаю это, спасибо! (Я также предпочел бы, чтобы он возвращал .items... но, вероятно, не стоит ломать весь существующий код...)
@en_Knight Я добавил несколько указателей кода, так как вы спросили о самой реализации, надеюсь, это поможет :)
Ваше "лучшее предположение" является правильным. Почему это странно?