Python sum, почему не строки?

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. Мой вопрос в том, почему опция использования sum для строк была запрещена, и никакого запрета не было, скажем, для списков.

Изменить 2: хотя я считаю, что это не нужно учитывая все хорошие ответы я получил, вопрос: почему sum работает над итерацией, содержащей числа, или итерацией, содержащей списки, но не итерацией, содержащей строки?

8 ответов


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

Edit (чтобы ответить на редактирование OP): что касается того, почему строки были, по-видимому, "выделены", я считаю, что это просто вопрос оптимизации для общего случая, а также обеспечения наилучшей практики: вы можете присоединяться к строкам намного быстрее с ".join, поэтому явно запрещает строки на sum укажет на это новичкам.

кстати, это ограничение было на месте "вечно", т. е. с sum была добавлена как встроенная функция (откр. 32347)


вы можете фактически использовать sum(..) для объединения строк, Если вы используете соответствующий начальный объект! Конечно, если вы зайдете так далеко, вы уже поняли достаточно, чтобы использовать "".join(..) в любом случае..

>>> class ZeroObject(object):
...  def __add__(self, other):
...   return other
...
>>> sum(["hi", "there"], ZeroObject())
'hithere'

вот источник: 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);
    }

Так.. это твой ответ.

он явно проверен в коде и отклонен.


С документы:

предпочтительный, быстрый способ объединения a последовательность строк по вызову ".соединение(последовательность).

делая sum отказаться работать со строками, Python призвал вас использовать правильный метод.


короткий ответ: эффективность.

длинный ответ:sum функция должна создать объект для каждой частичной суммы.

предположим, что время, необходимое для создания объекта, прямо пропорционально размеру его данных. Пусть N обозначает количество элементов в последовательности для суммирования.

doubles всегда одинакового размера, что делает sumвремя работы O (1)×N = O (N).

int (ранее известная как long) arbitary-длина. Пусть 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 (N2).

таким образом, sumстроки ming будут намного медленнее, чем sumМинг числа.

str.join не выделяет никаких промежуточных объектов. Он предварительно выделяет буфер, достаточно большой для хранения Соединенных строк, и копирует строковые данные. Он бежит в O (N) время, гораздо быстрее, чем sum.


Почему

@dan04 имеет отличное объяснение затрат на использование sum в больших списках строк.

недостающая часть о том, почему str Не допускается sum Это то, что многие, многие люди пытались использовать sum для строк, и не многие используют sum для списков и кортежей и других o (n**2) структур данных. Ловушка в том, что sum отлично работает для коротких списков строк, но затем попадает в производство, где списки могут быть огромными,и производительность замедляется до обхода. Это была такая распространенная ловушка, что было принято решение игнорировать утиный ввод в этом случае и не позволять использовать строки с sum.


Edit: переместил части о неизменности в историю.

в основном, это вопрос предварительного распределения. Когда вы используете оператор, такой как

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, однако имеет задание выяснить реальный размер строки до ее запуска и поэтому теоретически будет выделять только один раз, в начале, а затем просто заполнить эту новую строку, что намного дешевле, чем другое решение.


Я не знаю, почему, но это работает!

import operator
def sum_of_strings(list_of_strings):
    return reduce(operator.add, list_of_strings)