61

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 对包含数字的可迭代对象或包含列表的可迭代对象起作用,但对包含字符串的可迭代对象不起作用?

4

8 回答 8

49

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 的编辑):至于为什么字符串显然是“单出”的,我相信这只是针对常见情况进行优化以及执行最佳实践的问题:您可以使用 '' 更快地连接字符串。加入,所以明确禁止字符串sum会向新手指出这一点。

顺便说一句,这个限制已经“永远”存在,即,自从sum作为内置函数添加(rev. 32347

于 2010-08-19T19:15:55.930 回答
27

如果您使用适当的起始对象,您实际上可以sum(..)用来连接字符串!当然,如果你走到这一步,你已经明白了"".join(..)无论如何都可以使用..

>>> class ZeroObject(object):
...  def __add__(self, other):
...   return other
...
>>> sum(["hi", "there"], ZeroObject())
'hithere'
于 2010-08-19T20:01:06.497 回答
17

这是来源: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);
    }

所以..这就是你的答案。

它在代码中明确检查并被拒绝。

于 2010-08-20T05:15:12.640 回答
14

文档

连接字符串序列的首选快速方法是调用 ''.join(sequence)。

通过sum拒绝对字符串进行操作,Python 鼓励您使用正确的方法。

于 2010-08-19T19:16:24.527 回答
11

简短的回答:效率。

长答案:该sum函数必须为每个部分总和创建一个对象。

假设创建对象所需的时间与其数据大小成正比。让 N 表示要求和的序列中元素的数量。

doubles 总是相同的大小,这使得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明字符串会比sum明数字慢得多。

str.join不分配任何中间对象。它预先分配一个足够大的缓冲区来保存连接的字符串,并复制字符串数据。它在O(N)时间内运行,比sum.

于 2010-08-20T04:46:26.957 回答
10

之所以

@dan04 很好地解释了sum在大型字符串列表上使用的成本。

str关于为什么不允许使用的缺失部分sum是,很多人试图将其sum用于字符串,而sum用于列表和元组以及其他 O(n**2) 数据结构的人并不多。陷阱是sum对于字符串的短列表工作得很好,但随后在列表可能很大的地方投入生产,并且性能减慢到爬行。这是一个常见的陷阱,因此决定在这种情况下忽略鸭子类型,并且不允许字符串与sum.

于 2014-04-28T20:05:53.893 回答
4

编辑:将关于不变性的部分移至历史。

基本上,这是一个预分配的问题。当您使用诸如

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可以在字符串开始之前计算出字符串的实际大小,因此理论上只会在开始时分配一次,然后只填充新字符串,这比其他解决方案便宜得多。

于 2010-08-19T19:26:51.197 回答
3

我不知道为什么,但这有效!

import operator
def sum_of_strings(list_of_strings):
    return reduce(operator.add, list_of_strings)
于 2014-10-21T12:45:37.513 回答