Сумма Python, почему бы не строки?
Python имеет встроенную функцию sum
, которая фактически эквивалентна:
def sum2(iterable, start=0):
return start + reduce(operator.add, iterable)
для всех типов параметров, кроме строк. Он работает для чисел и списков, например:
sum([1,2,3], 0) = sum2([1,2,3],0) = 6 #Note: 0 is the default value for start, but I include it for clarity
sum({888:1}, 0) = sum2({888:1},0) = 888
Почему строки были специально исключены?
sum( ['foo','bar'], '') # TypeError: sum() can't sum strings [use ''.join(seq) instead]
sum2(['foo','bar'], '') = 'foobar'
Кажется, я помню обсуждения в списке Python по причине, поэтому объяснение или ссылка на поток, объясняющий это, были бы хороши.
Изменить. Я знаю, что стандартным способом является "".join
. Мой вопрос в том, почему вариант использования суммы для строк был запрещен, и никаких запретов не было, например, для списков.
Изменить 2. Хотя я считаю, что это не нужно, учитывая все хорошие ответы, которые я получил, возникает вопрос: почему суммирование работает над итерабельными номерами или списком с итерабельными списками, но не является итерируемым содержащие строки?
Ответы
Ответ 1
Python пытается отговорить вас от "суммирования" строк. Вы должны присоединиться к ним:
"".join(list_of_strings)
Это намного быстрее и использует гораздо меньше памяти.
Быстрый тест:
$ python -m timeit -s 'import operator; strings = ["a"]*10000' 'r = reduce(operator.add, strings)'
100 loops, best of 3: 8.46 msec per loop
$ python -m timeit -s 'import operator; strings = ["a"]*10000' 'r = "".join(strings)'
1000 loops, best of 3: 296 usec per loop
Изменить (чтобы ответить на редактирование OP). Что касается того, почему строки были, по-видимому, "выделены", я считаю, что это просто вопрос оптимизации для общего случая, а также для обеспечения наилучшей практики: вы можете присоединяться к строкам намного быстрее с помощью '.join, поэтому явно запрещающие строки на sum
укажут это на новичков.
BTW, это ограничение было "навсегда", т.е. поскольку sum
был добавлен как встроенная функция (rev. 32347)
Ответ 2
Фактически вы можете использовать sum(..)
для конкатенации строк, если вы используете соответствующий начальный объект! Конечно, если вы заходите так далеко, вы уже достаточно поняли, чтобы использовать "".join(..)
в любом случае..
>>> class ZeroObject(object):
... def __add__(self, other):
... return other
...
>>> sum(["hi", "there"], ZeroObject())
'hithere'
Ответ 3
Здесь источник: http://svn.python.org/view/python/trunk/Python/bltinmodule.c?revision=81029&view=markup
В функции builtin_sum мы имеем этот бит кода:
/* reject string values for 'start' parameter */
if (PyObject_TypeCheck(result, &PyBaseString_Type)) {
PyErr_SetString(PyExc_TypeError,
"sum() can't sum strings [use ''.join(seq) instead]");
Py_DECREF(iter);
return NULL;
}
Py_INCREF(result);
}
Итак, вот ваш ответ.
Он явно проверяется в коде и отклоняется.
Ответ 4
От документы:
Предпочтительный, быстрый способ конкатенировать последовательность строк - это вызов ''.join(последовательность).
Отменив sum
для работы с строками, Python предложил вам использовать правильный метод.
Ответ 5
Краткий ответ: эффективность.
Длинный ответ: функция sum
должна создать объект для каждой частичной суммы.
Предположим, что время, необходимое для создания объекта, прямо пропорционально размеру его данных. Пусть N обозначает количество элементов в последовательности, чтобы суммировать.
double
всегда имеют одинаковый размер, что делает sum
время работы O (1) × N = O (N).
int
(ранее известный как long
) имеет длину. Обозначим через M абсолютное значение наибольшего элемента последовательности. Тогда sum
наихудшее время работы: lg (M) + lg (2M) + lg (3M) +... + lg (NM) = N × lg (M) + lg (N!) = O (N log N).
Для str
(где M = длина самой длинной строки) наихудшее время работы: M + 2M + 3M +... + NM = M × (1 + 2 +... + N ) = O (N²).
Таким образом, строки sum
ming будут намного медленнее, чем числа sum
ming.
str.join
не выделяет никаких промежуточных объектов. Он предопределяет буфер, достаточно большой для хранения объединенных строк, и копирует строковые данные. Он работает в O (N) времени, намного быстрее, чем sum
.
Ответ 6
Причина Почему
@dan04 имеет отличное объяснение затрат на использование sum
в больших списках строк.
Недопустимая часть вопроса о том, почему str
не разрешена для sum
, заключается в том, что многие, многие люди пытались использовать sum
для строк, и не многие используют sum
для списков и кортежей и других O ( n ** 2) структуры данных. Ловушка заключается в том, что sum
отлично подходит для коротких списков строк, но затем запускается в производство, где списки могут быть огромными, а производительность замедляется до обхода. Это была такая общая ловушка, что было принято решение игнорировать утиную печать в этом случае и не допускать использования строк с помощью sum
.
Ответ 7
Изменить: Перемещено части об неизменяемости в историю.
В принципе, это вопрос предварительного распределения. Когда вы используете инструкцию типа
sum(["a", "b", "c", ..., ])
и ожидать, что он будет работать аналогично оператору reduce
, генерируемый код выглядит примерно так:
v1 = "" + "a" # must allocate v1 and set its size to len("") + len("a")
v2 = v1 + "b" # must allocate v2 and set its size to len("a") + len("b")
...
res = v10000 + "$" # must allocate res and set its size to len(v9999) + len("$")
В каждом из этих шагов создается новая строка, которая может дать некоторые накладные расходы на копирование, поскольку строки становятся длиннее и длиннее. Но, может быть, и не здесь. Более важно то, что каждая новая строка в каждой строке должна быть выделена для ее определенного размера (который я не знаю, что он должен выделять на каждой итерации оператора reduce
, могут быть некоторые очевидные эвристики для использования, и Python может выделите немного больше здесь и там для повторного использования, но в нескольких точках новая строка будет достаточно большой, чтобы это больше не помогло, и Python должен снова выделить, что довольно дорого.
Выделенный метод, например join
, тем не менее имеет задание определить реальный размер строки до ее начала и поэтому теоретически будет выделять только один раз в начале, а затем просто заполнить эту новую строку, что много дешевле другого решения.
Ответ 8
Я не знаю почему, но это работает!
import operator
def sum_of_strings(list_of_strings):
return reduce(operator.add, list_of_strings)