python 高階篇 迭代器和生成器深入理解

2022-03-27 20:32:47 字數 4856 閱讀 5536

列表/元組/字典/集合都是容器。對於容器,可以很直觀地想象成多個元素在一起的單元;而不同容器的區別,正是在於內部資料結構的實現方法。

所有的容器都是可迭代的(iterable)。另外字串也可以被迭代。

迭代可以想象成是你去買蘋果,賣家並不告訴你他有多少庫存。這樣,每次你都需要告訴賣家,你要乙個蘋果,然後賣家採取行為:要麼給你拿乙個蘋果;要麼告訴你,蘋果已經賣完了。你並不需要知道,賣家在倉庫是怎麼擺放蘋果的。

嚴謹地說,迭代器(iterator)提供了乙個next(可以不重複不遺漏地乙個乙個拿到所有元素) 的方法。呼叫這個方法後,你要麼得到這個容器的下乙個物件,要麼得到乙個stopiteration的錯誤(蘋果賣完了)。

示例,判斷是否可迭代

from collections.abc import iterable

params = [

1234,

'1234',

[1, 2, 3, 4],

set([1, 2, 3, 4]),

,(1, 2, 3, 4)

]

for param in params:

print('{} is iterable? {}'.format(param, isinstance(param, iterable)))

# 輸出

# 1234 is iterable? false

# 1234 is iterable? true

# [1, 2, 3, 4] is iterable? true

# is iterable? true

# is iterable? true

# (1, 2, 3, 4) is iterable? true

生成器可以想象成是你去買蘋果,賣家並沒有庫存。這樣,每次你都需要告訴賣家,你要乙個蘋果,然後賣家採取行為,立馬生成 1 個蘋果(生成速度極快):要麼給你拿乙個蘋果;要麼告訴你,蘋果已經賣完了。

示例,迭代器與生成器的對比

import os

import psutil

import time

import functools

def log_execution_time(func):

@functools.wraps(func)

start = time.perf_counter()

res = func(*args, **kwargs)

end = time.perf_counter()

print('{} took {} ms'.format(func.__name__, (end - start) * 1000))

return res

# 顯示當前 python 程式占用的記憶體大小

def show_memory_info(hint):

pid = os.getpid()

p = psutil.process(pid)

info = p.memory_full_info()

memory = info.uss / 1024. / 1024

print('{} memory used: {} mb'.format(hint, memory))

@log_execution_time

def test_iterator():

show_memory_info('initing iterator')

list_1 = [i for i in range(100000000)]

show_memory_info('after iterator initiated')

print(sum(list_1))

show_memory_info('after sum called')

@log_execution_time

def test_generator():

show_memory_info('initing generator')

list_2 = (i for i in range(100000000))

show_memory_info('after generator initiated')

print(sum(list_2))

show_memory_info('after sum called')

test_iterator()

print()

test_generator()

########## 輸出 ##########

# initing iterator memory used: 10.16796875 mb

# after iterator initiated memory used: 3664.34765625 mb

# 4999999950000000

# after sum called memory used: 3664.34765625 mb

# test_iterator took 6179.794754018076 ms

# initing generator memory used: 19.140625 mb

# after generator initiated memory used: 19.14453125 mb

# 4999999950000000

# after sum called memory used: 19.171875 mb

# test_generator took 4912.561981996987 ms

迭代器是乙個有限集合,生成器則可以成為乙個無限集

我們並不需要在記憶體中同時儲存這麼多東西,比如對元素求和,我們只需要知道每個元素在相加的那一刻是多少就行了,用完就可以扔掉了。

於是,生成器的概念應運而生,在你呼叫next()函式的時候,才會生成下乙個變數。生成器在 python 的寫法是用小括號括起來,(i for i in range(100000000)),即初始化了乙個生成器。

這樣一來,你可以清晰地看到,生成器並不會像迭代器一樣占用大量記憶體,只有在被使用的時候才會呼叫。而且生成器在初始化的時候,並不需要執行一次生成操作,相比於test_iterator()test_generator()函式節省了一次生成一億個元素的過程,因此耗時明顯比迭代器短。

示例,數學中有乙個恒等式,(1 + 2 + 3 + ... + n)^2 = 1^3 + 2^3 + 3^3 + ... + n^3 的證明

def generator(k):

i = 1

while true:

yield i ** k

i += 1

gen_1 = generator(1)

gen_3 = generator(3)

print(gen_1)

print(gen_3)

def get_sum(n):

sum_1, sum_3 = 0, 0

for i in range(n):

next_1 = next(gen_1)

next_3 = next(gen_3)

print('next_1 = {}, next_3 = {}'.format(next_1, next_3))

sum_1 += next_1

sum_3 += next_3

print(sum_1 * sum_1, sum_3)

get_sum(8)

########## 輸出 ##########

# # # next_1 = 1, next_3 = 1

# next_1 = 2, next_3 = 8

# next_1 = 3, next_3 = 27

# next_1 = 4, next_3 = 64

# next_1 = 5, next_3 = 125

# next_1 = 6, next_3 = 216

# next_1 = 7, next_3 = 343

# next_1 = 8, next_3 = 512

# 1296 1296

接下來的 yield 是魔術的關鍵。對於初學者來說,你可以理解為,函式執行到這一行的時候,程式會從這裡暫停,然後跳出,不過跳到**呢?答案是next()函式。那麼i ** k是幹什麼的呢?它其實成了next()函式的返回值。這樣,每次 next(gen) 函式被呼叫的時候,暫停的程式就又復活了,從 yield 這裡向下繼續執行;同時注意,區域性變數 i 並沒有被清除掉,而是會繼續累加。我們可以看到 next_1 從 1 變到 8,next_3 從 1 變到 512。

示例,給定兩個序列,判定第乙個是不是第二個的子串行。

leetcode 鏈結如下:

先來解讀一下這個問題本身。序列就是列表,子串行則指的是,乙個列表的元素在第二個列表中都按順序出現,但是並不必挨在一起。舉個例子,[1, 3, 5] 是 [1, 2, 3, 4, 5] 的子串行,[1, 4, 3] 則不是。

def is_subsequence(ls, sub):

ls = iter(ls)

return all(i in ls for i in sub)

print(is_subsequence([1, 2, 3, 4, 5],[1, 3, 5]))

print(is_subsequence([1, 2, 3, 4, 5],[1, 4, 3]))

########## 輸出 ##########

# true

# false

Python高階之迭代器和生成器

python中任意的物件,只要它定義了可以返回乙個迭代器的 iter 方法,或者定義了可以支援下標索引的 getitem 方法,那麼它就是乙個可迭代物件。簡單來說,可迭代物件就是能提供迭代器的任意物件,但可迭代物件本身並不一定是乙個迭代器。任意物件,只要定義了next python2 或者 next...

Python 高階特性 生成器和迭代器

生成器generator 目的 list容量太大佔太多記憶體而操作時卻僅僅訪問前幾個元素,generator可以通過迴圈不斷推算出後面的元素,邊迴圈邊計算,節省大量空間 建立乙個generator vlist a a for a in range 3 vlist 0,1,4 vgenerator b...

python高階 迭代器 生成器

迭代是訪問集合元素的一種方式。迭代器是乙個可以記住遍歷的位置的物件。迭代器物件從集合的第乙個元素開始訪問,直到所有 list tuple等都是可迭代物件,我們可以通過iter 函式獲取這些可迭代物件的迭代器。然後我們可以對獲取到的迭代器不斷使用next 函式來獲取下一條資料。iter 函式實際上就是...