Почему плюс-равно допустимо для списка и словаря?

Добавление словаря в список с использованием нотации __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__ вызывает расширение, которое определено здесь, а затем пытается перебрать словарь, который, в свою очередь, дает свои ключи. Но это кажется... странным? И я не вижу никакой интуиции в том, что исходит из документов.

Ваше "лучшее предположение" является правильным. Почему это странно?

assembly_wizard 10.12.2020 00:55

когда вы используете for key in b, тогда словарь также дает вам keys, а не values. И list(b) также дает вам keys, а не values. Так что, возможно, это работает как a += list(b)

furas 10.12.2020 00:58

@assembly Из документов по коллекциям я ожидал, что += завершится ошибкой всякий раз, когда + не определено (я думаю об этом как о сокращении для a = a + b, поэтому успех там, где это не удается, меня удивляет). Это просто причуда их оптимизации или что-то в этом роде, или есть кто-то более очевидный, чтобы посмотреть, где они объясняют, что делают операторы? Наверное, я просто неправильно понял, что такое контракт для оператора.

en_Knight 10.12.2020 00:59

@furas, это может быть, хотя я предполагаю, что он явно не вызывает 'a += list(b)' - если это так, и вы знаете пример исходного кода или какой-то PEP, поддерживающий его, я' я определенно приму этот ответ!

en_Knight 10.12.2020 01:01

это не явный вызов list(b), но кто-то решил, что b будет возвращать только ключи, когда вы используете его с for-loop, list() и то же самое в +=. Кто-то решил, что ключи могут быть полезнее values

furas 10.12.2020 01:03

@furas Почему тогда это не делает то же самое для a + b? Поведение итератора задокументировано, но, похоже, это не так.

Selcuk 10.12.2020 01:04
+ и += могут не использовать один и тот же код — и, возможно, у них нет времени написать его одинаково.
furas 10.12.2020 01:06
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
15
7
1 462
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Мое предположение состоит в том, что 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 10.12.2020 01:11

@en_Knight Я добавил несколько указателей кода, так как вы спросили о самой реализации, надеюсь, это поможет :)

assembly_wizard 10.12.2020 01:20

Другие вопросы по теме