从这篇帖子中我学到,你可以使用sum()
函数来连接元组:
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
>>> sum(tuples, ())
('hello', 'these', 'are', 'my', 'tuples!')
看起来相当不错。但是为什么这行得通呢?而且,这是否是最优的实现方式,或者在 itertools
中有更好的替代方案呢?
从这篇帖子中我学到,你可以使用sum()
函数来连接元组:
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
>>> sum(tuples, ())
('hello', 'these', 'are', 'my', 'tuples!')
看起来相当不错。但是为什么这行得通呢?而且,这是否是最优的实现方式,或者在 itertools
中有更好的替代方案呢?
在 Python 中,加法运算符用于连接元组(tuples):
('a', 'b')+('c', 'd')
Out[34]: ('a', 'b', 'c', 'd')
从 sum
的文档字符串中可以得知:
返回一个数字迭代器的 'start' 值(默认为 0)的总和。
这意味着 sum
不是从可迭代对象的第一个元素开始计算,而是从传递给 start=
参数的初始值开始计算。
默认情况下,sum
用于数字,因此默认的起始值为 0
。因此,对元组的可迭代对象求和需要以空元组开始。()
是一个空元组。
type(())
Out[36]: tuple
因此,工作串联。
就性能而言,这里有一个比较:
%timeit sum(tuples, ())
The slowest run took 9.40 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 285 ns per loop
%timeit tuple(it.chain.from_iterable(tuples))
The slowest run took 5.00 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 625 ns per loop
现在t2的大小为10000:
%timeit sum(t2, ())
10 loops, best of 3: 188 ms per loop
%timeit tuple(it.chain.from_iterable(t2))
1000 loops, best of 3: 526 µs per loop
所以,如果你的元组列表很小,那就不必费心了。但如果它是中等大小或更大,则应使用itertools
。
>>> () + ('hello',) + ('these', 'are') + ('my', 'tuples!')
('hello', 'these', 'are', 'my', 'tuples!')
这基本上就是sum
正在做的事情,你给出一个空元组的初始值,然后将元组添加到其中。
然而,这通常是一个不好的主意,因为元组的相加会创建一个新的元组,所以你需要创建多个中间元组,然后将它们复制到连接的元组中:
()
('hello',)
('hello', 'these', 'are')
('hello', 'these', 'are', 'my', 'tuples!')
这是一种具有二次运行时行为的实现。可以通过避免中间元组来避免这种二次运行时行为。
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
使用嵌套生成器表达式:
>>> tuple(tuple_item for tup in tuples for tuple_item in tup)
('hello', 'these', 'are', 'my', 'tuples!')
或者使用生成器函数:
def flatten(it):
for seq in it:
for item in seq:
yield item
>>> tuple(flatten(tuples))
('hello', 'these', 'are', 'my', 'tuples!')
或者使用itertools.chain.from_iterable
:
>>> import itertools
>>> tuple(itertools.chain.from_iterable(tuples))
('hello', 'these', 'are', 'my', 'tuples!')
如果您对这些表现感兴趣(使用我的simple_benchmark
包):
import itertools
import simple_benchmark
def flatten(it):
for seq in it:
for item in seq:
yield item
def sum_approach(tuples):
return sum(tuples, ())
def generator_expression_approach(tuples):
return tuple(tuple_item for tup in tuples for tuple_item in tup)
def generator_function_approach(tuples):
return tuple(flatten(tuples))
def itertools_approach(tuples):
return tuple(itertools.chain.from_iterable(tuples))
funcs = [sum_approach, generator_expression_approach, generator_function_approach, itertools_approach]
arguments = {(2**i): tuple((1,) for i in range(1, 2**i)) for i in range(1, 13)}
b = simple_benchmark.benchmark(funcs, arguments, argument_name='number of tuples to concatenate')
b.plot()
如果你只连接少量元组,那么使用sum
方法会非常快,但是如果你尝试连接大量元组,它将变得非常慢。在连接多个元组时,已测试的最快方法是itertools.chain.from_iterable
(Python 3.7.2 64位,Windows 10 64位)
这很聪明,我不得不笑,因为帮助明确禁止字符串,但它可以工作。
sum(...)
sum(iterable[, start]) -> value
Return the sum of an iterable of numbers (NOT strings) plus the value
of parameter 'start' (which defaults to 0). When the iterable is
empty, return start.
您可以添加元组以获得一个新的、更大的元组。由于您提供了一个元组作为起始值,所以加法运算可行。
sum
并没有对字符串求和:在输入中分开的两个字符串在这里没有被连接起来。(例如,无法使用sum
将hello
和world
变成helloworld
。) - ShreevatsaR+
运算符的对象进行求和,包括字符串。为了追求性能和良好的规范,而明确禁止字符串这种情况,这种设计并不好。尤其是 Python 中还有许多其他不被禁止的反模式存在。 - mike3996补充一下已被接收的答案,提供更多基准测试数据:
import functools, operator, itertools
import numpy as np
N = 10000
M = 2
ll = tuple(tuple(x) for x in np.random.random((N, M)).tolist())
%timeit functools.reduce(operator.add, ll)
# 407 ms ± 5.63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit functools.reduce(lambda x, y: x + y, ll)
# 425 ms ± 7.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit sum(ll, ())
# 426 ms ± 14.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit tuple(itertools.chain(*ll))
# 601 µs ± 5.43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit tuple(itertools.chain.from_iterable(ll))
# 546 µs ± 25.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
编辑:代码已更新以实际使用元组。根据评论,最后两个选项现在位于tuple()
构造函数内,并且所有时间都已更新(为了一致性)。itertools.chain*
选项仍然是最快的,但现在边距已经缩小。
itertools.chain
和itertools.chain.from_iterable
返回迭代器。为了公平计时,你需要使用 tuple(itertools.chain...)
进行迭代。 - MSeifert第二个参数start
,在你放置()
的地方,是要添加到的起始对象,对于数字加法,默认为0
。
这里是sum
的一个示例实现(我的期望):
def sum(iterable, /, start=0):
for element in iterable:
start += element
return start
例子:
>>> sum([1, 2, 3])
6
>>> tuples = (('hello',), ('these', 'are'), ('my', 'tuples!'))
>>> sum(tuples)
TypeError: unsupported operand type(s) for +=: 'int' and 'tuple'
>>> sum(tuples, ())
('hello', 'these', 'are', 'my', 'tuples!')
>>>
由于元组连接支持使用+
,因此它可以正常工作。
实际上,这被翻译为:
>>> () + ('hello',) + ('these', 'are') + ('my', 'tuples!')
('hello', 'these', 'are', 'my', 'tuples!')
>>>
tuple(chain(*tuples))
。 - PM 2Ringchain
,因为它比sum
更低效(除非元组的集合非常小)。改用chain.from_iterable
。 - ekhumoro