7.1 又見函數(shù)
1.Python中的函數(shù)式
前面介紹了面向進程和面向?qū)ο蟮木幊蹋@一章講解面向函數(shù)的編程即函數(shù)式編程视译。函數(shù)式編程的本質(zhì)在于封裝,它以函數(shù)為中心進行代碼封裝,函數(shù)在面向進程中涉及過私沮,它有參數(shù)和返回值,起到輸入和輸出數(shù)據(jù)的功能和橙。
函數(shù)式編程強調(diào)了函數(shù)的純粹性仔燕,一個純函數(shù)是沒有副作用的,即一個函數(shù)不會影響其他函數(shù)魔招。我們在之前的可變對象中演示過晰搀,在函數(shù)內(nèi)部改變列表里面的對象,會影響函數(shù)外部的列表元素办斑,其他調(diào)用該列表的函數(shù)也會產(chǎn)生相應的影響外恕,這就產(chǎn)生了副作用。所以乡翅,為了是函數(shù)純粹鳞疲,我們在函數(shù)內(nèi)部使用的都是不可變更的變量。
由于Python中存在可變對象蠕蚜,因此只能盡量避免副作用尚洽。
函數(shù)式編程由于函數(shù)相互獨立,因此不用擔心函數(shù)調(diào)用對其他函數(shù)的影響波势,使用起來更加簡單翎朱。
而且涉枫,純函數(shù)也方便進行并行化運算喘漏。
在進行并行化編程時,我們經(jīng)常擔心不同進程之間相互干擾的問題士修。當多個進程或者線程同時修改一個變量時凛忿,進程或線程的先后順序會影響最終結果澈灼,比如
from threading import Thread
x = 5
def double():
global x # 聲明全局變量x
x = x * 2
def plus_ten():
global x # 聲明全局變量x
x = x + 10
th1 = Thread(target = double) # 線程1
th2 = Thread(target = plus_ten) # 線程2
th1.start() # 線程1開始執(zhí)行
th2.start() # 線程2開始執(zhí)行
th1.join() # 線程1結束
th2.join() # 線程2結束
print(x)
結果:
20
將上面的線程調(diào)換一下
th2.start() # 線程2開始執(zhí)行
th1.start() # 線程1開始執(zhí)行
th2.join() # 線程2結束
th1.join() # 線程1結束
print(x)
結果:
30
global
是聲明全局變量的關鍵字,函數(shù)對全局變量的修改能被其他函數(shù)看見,因此造成了副作用叁熔。如果并行地執(zhí)行上面兩個函數(shù)委乌,執(zhí)行順序是不確定的,結果也不一樣荣回。比如先執(zhí)行double遭贸,再執(zhí)行plus_ten,得到的結果是20心软;若先執(zhí)行plus_ten壕吹,再執(zhí)行double,得到的結果是30删铃。這被稱為竟跑條件耳贬,是并行編程中需要盡力避免的。
而函數(shù)式編程消滅了副作用猎唁,也在無形之中消除了竟跑條件的可能咒劲。因此,函數(shù)式編程天然地適用于并行化運算诫隅。
Python中加入了lambda函數(shù)腐魂、map、filter阎肝、reduce等高階函數(shù)挤渔,引入了函數(shù)式編程的特征。
函數(shù)式編程的思路是自上而下的风题,先提出一個大問題,在最高層定義一個函數(shù)來解決這個大問題嫉父,在函數(shù)的內(nèi)部沛硅,再用其他函數(shù)來解決小問題。比如绕辖,我們要解決"如何把大象放進冰箱"的問題摇肌,首先我們定義一個函數(shù)"把大象放進冰箱"來解決大問題,然后在"把大象放進冰箱"里面仪际,有三步函數(shù)"打開冰箱門"围小、"放入大象"、"關上冰箱"树碱,如果需要繼續(xù)細化肯适,則在這三步函數(shù)內(nèi)部繼續(xù)調(diào)用其他函數(shù)。
2.并行運算
并行運算是指多條指令同時執(zhí)行成榜。對應的串行運算指的是同一時間只能執(zhí)行一條指令框舔。
大規(guī)模的并行運算一般是在多個主機組成的集群上進行的,主機之間通過高速的網(wǎng)絡設備通信。但由于集群的成本過高刘绣,我們可以在單機上通過多線程或多進程來模擬集群的并行處理樱溉。
在一臺單機上,往往運行著多個程序纬凤,即進程福贞。如用瀏覽器上網(wǎng)時,用網(wǎng)易云聽音樂停士,計算機就同時運行兩個進程肚医。這就是單機"分時復用"的方式,把運算能力分給多個進程向瓷。
也就是說肠套,集群和單機都實現(xiàn)了多個進程的并行運算。集群上的多進程分布在不同的主機猖任,而單機則使用"分時復用"的方式來實現(xiàn)你稚。下面看一個多進程的例子
import multiprocessing as mulpr
def proc1():
return 333*333
def proc2():
return 444*444
p1 = mulpr.Process(target = proc1())
p2 = mulpr.Process(target = proc2())
p1.start() # 啟動進程
p2.start()
p1.join() # 結束進程
p2.join()
多進程和多線程的區(qū)分在哪呢?
一個程序運行后朱躺,就是一個進程刁赖。進程有自己的內(nèi)存空間,存儲自己的運行狀態(tài)长搀、數(shù)據(jù)和代碼宇弛。進程與進程之間一般不會相互讀取數(shù)據(jù)。
但在一個進程中源请,可以有多個"線程"任務枪芒,處理器在多個線程之間進行切換,從而形成并行的多線程處理谁尸。線程之間可以共享同一個進程的內(nèi)存舅踪。
7.2 被解放的函數(shù)
1.函數(shù)作為參數(shù)
在函數(shù)式編程中,函數(shù)是第一級對象良蛮,也即是函數(shù)能像普通對象一樣使用抽碌。因此,函數(shù)也可以作為參數(shù)决瞳,成為其他函數(shù)的參數(shù)货徙。如
def square_sum(a,b): # 準備作為參數(shù)的函數(shù),求出平方和
return a**2 + b**2
def cubic_sum(a,b): # 準備作為參數(shù)的函數(shù)皮胡,求出立方和
return a**3 + b**3
def argF_demo(f,a,b): # f是函數(shù)痴颊,a是數(shù)字,b是數(shù)字
return f(a,b)
print(argF_demo(square_sum,3,5))
print(argF_demo(cubic_sum,3,5))
結果:
34
152
argF_demo()的第一個參數(shù)f是一個函數(shù)對象胸囱。square_sum和cubic_sum作為f傳遞給argF_demo()祷舀。
再如,圖形用戶界面中,作為參數(shù)的函數(shù)經(jīng)常起到回調(diào)(callback)的作用裳扯。當某個事件發(fā)生時(如界面中的某個按鈕被點擊)抛丽,回調(diào)函數(shù)就會被調(diào)用。下面是GUI回調(diào)的例子
import tkinter as tk
def callback():
listbox.insert(tk.END,"我出現(xiàn)了,我是回調(diào)函數(shù)")
if __name__ == "__main__":
master = tk.Tk()
button = tk.Button(master,text = "OK",command = callback)
# 創(chuàng)建一個按鈕饰豺,一旦點擊就會使用callback函數(shù)
button.pack()
listbox = tk.Listbox(master)
listbox.pack()
tk.mainloop()
Python中內(nèi)置了tkinter的圖形化功能亿鲜。listbox是列表欄,就是上圖的顯示框冤吨,一旦點擊按鈕OK蒿柳,就會在列表欄中插入"我出現(xiàn)了,我是回調(diào)函數(shù) "漩蟆。
2.函數(shù)作為返回值
函數(shù)是一個對象垒探,除了可以作為函數(shù)的參數(shù),還可以作為函數(shù)的返回值怠李。
def line_res():
def line(x):
return 2*x + 1
return line # 返回一個函數(shù)
line1 = line_res()
print(line1(3))
結果:
7
line_res將函數(shù)line作為對象返回給line1圾叼,line1通過輸入?yún)?shù)就可以得到結果11。
從上例可以看出捺癞,函數(shù)里面再定義了函數(shù)夷蚊,函數(shù)內(nèi)部的函數(shù)對象也有作用域,Python中用縮進塊來表示髓介。如
def line_res():
def line(x):
return 2*x + 1
print(line(3)) # 作用域內(nèi)
line_res() # 打印出7
print(line(3)) # 作用域之外惕鼓,將會報錯
結果:
7
NameError: name 'line' is not defined
line()函數(shù)在line_res()的作用域之內(nèi),只能在line_res()之內(nèi)調(diào)用唐础,當在line_res()的作用域之外調(diào)用line()函數(shù)時箱歧,將會報錯。
3.閉包
閉包:一個函數(shù)與它的環(huán)境變量合在一起彻犁,即為閉包叫胁。
用一個例子來理解閉包和環(huán)境變量
def line_res():
b = 15 # 這個就是line()的環(huán)境變量
def line(x):
return 2*x + b
b = 5 # line()的環(huán)境變量
return line # 返回函數(shù)對象
line1 = line_res() # 將函數(shù)對象賦給line1
print(line1(3))
結果:
11
line()函數(shù)的引用了它作用域外的變量b,b是line()外部的變量汞幢,這個b就是line()函數(shù)的環(huán)境變量。line()函數(shù)和環(huán)境變量b合在一塊微谓,這就是閉包森篷。
上面代碼中,b分別在line()定義前后有兩次不同的值豺型,最終結果是11仲智,即line()中的b=5。因此姻氨,閉包中的環(huán)境變量是內(nèi)部函數(shù)作為對象返回時钓辆,最近賦值的那個值。
在Python中,閉包(closure)是一個包含環(huán)境變量取值的函數(shù)對象前联。環(huán)境變量取值被復制到函數(shù)對象的__closure__
屬性中功戚。
def line_res():
b = 15 # 這個就是line()的環(huán)境變量
def line(x):
return 2*x + b + a
b = 5 # line()的環(huán)境變量
a = 2 # line()的環(huán)境變量
return line # 返回函數(shù)對象
line1 = line_res()
print(line1.__closure__) # 閉包
print(line1.__closure__[0].cell_contents) # 打印2 也即是a=2
print(line1.__closure__[1].cell_contents) # 打印5 也即是b=5
結果:
(<cell at 0x000001CC6DA83288: int object at 0x00007FFBB9B19360>,
<cell at 0x000001CC6DDFFFD8: int object at 0x00007FFBB9B193C0>)
2
5
可以看出,閉包屬性中包含了一個元組似嗤,元組中是cell型的對象啸臀。
第一個是2,即環(huán)境變量a=2烁落,第二個是5乘粒,即b=5。
閉包可以提高代碼的復用性伤塌。
def line1():
return x + 1
def line2():
return 2*x + 1
def line3():
return 4*x + 2
上面一共有三條直線灯萍,可以看出都有一個自變量x,還有另外兩個數(shù)每聪,可以定義為a和b旦棉。則上面的直線可以統(tǒng)一寫為:a*x+b。用閉包來改寫代碼
def line_res(a,b):
def line(x):
return a*x + b
return line
line1 = line_res(1,1)
line2 = line_res(2,1)
line3 = line_res(4,2)
print(line1(2),line2(2),line3(2))
結果:
3 5 10
閉包還可以起到減少函數(shù)參數(shù)的作用熊痴。
def curve(a,b,c,x): # 定義一個二次函數(shù)他爸,需要4個參數(shù)a,b,c,x
return a*(x**2) + b*x + c
print(curve(2,4,3,1))
print(curve(2,4,3,2))
結果:
9
19
可見每次計算一個二次函數(shù)值需要輸入很多參數(shù),盡管我們只是自變量x改變而其他參數(shù)不變果善。我們可以通過閉包來減少參數(shù)
def curve_closure(a,b,c):
def curve(x):
return a*(x**2) + b*x + c
return curve
curve1 = curve_closure(2,4,3)
print(curve1(1))
print(curve1(2))
結果:
9
19
這段代碼可以與上面的代碼比較诊笤,需要輸入的參數(shù)少了3個。
7.3 小女子的梳妝匣子
1.裝飾器
裝飾器是一種高級Python語法巾陕。它可以對一個函數(shù)讨跟、方法或類進行加工。
首先我們定義兩個函數(shù)鄙煤,一個計算平方和晾匠,一個計算平方差。
def square_sum(a,b):
return a**2 + b**2
def square_diff(a,b):
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
結果:
25
12
如果我們想要顯示我們的輸入梯刚,可以改進
def square_sum(a,b):
print("輸入的數(shù)據(jù)",a,b)
return a**2 + b**2
def square_diff(a,b):
print("輸入的數(shù)據(jù)",a,b)
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
結果:
輸入的數(shù)據(jù) 4 3
25
輸入的數(shù)據(jù) 4 2
12
可以看出來凉馆,打印輸入數(shù)據(jù)的操作語句一樣,我們可以將這個打印輸入數(shù)據(jù)的操作寫成裝飾器亡资,然后用于函數(shù)上澜共。
def decorator_demo(old_fun): # 定義一個裝飾器
def new_fun(a,b):
print("輸入的數(shù)據(jù)",a,b)
return old_fun(a,b)
return new_fun
@decorator_demo
def square_sum(a,b):
return a**2 + b**2
@decorator_demo
def square_diff(a,b):
return a**2 - b**2
print(square_sum(4,3))
print(square_diff(4,2))
結果:
輸入的數(shù)據(jù) 4 3
25
輸入的數(shù)據(jù) 4 2
12
裝飾器用def定義,它接收了一個可調(diào)用對象(如函數(shù)對象)作為輸入?yún)?shù)锥腻,并且返回一個新的可調(diào)用對象嗦董,如上面的new_fun()。在new_fun()中瘦黑,我們增加了打印的功能京革,同時也保留了原來old_fun的功能奇唤。
定義好裝飾器之后,我們通過"@"語法使用它匹摇。
在函數(shù)square_sum()咬扇,square_diff()之前調(diào)用裝飾器@decorator_demo,實際上就是將原來的函數(shù)square_sum()来惧,square_diff()
傳遞給decorator_demo()
冗栗,然后將decorator_demo()
返回的新函數(shù)對象賦給原來的函數(shù)名square_sum(),square_diff()
供搀。即如在調(diào)用square_sum(3,4)
時隅居,實際發(fā)生
我們可以再來舉一個例子的诵,假設我們想知道函數(shù)的運行時間万栅。這個對于每個函數(shù)都是適用的,因此我們可以把這個功能做成裝飾器西疤。
import time as t
def decorator(old_fun):
def new_fun(*arg,**dict_arg):
start = t.perf_counter()
res = old_fun(*arg,**dict_arg)
end = t.perf_counter()
print("函數(shù)運行時間為:",end - start)
return res
return new_fun
@decorator
def curve(a,b,c,x):
return a*x**2 + b*x + c
print(curve(2,3,4,1))
結果:
函數(shù)運行時間為: 4.700001227320172e-06
9
2.帶參裝飾器
裝飾器允許我們使用它時烦粒,傳入其他參數(shù)。
def pre_str(pre): # 帶參的裝飾器
def decorator(old_fun):
def new_fun(a,b):
print(pre,"輸入的數(shù)據(jù)是",a,b)
return old_fun(a,b)
return new_fun
return decorator
#裝飾square_sum()
@pre_str(">_<")
def square_sum(a,b):
return a**2 + b**2
#裝飾square_dif()
@pre_str("^_^")
def square_dif(a,b):
return a**2 - b**2
print(square_sum(3,4))
print(square_dif(2,1))
結果:
>_< 輸入的數(shù)據(jù)是 3 4
25
^_^ 輸入的數(shù)據(jù)是 2 1
3
pre_str是一個帶參裝飾器代赁,對原來裝飾器進行封裝扰她,返回的是一個裝飾器。當我們使用@pre_str("_")時芭碍,Python能夠發(fā)現(xiàn)這一層的封裝徒役,并將參數(shù)傳入到裝飾器中,相當于
根據(jù)參數(shù)的不同窖壕,帶參裝飾器會對函數(shù)進行不同的加工忧勿,進一步提高裝飾器的適用范圍。
3.裝飾類
裝飾器還可以裝飾類瞻讽,一個裝飾器裝飾一個舊類狐蜕,并返回一個新類,起到了加工類的效果卸夕。
def decorator_Class(OldClass):
class NewClass(object):
def __init__(self,age):
self.total_display = 0
self.wrapped = OldClass(age) # 將舊類的對象進行打包
def display(self):
self.total_display += 1
print("展示次數(shù)",self.total_display)
self.wrapped.display() # 調(diào)用舊類對象的功能或數(shù)學
return NewClass # 最終返回一個新類
@decorator_Class
class Bird(object):
def __init__(self,age):
self.age = age
def display(self):
print("我的年齡是:",self.age,"歲")
eagle = Bird(5)
for i in range(3):
eagle.display()
在裝飾器decorator_class中,我們定義了新類婆瓜,在新類中的初始化方法中快集,用self.wrapped將舊類的對象進行打包贡羔,并附加了新屬性total_display,用于記錄展示次數(shù)个初。而且我們還同時更改了display方法乖寒。通過裝飾,舊類就可以顯示調(diào)用display()的次數(shù)院溺。7.4 高階函數(shù)
1.lambda與map函數(shù)
能接收其他函數(shù)作為參數(shù)的函數(shù)楣嘁,稱為"高階函數(shù)"。像上面介紹的裝飾器珍逸,也屬于高階函數(shù)逐虚。最具有代表性的高階函數(shù)有:map()、filter()谆膳、reduce()叭爱。
在講解高階函數(shù)之前,先講解匿名函數(shù)lambda漱病。lambda語法也可以用來定義函數(shù)买雾,只不過比較實用簡短語句的函數(shù)。如
lambda_sum = lambda x,y:x+y
print(lambda_sum(3,4))
通過lambda杨帽,我們創(chuàng)建了一個匿名函數(shù)對象漓穿,它的參數(shù)是x,y,返回值是x+y注盈,然后將它賦值給lambda_sum()晃危。lambda_sum()的使用與正常函數(shù)一樣。lambda定義的函數(shù)適用于簡短函數(shù)当凡。
高階函數(shù)從map()開始介紹山害,map()的第一個參數(shù)是函數(shù)對象,它把這一個函數(shù)對象作用于后面多個元素沿量。第二個元素之后都是循環(huán)對象浪慌。
data1 = [1,3,5,7]
res = map(lambda x:x+3,data1) # x : 4 7 8 10
map()的第一個參數(shù)是函數(shù)對象,第二個參數(shù)是可循環(huán)對象朴则。對于data1中的每個元素权纤,都會成為lambda函數(shù)的參數(shù),lambda函數(shù)都會調(diào)用一次乌妒。也就是說汹想,map()接收到的函數(shù)對象參數(shù)依次作用于每一個參數(shù)。map()會返回一個迭代器撤蚊。上面的代碼就相當于如下
def generator(fun,iterator):
for item in iterator:
yield fun(item)
data1 = [1,3,5,7]
res = generator(lambda x:x+3,data1)
lambda函數(shù)也可以是多個參數(shù)的古掏,如
def square_sum(x,y):
return x**2 + y**2
data1 = [1,3,5,7]
data2 = [2,4,6,8]
res = map(square_sum,data1,data2)
map()函數(shù)中第一個參數(shù)是函數(shù)對象,第二和第三個是可循環(huán)對象侦啸。第二個循環(huán)對象對應于square_sum中的x槽唾,第三個循環(huán)對象對應于square_sum中的y丧枪。2.filter函數(shù)
filter函數(shù)與map函數(shù)相似,但filter()只有兩個參數(shù)庞萍,第一個參數(shù)也是函數(shù)對象拧烦,第二個參數(shù)都是循環(huán)對象。如果函數(shù)對象返回的是True钝计,則該元素被放到迭代器中恋博,即filter()函數(shù)通過調(diào)用函數(shù)來篩選數(shù)據(jù)。如
def larage100(x):
if x > 100:
return True
else:
return False
for item in filter(larage100,[1,101,202,5]):
print(item)
結果:
101
202
import math
def is_Sqr(x):
return math.sqrt(x) % 1 == 0 # 判斷平方根是否為整數(shù)
newlist = filter(is_Sqr, range(1, 101))
for item in newlist:
print(item,end = " ")
結果:
1 4 9 16 25 36 49 64 81 100
filter()函數(shù)顧名思義私恬,就是一個過濾器债沮,更多的用于篩選出符合我們定義的函數(shù)對象里面的條件的數(shù)據(jù)。
3.reduce函數(shù)
reduce函數(shù)一共有三個參數(shù)践付,第一個是函數(shù)對象秦士,且這個函數(shù)對象能夠接收兩個參數(shù);第二個參數(shù)是序列永高;第三個參數(shù)是初始值隧土,無初始值就按照序列的第一個為初始值。
from functools import reduce
res1 = reduce(lambda x,y:x+y,[1,3,5,7,9])
print(res1)
res2 = reduce(lambda x,y:x+y,["x","y","z"],"a")
print(res2)
結果:
24
axyz
從第一個res1看出命爬,它接收兩個參數(shù)x和y曹傀,將列表中的元素進行累進運算,即(((1+3)+5)+7)=16饲宛;相似的res2也是如此皆愉。
reduce通過二元運算,將多個元素聚集成一個結果艇抠。map和reduce都是單線程的幕庐,運行效果和循環(huán)類似,但map和reduce可以方便的移植入并行化的運行環(huán)境下家淤。
在并行運算中异剥,reduce緊接著map運算,map將運算結果分布在多個主機上絮重,reduce運算把結果收集起來冤寿。谷歌用于并行運算的軟件架構叫MapReduce
4.并行處理
import time
from multiprocessing import Pool
import requests
# 定義一個運行時間的修飾器
def decorator_timer(oldFun):
def newFun(*arg,**dictarg):
start = time.perf_counter()
res = oldFun(*arg,**dictarg)
end = time.perf_counter()
print("運行時間為:",end - start)
return res
return newFun
# 訪問網(wǎng)頁任務
def download_once(i,addres="http://cnblogs.com"):
print("第",i+1,"次訪問完成")
r = requests.get(addres)
return r.status_code
# 單線程,一次處理一個
@decorator_timer
def single_thread(f,times):
print("我是單線程青伤,每次只執(zhí)行一次")
res = map(f,range(times))
return list(res)
# 多線程督怜,并行處理
@decorator_timer
def multiple_thread(f,times,process_num = 5):
print("我是多線程,任務同時完成")
p = Pool(process_num)
res = p.map(f,range(times))
return list(res)
if __name__ == "__main__":
TOTAL = 10
print(single_thread(download_once,TOTAL))
print()
print(multiple_thread(download_once,TOTAL))
結果:
我是單線程狠角,每次只執(zhí)行一次
第 1 次訪問完成
第 2 次訪問完成
第 3 次訪問完成
第 4 次訪問完成
第 5 次訪問完成
第 6 次訪問完成
第 7 次訪問完成
第 8 次訪問完成
第 9 次訪問完成
第 10 次訪問完成
運行時間為: 12.258385399999952
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
我是多線程号杠,任務同時完成
運行時間為: 2.3248039000000063
[200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
上面的download_once是單次的訪問網(wǎng)頁任務,TOTAL是訪問次數(shù)需求丰歌,一共是10次究流。
單線程一次一次的進行辣吃,最終獲取了10個狀態(tài)碼;
而多線程開辟了5個進程芬探,10次任務同時進行,把10個任務分配給了5個工人厘惦。從運行時間可以明顯看出偷仿,多線程的運算更加快速。
7.5 自上而下
1.便捷式表達
Python中有幾種體現(xiàn)自上而下思維的語法宵蕉,如生成器表達式酝静、列表解析與詞典解析。
-
生成器表達式
生成器表達式是構建生成器的便捷表達式羡玛。假設我們要構建一個生成器
def gen():
for i in range(4):
yield i
上面代碼用生成器表達式可以改寫為
gen = (x for x in range(4))
-
列表解析(列表生成式)
若我們想要一個0-9每個數(shù)的平方的列表别智,可以這樣生成
li = []
for x in range(10):
li.append(x**2)
print(li)
結果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
更加便捷地,我們可以使用列表解析稼稿,或者說列表生成式
li = [x**2 for x in range(10)]
print(li)
結果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
列表生成式的語法很直觀薄榛,首先說明我們需要的是什么樣的元素,再通過for加入限定條件让歼,即哪些元素敞恋。再如生成3個隨機數(shù)列表,每個列表包含10個1到100內(nèi)的隨機整數(shù)
import random as r
ran_li1 = [r.randint(1,100) for i in range(10)]
ran_li2 = [r.randint(1,100) for i in range(10)]
ran_li3 = [r.randint(1,100) for i in range(10)]
print(ran_li1)
print(ran_li2)
print(ran_li3)
結果:
[47, 10, 21, 26, 37, 62, 28, 57, 40, 77]
[3, 7, 56, 70, 9, 17, 1, 60, 46, 96]
[59, 98, 87, 79, 55, 52, 37, 31, 84, 39]
我們還可以在列表解析里面加入if語法
x1 = [1,3,5,7]
y1 = [2,4,6,8]
li = [x**2 for x,y in zip(x1,y1) if y > 5]
# 當y>5時谋右,生成對應的x的平方硬猫,再保存入列表
print(li)
結果:
[25, 49]
zip()是一個將序列打包的方法,而for i in zip()則可以看成得到序列中的每個元素改执,即解壓啸蜜。
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
for i in zip(a,b,c):
print(i)
結果:
(1, 4, 4)
(2, 5, 5)
(3, 6, 6)
--------------------------------------------------
x1 = [1,3,5,7]
y1 = [2,4,6,8,10]
z1 = ["a","b","c","d","e"]
for x,y,z in zip(x1,y1,z1):
print(x,y,z)
結果:
1 2 a
3 4 b
5 6 c
7 8 d
-
詞典解析(詞典生成式)
類似的,詞典也可以快捷生成辈挂。與列表解析的語法類似衬横。
d1 = {key:val for key,val in enumerate("HelloWorld")}
print(d1)
d2 = {k:v for k,v in enumerate("Python") if v in "Py"}
print(d2)
"""
enumerate是枚舉,包括索引和元素
"""
結果:
{0: 'H', 1: 'e', 2: 'l', 3: 'l', 4: 'o', 5: 'W', 6: 'o', 7: 'r', 8: 'l', 9: 'd'}
{0: 'P', 1: 'y'}
2.懶惰求值
迭代器有時候看起來像列表呢岗,但實際上迭代器的元素是實時運算出來的冕香,在使用元素之前,是不會占據(jù)空間后豫;而列表在建立時悉尾,就先產(chǎn)生了元素,并保存在空間內(nèi)挫酿。
迭代器的工作方式就是懶惰求值构眯,即需要的時候再使用元素,才會計算具體的值早龟。
import time
start1 = time.perf_counter()
for i in (x**2 for x in range(100)):
print(i)
end1 = time.perf_counter()
print("迭代器運行時間:",end1-start1)
結果:
0
1
4
9
……
迭代器運行時間: 0.004747199999656004
import time
start2 = time.perf_counter()
for i in [x**2 for x in range(100)]:
print(i)
end2 = time.perf_counter()
print("列表運行時間:",end2 - start2)
結果:
0
1
4
9
……
列表運行時間: 0.008195000000341679
通過上面的對比可以看出惫霸,雖然生成的結果相同猫缭,但是迭代器的運行時間小于使用列表運行的時間。這是因為建立列表需要先計算產(chǎn)生元素壹店,再保存生成入列表猜丹,再使用列表;而迭代器卻是"懶惰求值"硅卢。
map和range函數(shù)返回的都是迭代器射窒,它們所做的也是懶惰求值,需要的時候再使用将塑;但是若將它們轉(zhuǎn)化為列表脉顿,時間將會大大增加、如
#未轉(zhuǎn)化為列表之前
import time as t
start = t.perf_counter()
ite = range(100)
res = map(lambda x:x**2,ite)
print(res) # 返回的是一個迭代器
end = t.perf_counter()
print("運行時間為:",end-start)
結果:
<map object at 0x0000018149C5B8D0>
運行時間為: 0.00012900000001536682
轉(zhuǎn)化為列表
import time as t
start = t.perf_counter()
ite = range(1000)
res = map(lambda x:x**2,ite)
print(list(res))
end = t.perf_counter()
print("運行時間為:",end-start)
結果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]
運行時間為: 0.00015100000018719584
可見100次時点寥,生成為迭代器的時間只需要0.000129艾疟,而轉(zhuǎn)化為列表需要0.000151,雖然相差不大敢辩,但是已經(jīng)生成為迭代器的時間已經(jīng)小于生成為列表的時間蔽莱。如果將100設置為1000,相差的時間更大责鳍。如
1000次時
生成為迭代器的時間
運行時間為: 0.00013020000005781185
生成為列表的時間
運行時間為: 0.0005742000003010617
可以看出碾褂,將map生成的結果轉(zhuǎn)化為列表,時間將大大增加历葛。
再者正塌,如果我們不需要窮盡所有數(shù)據(jù)元素,那么懶惰求值將節(jié)省很多時間恤溶,而列表生成式的方式乓诽,提前準備的數(shù)據(jù)就會造成極大浪費。如
import time as t
start1 = t.perf_counter()
for i in (x**2 for x in range(100000)):
if i > 500:
break
end1 = t.perf_counter()
print("迭代器運行時間:",end1-start1)
start2 = t.perf_counter()
for i in [x**2 for x in range(100000)]:
if i > 500:
break
end2 = t.perf_counter()
print("列表運行時間",end2-start2)
結果:
迭代器運行時間: 1.700000029813964e-05
列表運行時間 0.04208400000061374
迭代器的運行時間為1.7*10-5咒程,而列表運行時間是0.0420鸠天,可見 迭代器更快。也可以看出帐姻,除了運行更快外稠集,懶惰求值還可以節(jié)省內(nèi)存空間。
除了map饥瓷、filter函數(shù)剥纷,Python中itertools包提供豐富的操作迭代器的工具。
3.itertools包
首先導入itertools包
from itertools import *
- count()和cycle()可以生成無限循環(huán)的迭代器呢铆。
count(5,2):從5開始每次增加2晦鞋,無限循環(huán)
# count(5,2):從5開始每次增加2,無限循環(huán)
for i in count(5,2):
print(i)
if i > 15: # 到15結束,不然無限循環(huán)了
break
結果:
5
7
9
11
13
15
17
cycle(list|str|tuple|dict):不斷重復序列中的元素
# cycle(list|str|tuple|dict):不斷重復序列中的元素
cut1 = 0
for i in cycle([1,3,"ab"]):
cut1 += 1
print(i)
if cut1 == 7: # 不斷循環(huán)悠垛,直到第7次時跳出
break
結果:
1
3
ab
1
3
ab
1
- repeat():返回一個不斷重復元素的迭代器线定,也可以有次數(shù)限制的重復。
# repeat():不斷重復元素确买,也可以有次數(shù)限制
cut2 = 0
for i in repeat(5):
cut2 += 1
print(i)
if cut2 == 7:# 不斷循環(huán)斤讥,直到第7次時跳出
break
for i in repeat([1,"a",1.2],4): # 重復4次該列表
print(i)
結果:
5
5
5
5
5
5
5
[1, 'a', 1.2]
[1, 'a', 1.2]
[1, 'a', 1.2]
[1, 'a', 1.2]
- 組合舊迭代器,生成新迭代器
chain():連接兩個及以上的迭代器
# chain():連接兩個及以上的迭代器
for i in chain([1,3,5,7],[2,4,6,8]):
print(i)
結果:
1
3
5
7
2
4
6
8
product():返回多個迭代器的笛卡爾積,即得到元素的所有可能的組合方式拇惋,相當于嵌套循環(huán)周偎。
# product():返回多個迭代器的笛卡爾積,
# 即得到元素的所有可能的組合方式,相當于嵌套循環(huán)
for i in product("abc",[1,3]): # 以元組來接收
print(i)
for x,y in product("abc",[1,3]):# 以每個元素來接收
print(x,y)
結果:
('a', 1)
('a', 3)
('b', 1)
('b', 3)
('c', 1)
('c', 3)
a 1
a 3
b 1
b 3
c 1
c 3
permutations("abc",2):從"abc"中挑2個元素撑帖,將所有結果排序,返回新的迭代器澳眷,且組合區(qū)分順序胡嘿。
'''
permutations("abc",2)
從"abc"中挑2個元素,將所有結果排序钳踊,
返回新的迭代器衷敌,且組合區(qū)分順序
'''
for i in permutations("abc",2):
print(i)
結果:
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
combinations("abcd",3):從"abcd"中挑3個元素進行組合,將所有結果排序拓瞪,返回新迭代器且組合不區(qū)分順序缴罗,即ab和ba都是ab
'''
combinations("abcd",3)
從"abcd"中挑3個元素進行組合,
將所有結果排序祭埂,返回新迭代器
且組合不區(qū)分順序面氓,即ab和ba都是ab
'''
for i in combinations("abcd",3):
print(i)
結果:
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'd')
('b', 'c', 'd')
combinations_with_replacement("abcd",3):與上面類似,但是允許出現(xiàn)重復的元素,如a,a,a蛆橡。
'''
combinations_with_replacement("abcd",3)
與上面類似舌界,但是允許出現(xiàn)重復的元素,如a,a,a
'''
for i in combinations_with_replacement("abcd",3):
print(i)
結果:
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'a', 'c')
('a', 'a', 'd')
('a', 'b', 'b')
('a', 'b', 'c')
('a', 'b', 'd')
('a', 'c', 'c')
('a', 'c', 'd')
('a', 'd', 'd')
('b', 'b', 'b')
('b', 'b', 'c')
('b', 'b', 'd')
('b', 'c', 'c')
('b', 'c', 'd')
('b', 'd', 'd')
('c', 'c', 'c')
('c', 'c', 'd')
('c', 'd', 'd')
('d', 'd', 'd')
- starmap(pow,[[1,1],(2,2),(3,3)]):將pow作用于列表中的每個序列
'''
starmap(pow,[[1,1],(2,2),(3,3)])
將pow作用于列表中的每個序列
'''
for i in starmap(pow,[[1,1],(2,2),(3,3)]):
print(i)
結果:
1
4
27
- takewhile(lambda x:x>2,[1,2,5,6]):只有兩個參數(shù)。當函數(shù)返回True時泰演,收集元素到迭代器呻拌。一旦返回False,就停止睦焕。
'''
takewhile(lambda x:x>2,[1,2,5,6]),只有兩個參數(shù)
當函數(shù)返回True時藐握,收集元素到迭代器。一旦返回False垃喊,就停止猾普。
'''
for i in takewhile(lambda x:x<6,[1,2,5,6]):
print(i)
結果:
1
2
5
dropwhile(lambda x:x<3,[1,2,3,2,1,8]):當函數(shù)返回False時汉额,跳過并記錄元素愕秫。一旦返回True,則開始收集剩下的所有元素到迭代器炸裆。
'''
dropwhile(lambda x:x<3,[1,2,3,2,1,8])
當函數(shù)返回False時荡灾,跳過并記錄元素
一旦返回True,則開始收集剩下的所有元素到迭代器固惯。
'''
for i in dropwhile(lambda x:x<3,[1,2,3,2,1,8]):
print(i)
結果:
3
2
1
8
groupby():能將一個key函數(shù)作用于原迭代器的各個元素,從而獲得各個函數(shù)的鍵值需频。根據(jù)key()函數(shù)的結果漓帅,來對原來迭代器的元素進行分組。
'''
groupby():
能將一個key函數(shù)作用于原迭代器的各個元素炕泳,從而獲得各個函數(shù)的鍵值纵诞。
根據(jù)key()函數(shù)的結果,來對原來迭代器的元素進行分組培遵。
'''
def height_keyFun(height):
if height > 180:
return "Tall"
elif height < 160:
return "Short"
else:
return "Middle"
People = [191,160,158,172,185,157,179]
People = sorted(People,key = height_keyFun) # 按照height_keyFun進行排序浙芙,
# 讓同組元素先在位置上靠攏。
print(People)
for x,y in groupby(People,key = height_keyFun):
print(x) # x是鍵
print(list(y)) # y是一個迭代器籽腕,相對應于鍵的值嗡呼,轉(zhuǎn)化為列表才可以顯示
結果:
[160, 172, 179, 158, 157, 191, 185]
Middle
[160, 172, 179]
Short
[158, 157]
Tall
[191, 185]
- 方便迭代器構建的工具
compress(["a",1,9,7],[1,0,0,1]):根據(jù)真假情況,選擇保留序列中的元素皇耗。True保存,False不保存
'''
compress(["a",1,9,7],[1,0,0,1])
根據(jù)真假情況南窗,選擇保留序列中的元素。True保存,False不保存
'''
for i in compress(["a",1,9,7],[1,0,0,1]):
print(i)
結果:
a
7
islice()與slice相似郎楼,只不過返回的是一個迭代器
'''
slice(start,stop,step) 切片器
start -- 起始位置
stop -- 結束位置
step -- 步長
islice()與slice相似万伤,只不過返回的是一個迭代器
'''
li = [1,2,3,"a","z"]
print("切片slice1",li[slice(1,4,1)])
print("切片slice2",li[slice(3)])
print("我是slice,返回的是函數(shù)對象",slice(3))
print("我是islice,返回的是迭代器",islice(li,4))
for i in islice(li,5):
print(i,end = " ")
結果:
切片slice1 [2, 3, 'a']
切片slice2 [1, 2, 3]
我是slice,返回的是函數(shù)對象 slice(None, 3, None)
我是islice呜袁,返回的是迭代器 <itertools.islice object at 0x000001814A824958>
1 2 3 a z
zip_longest()與zip()相似敌买,但是返回的是一個迭代器,且按照的是最長的序列來組合的
'''
zip()在上述中講過,是按照最短序列來組合的
zip_longest()與其相似阶界,但是返回的是一個迭代器,且按照的是最長的序列來組合的
'''
a = [1,"abc",8,"x","yz"]
b = [9,"ok","Python",777]
print("我是zip(),返回的是一個函數(shù)對象虹钮,是按照最短序列來組合的",zip(a,b))
for i in zip(a,b):
print(i)
print()
print("我是zip_longest(),返回的是一個迭代器,\
且按照的是最長的序列來組合的,沒有就給None",zip_longest(a,b))
for i in zip_longest(a,b):
print(i)
結果:
我是zip(),返回的是一個函數(shù)對象荐操,是按照最短序列來組合的
<zip object at 0x0000018149C7D9C8>
(1, 9)
('abc', 'ok')
(8, 'Python')
('x', 777)
我是zip_longest()芜抒,返回的是一個迭代器,且按照的是最長的序列來組合的,沒有就給None
<itertools.zip_longest object at 0x000001814A824958>
(1, 9)
('abc', 'ok')
(8, 'Python')
('x', 777)
('yz', None)
以上是itertools包的各種功能。