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 по этой причине, поэтому объяснение или ссылка на поток, объясняющий это, было бы хорошо.

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

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

8 59

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

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

int (ранее известная как long) является произвольная длина. Пусть M обозначает абсолютное значение самого большого элемента последовательности. Тогда sumв худшем случае время работы составляет lg(M) + lg(2M) + lg (3M) + ... + ЛГ(Нм) = Н×ЛГ(м) + LG с(Н!) = O (N log N).

на str (где M = длина самой длинной строки), в худшем случае время выполнения составляет M + 2M + 3M + ... + NM = M×(1 + 2 + ... + N)= O (N2).

таким образом, sumмин строки будут намного медленнее, чем 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)