首页 > 解决方案 > Python3中使用和不使用生成器的执行时间差异

问题描述

我正在学习在 Python 3 中使用生成器,这也是我刚开始学习的。所以我正在做一个实验,看看生成器真正节省了多少时间。下面是我的代码。

import time


def fib(n):
    a = b = 1
    result = []
    for i in range(n):
        result.append(a)
        a, b = b, a + b
    return result


def fib_gen(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b


def calc(n):
    start_time = time.perf_counter()
    fib(n)
    end_time = time.perf_counter() - start_time
    print(f'fib({n})',end_time, 'secs')

    start_time2 = time.perf_counter()
    fib_gen(n)
    end_time2 = time.perf_counter() - start_time2
    print(f'fib_gen({n})',end_time2, 'secs')
    print('Improvement %ge ',((end_time-end_time2)/end_time)*100)


for x in range(1, 10000, 1000):
    calc(x)

输出:

fib(1) 4.976999999999759e-06 secs
fib_gen(1) 3.7599999999984868e-06 secs
Improvement %ge  24.452481414533484
fib(1001) 0.000278241999999998 secs
fib_gen(1001) 1.8640000000007262e-06 secs
Improvement %ge  99.33007957102065
fib(2001) 0.0006537540000000029 secs
fib_gen(2001) 1.4999999999980307e-06 secs
Improvement %ge  99.77055589717263
fib(3001) 0.0009883400000000007 secs
fib_gen(3001) 1.7100000000019877e-06 secs
Improvement %ge  99.82698261731773
fib(4001) 0.001559401999999998 secs
fib_gen(4001) 2.5110000000001798e-06 secs
Improvement %ge  99.8389767359539
fib(5001) 0.001501413 secs
fib_gen(5001) 1.582999999999446e-06 secs
Improvement %ge  99.89456598550835
fib(6001) 0.0019986470000000027 secs
fib_gen(6001) 1.186999999999716e-06 secs
Improvement %ge  99.94060982254497
fib(7001) 0.002645030999999999 secs
fib_gen(7001) 1.3070000000024729e-06 secs
Improvement %ge  99.95058659047842
fib(8001) 0.0035235960000000004 secs
fib_gen(8001) 3.18499999999583e-06 secs
Improvement %ge  99.90960938768248
fib(9001) 0.004458613 secs
fib_gen(9001) 5.944000000000782e-06 secs
Improvement %ge  99.866684998227

我不想相信的是,每次生成器的性能都提高了近 99%。怎么会这样?我需要做什么才能更好地掌握它。

标签: pythonpython-3.x

解决方案


当您调用生成器函数时,您实际上并没有在其中运行计算。

def generate_range(n):
    for i in range(n):
        yield i

print(generator()) # prints "<generator object f at 0x123456789>"

要从生成器中获取值,您需要调用next它们或迭代它们:

gen = generate_range(3)

print(next(gen)) # prints 0
print(next(gen)) # prints 1
print(next(gen)) # prints 2
print(next(gen)) # raises StopIteration

for x in generator(3):
    print(x) # prints 0, then 1, then 2

从最后一个示例中,您还可以看到生成器yield按顺序获取其值,而不是一次全部获取。这允许生成器用于惰性计算——在被要求提供下一个结果之前,它们不会执行任何计算。

这是一个演示生成器如何惰性的示例:

import time

# In the real world, this could be any slow operation,
# like getting something from the internet.
def get_number_slowly(n):
    time.sleep(n ** 2)
    return n

def get_with_function(numbers):
    results = []
    for number in numbers:
        results.append(get_number_slowly(number))
    return results

def get_with_generator(numbers):
    for number in numbers:
        yield get_number_slowly(number)


numbers = [3, 2, 0, 3, 5, 1018]

# Find the first number greater than four using each of the two methods

for number in get_with_generator(numbers):
    print("Checking", number, "> 4")
    if number > 4:
        print(number, "is greater than 4")
        break

for number in get_with_function(numbers):
    print("Checking", number, "> 4")
    if number > 4:
        print(number, "is greater than 4")
        break

这两个函数将具有相同的输出,但生成器一次打印一行,总共等待 47 秒,然后打印“5 大于 4”并停止。在打印相同的get_with_function输出之前等待了将近 12 天,因为get_number_slowly它甚至在开始循环之前就调用了它给出的所有数字,所以它必须等待 1018 2秒才能获得最后一个数字。

所以如果你想在不同的计算之间做一些事情,或者你只想对你需要的数据进行计算,那么生成器会非常有用。


推荐阅读