Почему добавление списка Python должно быть однородным?
Может ли кто-нибудь, знакомый с внутренними компонентами Python (CPython или другими реализациями), объяснить, почему добавление списка должно быть однородным:
In [1]: x = [1]
In [2]: x+"foo"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
C:\Users\Marcin\<ipython-input-2-94cd84126ddc> in <module>()
----> 1 x+"foo"
TypeError: can only concatenate list (not "str") to list
In [3]: x+="foo"
In [4]: x
Out[4]: [1, 'f', 'o', 'o']
Почему не следует, чтобы x+"foo"
выше возвращал то же значение, что и конечное значение x
в приведенном выше расшифровке?
Этот вопрос следует из вопроса NPE здесь: Является ли поведение списка Python + = iterable документированным где угодно?
Обновление: я знаю, что не требуется, чтобы гетерогенный +=
работал (но это так), и также не требуется, чтобы гетерогенная +
была ошибкой. Этот вопрос связан с тем, почему этот последний выбор был сделан.
Слишком много говорить о том, что результаты добавления последовательности в список неопределенны. Если бы это было достаточным возражением, было бы целесообразно предотвратить гетерогенную +=
. Update2: В частности, python всегда делегирует вызовы оператора в lefthand operand, поэтому не возникает проблема "что делать правильно": левый объект всегда управляет (если он не делегирует справа).
Update3: для тех, кто утверждает, что это дизайнерское решение, объясните (а), почему он не документирован; или (b) там, где оно документировано.
Update4: "что должно [1] + (2, )
вернуться?" Он должен вернуть значение результата, равное значению переменной x
, первоначально удерживая [1]
сразу после x+=(2, )
. Этот результат хорошо определен.
Ответы
Ответ 1
Эти отчеты об ошибках предполагают, что эта ошибка дизайна была ошибкой.
Issue12318:
Да, это ожидаемое поведение и да, это противоречиво.
Это было так долго, и Гвидо сказал, что он не сделает этого снова (это в его списке сожалений). Однако мы не будем нарушать код, меняя его (list.__iadd__
работает как list.extend
).
Issue575536:
Цель заключалась в том, что list.__iadd__
точно соответствует list.extend()
. Нет необходимости в гиперсегментации list.__add__()
тоже: это особенность, что люди, которые не хочу удивиться, похожие на Мартин примеры могут избежать их с помощью простого +
для списков.
(Конечно, есть те из нас, кто считает это поведение совершенно неожиданным, включая разработчика, который открыл этот отчет об ошибках).
(Спасибо @Mouad за их поиск).
Ответ 2
Из Zen Python:
Перед лицом двусмысленности откажитесь от соблазна угадать.
Посмотрим, что здесь происходит:
x + y
Это дает нам значение, но какого типа? Когда мы добавляем вещи в реальную жизнь, мы ожидаем, что тип будет таким же, как и типы ввода, но что, если они являются несопоставимыми? Ну, в реальном мире мы отказываемся добавлять 1
и "a"
, это не имеет смысла.
Что делать, если у нас есть похожие типы? В реальном мире мы смотрим на контекст. Компьютер не может этого сделать, поэтому он должен угадать. Python выбирает левый операнд и позволяет ему решать. Ваша проблема возникает из-за отсутствия контекста.
Скажите программисту, что он хочет сделать ["a"] + "bc"
- это может означать, что они хотят "abc"
или ["a", "b", "c"]
. В настоящее время решение состоит в том, чтобы либо вызвать "".join()
в первом операнде, либо list()
на втором, что позволяет программисту делать то, что они хотят, и ясен и ясен.
Ваше предположение заключается в том, что Python угадывает (имея встроенное правило для выбора заданного операнда), поэтому программист может сделать то же самое, делая дополнение - почему это лучше? Это просто означает, что ошибочно ошибочно вводить неправильный тип, и мы должны помнить произвольное правило (левый операнд выбирает тип). Вместо этого мы получаем ошибку, чтобы мы могли предоставить Python необходимую информацию для правильного вызова.
Итак, почему +=
отличается? Ну, это потому, что мы даем Python этот контекст. С помощью операции на месте мы говорим Python об изменении значения, поэтому мы знаем, что мы имеем дело с чем-то вроде того значения, которое мы модифицируем. В этом контексте Python должен сделать правильный вызов, поэтому нам не нужно угадывать.
Когда я говорю об угадывании, я говорю о том, что Python угадывает намерение программиста. Это то, что Python делает много - см. Раздел в 3.x. /
выполняет разделение с плавающей точкой, исправляя ошибку его целочисленного деления в 2.x.
Это потому, что мы неявно запрашиваем float-деление, когда пытаемся разделить. Python учитывает это, и операции выполняются в соответствии с этим. Точно так же это касается угадывания намерений здесь. Когда мы добавляем с +
, наше намерение неясно. Когда мы используем +=
, это очень ясно.
Ответ 3
Я считаю, что дизайнеры Python сделали дополнение таким образом, чтобы оператор "+" оставался последовательно коммутативным в отношении типа результата: type(a + b) == type(b + a)
Все ожидают, что 1 + 2 имеет тот же результат, что и 2 + 1. Вы ожидали бы, что [1] + 'foo' будет таким же, как "foo" + [1]? Если да, каков должен быть результат?
У вас есть 3 варианта: вы выбираете левый операнд как тип результата, правый операнд в качестве типа результата или вызываете ошибку.
+ = не является коммутативным, поскольку содержит задание. В этом случае вы либо выбираете левый операнд в качестве результата, либо бросаете. Удивительно, что a += b
не совпадает с a = a + b
. a += b
не переводится на английский язык на "Добавить a в b и присвоить результат". Он переводится как "Добавить a в b". Вот почему это не работает над непреложными, такими как строка или кортеж.
Спасибо за комментарии. Отредактировано сообщение.
Ответ 4
Я предполагаю, что Python строго типизирован, и здесь нет четкого указания на правильную вещь. Вы просите Python добавить строку непосредственно или передать строку в список (что вы указали, что вы хотели бы сделать)?
Помните, что явное лучше, чем неявное. В наиболее распространенном случае ни одна из этих догадок не является правильной, и вы случайно пытаетесь сделать что-то, чего не хотели. Поднять TypeError и позволить вам разобраться в нем - это самая безопасная, самая питоническая вещь, которую можно сделать здесь.