迭代
1.1 迭代的概念
使用for循環遍歷取值的過程叫做迭代,比如:使用for循環遍歷列表獲取值的過程
for value in [2, 3, 4]:
print(value)
1.2 可迭代對象
標準概念:在類里面定義
__iter__
方法,并使用該類創建的對象就是可迭代對象簡單記憶:使用for循環遍歷取值的對象叫做可迭代對象, 比如:列表、元組、字典、集合、range、字符串
1.3 判斷對象是否是可迭代對象
from collections import Iterable
result = isinstance((3, 5), Iterable)
print("元組是否是可迭代對象:", result)
result = isinstance([3, 5], Iterable)
print("列表是否是可迭代對象:", result)
result = isinstance({"name": "張三"}, Iterable)
print("字典是否是可迭代對象:", result)
result = isinstance("hello", Iterable)
print("字符串是否是可迭代對象:", result)
result = isinstance({3, 5}, Iterable)
print("集合是否是可迭代對象:", result)
result = isinstance(range(5), Iterable)
print("range是否是可迭代對象:", result)
result = isinstance(5, Iterable)
print("整數是否是可迭代對象:", result)
result = isinstance(5, int)
print("整數是否是int類型對象:", result)
class Student(object):
passstu = Studentresult = isinstance(stu, Iterable)print("stu是否是可迭代對象:", result)
result = isinstance(stu, Student)print("stu是否是Student類型的對象:", result)
1.4 自定義可迭代對象
在類中實現
__iter__
方法
自定義可迭代類型代碼
from collections import Iterable
class MyList(object):def __init__(self):
self.my_list = listdef Append_item(self, item):self.my_list.append(item)def __iter__(self):
passmy_list = MyListmy_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)print(result)for value in my_list:
print(value)
執行結果:
Traceback (most recent call last):
TrueFile "/Users/hbin/Desktop/untitled/aa.py", line 24, in <module>
for value in my_list:
TypeError: iter returned non-iterator of type 'N.NEType'
通過執行結果可以看出來,遍歷可迭代對象依次獲取數據需要迭代器
總結
在類里面提供一個__iter__
創建的對象是可迭代對象,可迭代對象是需要迭代器完成數據迭代的
2、迭代器
2.1 自定義迭代器對象
自定義迭代器對象: 在類里面定義__iter__
和__next__
方法創建的對象就是迭代器對象
from collections import Iterable
from collections import Iteratorclass MyList(object):
def __init__(self):
self.my_list = list
def append_item(self, item):
self.my_list.append(item)
def __iter__(self):
my_iterator = MyIterator(self.my_list)
return my_iterator
class MyIterator(object):
def __init__(self, my_list):
self.my_list = my_list
self.current_index = 0result = isinstance(self, Iterator)
print("MyIterator創建的對象是否是迭代器:", result)
def __iter__(self):
return self
def __next__(self):
if self.current_index < len(self.my_list):
self.current_index += 1
return self.my_list[self.current_index - 1]
else:
raise StopIterationmy_list = MyListmy_list.append_item(1)
my_list.append_item(2)
result = isinstance(my_list, Iterable)print(result)for value in my_list:
print(value)
運行結果:
True
MyIterator創建的對象是否是迭代器: True
12
2.2 iter函數與next函數
iter函數
: 獲取可迭代對象的迭代器,會調用可迭代對象身上的__iter__
方法
next函數
: 獲取迭代器中下一個值,會調用迭代器對象身上的__next__
方法
class MyList(object):
def __init__(self):
self.my_list = listdef append_item(self, item):self.my_list.append(item)def __iter__(self):
my_iterator = MyIterator(self.my_list)return my_iterator
class MyIterator(object):def __init__(self, my_list):
self.my_list = my_listself.current_index = 0
def __iter__(self):
return self
def __next__(self):if self.current_index < len(self.my_list):
self.current_index += 1
return self.my_list[self.current_index - 1]
else:
raise StopIteration
my_list = MyListmy_list.append_item(1)
my_list.append_item(2)
my_iterator = iter(my_list)print(my_iterator)while True:
try:
value = next(my_iterator)print(value)except StopIteration as e:
break
2.3 for循環的本質
遍歷的是可迭代對象
for item in Iterable 循環的本質就是先通過iter函數獲取可迭代對象Iterable的迭代器,然后對獲取到的迭代器不斷調用next方法來獲取下一個值并將其賦值給item,當遇到StopIteration的異常后循環結束。
遍歷的是迭代器
for item in Iterator 循環的迭代器,不斷調用next方法來獲取下一個值并將其賦值給item,當遇到StopIteration的異常后循環結束。
2.4 迭代器的應用場景
我們發現迭代器最核心的功能就是可以通過next函數的調用來返回下一個數據值。如果每次返回的數據值不是在一個已有的數據集合中讀取的,而是通過程序按照一定的規律計算生成的,那么也就意味著可以不用再依賴一個已有的數據集合,也就是說不用再將所有要迭代的數據都一次性緩存下來供后續依次讀取,這樣可以節省大量的存儲(內存)空間。
舉個例子,比如,數學中有個著名的斐波拉契數列(Fibonacci),數列中第一個數為0,第二個數為1,其后的每一個數都可由前兩個數相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
現在我們想要通過for...in...循環來遍歷迭代斐波那契數列中的前n個數。那么這個斐波那契數列我們就可以用迭代器來實現,每次迭代都通過數學計算來生成下一個數。
class Fibonacci(object):
def __init__(self, num):
self.num = num
self.a = 0
self.b = 1
self.current_index = 0
def __iter__(self):
return self
def __next__(self):
if self.current_index < self.num:
result = self.a
self.a, self.b = self.b, self.a + self.b
self.current_index += 1
return result
else:raise StopIterationfib = Fibonacci(5)
for value in fib:
print(value)
執行結果:
0
1
1
2
3
小結
迭代器的作用就是是記錄當前數據的位置以便獲取下一個位置的值
3、生成器
3.1 生成器的概念
生成器是一類特殊的迭代器,它不需要再像上面的類一樣寫__iter__和__next__
方法了, 使用更加方便,它依然可以使用next函數和for循環取值
3.2 創建生成器方法1
第一種方法很簡單,只要把一個列表生成式的 改成
my_list = [i * 2 for i in range(5)]
print(my_list)
my_generator = (i * 2 for i in range(5))
print(my_generator)
for value in my_generator:
print(value)
執行結果:
[0, 2, 4, 6, 8]
<generator object <genexpr> at 0x101367048>
0
2
4
6
8
3.3 創建生成器方法2
在def函數里面看到有yield關鍵字那么就是生成器
def fibonacci(num):
a = 0
b = 1
current_index = 0
print("--11---")
while current_index < num:
result = aa, b = b, a + bcurrent_index += 1
print("--22---")
yield result
print("--33---")
fib = fibonacci(5)
value = next(fib)
print(value)
value = next(fib)
print(value)
value = next(fib)
print(value)
在使用生成器實現的方式中,我們將原本在迭代器__next__
方法中實現的基本邏輯放到一個函數中來實現,但是將每次迭代返回數值的return換成了yield,此時新定義的函數便不再是函數,而是一個生成器了。
簡單來說:只要在def中有yield關鍵字的 就稱為 生成器
3.4 生成器使用return關鍵字
def fibonacci(num):
a = 0
b = 1
current_index = 0
print("--11---")
while current_index < num:
result = aa, b = b, a + bcurrent_index += 1
print("--22---")
yield result
print("--33---")
return "嘻嘻"
fib = fibonacci(5)
value = next(fib)print(value)try:
value = next(fib)print(value)except StopIteration as e:
print(e.value)
提示:
生成器里面使用return關鍵字語法上沒有問題,但是代碼執行到return語句會停止迭代,拋出停止迭代異常。
3.5 yield和return的對比
使用了yield關鍵字的函數不再是函數,而是生成器。(使用了yield的函數就是生成器)
代碼執行到yield會暫停,然后把結果返回出去,下次啟動生成器會在暫停的位置繼續往下執行
每次啟動生成器都會返回一個值,多次啟動可以返回多個值,也就是yield可以返回多個值
return只能返回一次值,代碼執行到return語句就停止迭代,拋出停止迭代異常
3.6 使用send方法啟動生成器并傳參
send方法啟動生成器的時候可以傳參數
def gen:
i = 0
while i<5:
temp = yield i
print(temp)i+=1
執行結果:
In [43]: f = gen
In [44]: next(f)
Out[44]: 0
In [45]: f.send('haha')
hahaOut[45]: 1
In [46]: next(f)
NoneOut[46]: 2
In [47]: f.send('haha')
hahaOut[47]: 3
In [48]:
注意: 如果第一次啟動生成器使用send方法,那么參數只能傳入None,一般第一次啟動生成器使用next函數
小結
-
生成器創建有兩種方式,一般都使用yield關鍵字方法創建生成器
-
yield特點是代碼執行到yield會暫停,把結果返回出去,再次啟動生成器在暫停的位置繼續往下執行
4、協程
4.1 協程的概念
協程,又稱微線程,纖程,也稱為用戶級線程,在不開辟線程的基礎上完成多任務,也就是在單線程的情況下完成多任務,多個任務按照一定順序交替執行 通俗理解只要在def里面只看到一個yield關鍵字表示就是協程
協程是也是實現多任務的一種方式
協程yield的代碼實現
簡單實現協程
import time
def work1:while True:
print("----work1---")
yieldtime.sleep(0.5)
def work2:
while True:
print("----work2---")
yieldtime.sleep(0.5)
def main:
w1 = work1w2 = work2while True:
next(w1)next(w2)if __name__ == "__main__":
main
運行結果:
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
...省略...
小結
協程之間執行任務按照一定順序交替執行
5、greenlet
5.1 greentlet的介紹
為了更好使用協程來完成多任務,Python/ target=_blank class=infotextkey>Python中的greenlet模塊對其封裝,從而使得切換任務變的更加簡單
使用如下命令安裝greenlet模塊:
pip3 install greenlet
使用協程完成多任務
import time
import greenlet# 任務1
def work1:for i in range(5):
print("work1...")
time.sleep(0.2)
# 切換到協程2里面執行對應的任務
g2.switch
# 任務2
def work2:for i in range(5):
print("work2...")
time.sleep(0.2)
# 切換到第一個協程執行對應的任務g1.switchif __name__ == '__main__':
# 創建協程指定對應的任務g1 = greenlet.greenlet(work1)g2 = greenlet.greenlet(work2)# 切換到第一個協程執行對應的任務g1.switch
運行效果
work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...
work1...
work2...
6、gevent
6.1 gevent的介紹
greenlet已經實現了協程,但是這個還要人工切換,這里介紹一個比greenlet更強大而且能夠自動切換任務的第三方庫,那就是gevent。
gevent內部封裝的greenlet,其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網絡、文件操作等)操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。
由于IO操作非常耗時,經常使程序處于等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO
安裝
pip3 install gevent
6.2 gevent的使用
import gevent
def work(n):for i in range(n):
print(gevent.getcurrent, i)
g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.joing2.joing3.join
運行結果
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 0
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 0
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 0
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 1
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 1
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 1
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 2
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 2
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 2
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 3
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 3
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 3
<Greenlet "Greenlet-0" at 0x26d8c970488: work(5)> 4
<Greenlet "Greenlet-1" at 0x26d8c970598: work(5)> 4
<Greenlet "Greenlet-2" at 0x26d8c9706a8: work(5)> 4
可以看到,3個greenlet是依次運行而不是交替運行
6.3 gevent切換執行
import gevent
def work(n):for i in range(n):
print(gevent.getcurrent, i)
gevent.sleep(1)
g1 = gevent.spawn(work, 5)
g2 = gevent.spawn(work, 5)
g3 = gevent.spawn(work, 5)
g1.joing2.joing3.join
運行結果
<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4
6.4 給程序打補丁
import gevent
import timefrom gevent import monkeymonkey.patch_alldef work1(num):
for i in range(num):
print("work1....")
time.sleep(0.2)
def work2(num):
for i in range(num):
print("work2....")
time.sleep(0.2)
if __name__ == '__main__':
g1 = gevent.spawn(work1, 3)
g2 = gevent.spawn(work2, 3)
g1.joing2.join
運行結果
work1....
work2....
work1....
work2....
work1....
work2....
6.5 注意
當前程序是一個死循環并且還能有耗時操作,就不需要加上join方法了,因為程序需要一直運行不會退出
示例代碼
import gevent
import timefrom gevent import monkey
monkey.patch_alldef work1(num):for i in range(num):
print("work1....")
time.sleep(0.2)
def work2(num):
for i in range(num):
print("work2....")
time.sleep(0.2)
if __name__ == '__main__':
g1 = gevent.spawn(work1, 3)
g2 = gevent.spawn(work2, 3)
while True:
print("主線程中執行")
time.sleep(0.5)
執行結果:
主線程中執行
work1....
work2....
work1....
work2....
work1....
work2....
主線程中執行主線程中執行主線程中執行..省略..
如果使用的協程過多,如果想啟動它們就需要一個一個的去使用join方法去阻塞主線程,這樣代碼會過于冗余,可以使用gevent.joinall方法啟動需要使用的協程
實例代碼
import time
import geventdef work1:
for i in range(5):
print("work1工作了{}".format(i))
gevent.sleep(1)
def work2:
for i in range(5):
print("work2工作了{}".format(i))
gevent.sleep(1)
if __name__ == '__main__':
w1 = gevent.spawn(work1)
w2 = gevent.spawn(work2)gevent.joinall([w1, w2])
7、進程、線程、協程對比
7.1 進程、線程、協程之間的關系
-
一個進程至少有一個線程,進程里面可以有多個線程
-
一個線程里面可以有多個協程
7.2 進程、線程、線程的對比
-
進程是資源分配的單位
-
線程是操作系統調度的單位
-
進程切換需要的資源最大,效率很低
-
線程切換需要的資源一般,效率一般(當然了在不考慮GIL的情況下)
-
協程切換任務資源很小,效率高
-
多進程、多線程根據cpu核數不一樣可能是并行的,但是協程是在一個線程中 所以是并發
小結
1. 進程、線程、協程都是可以完成多任務的,可以根據自己實際開發的需要選擇使用
2. 由于線程、協程需要的資源很少,所以使用線程和協程的幾率最大
3. 開辟協程需要的資源最少
作者:y大壯
來源:
https://juejin.cn/post/6970520987011907615僅做學術分享,若有侵權,請聯系刪除