列表推導式
產生背景
現在有個需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表里的每個值加1,你怎么實現?
第一種方法:
a = [1,3,4,6,7,7,8,9,11] for ind, val in enumerate(a): a[ind] += 1 print(a)
第二種方法:
a = [1,3,4,6,7,7,8,9,11] print(list(map(lambda x: x+1, a)))
列表推導式:
a = [1,3,4,6,7,7,8,9,11] print([i+1 for i in a])
使用列表推導式可簡化代碼。用法如下
例一:30以內所有能被3整除的數
print([i for i in list(range(31)) if i % 3 == 0]) #使用if 表達式
例二:30以內所有能被3整除的數的變為平方,否則乘以2
# 使用三元表達式 print([i*i if i % 3 == 0 else i*2 for i in list(range(31))])
練習題:
""" 例1: 過濾掉長度小於3的字符串列表,並將剩下的轉換成大寫字母 例2: 求(x,y)其中x是0-5之間的偶數,y是0-5之間的奇數組成的元祖列表 例3: 求M中3,6,9組成的列表M = [[1,2,3],[4,5,6],[7,8,9]] """ str_li = ['abc', 'cd', 'xy'] print([x.upper() for x in str_li if len(x) >= 3]) print([(x, y) for x in list(range(6)) if x % 2 == 0 for y in list(range(6)) if y % 2 == 1]) M = [[1,2,3],[4,5,6],[7,8,9]] print([row[2] for row in M])
字典推導式
例一:將一個字典的key和value對調
# mcase = {'a': 10, 'b': 34} print({mcase[k]: k for k in mcase})
生成器
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,
如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。
所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?
這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。
要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]
改成(),
就創建了一個generator:
生成器的創建方式
1. 類似於列表生成式()
2. 函數中使用yield
函數有了yield之后
1. 函數調用之后就得到了一個生成器,
2. return 在生成器里,代表生成器的中止,直接報錯
3. yield 返回數據 ,並凍結當前的執行過程 。。
類似於列表生成式()創建
L = [x * x for x in range(10)] gen_L = (x * x for x in range(10)) # 生成器存放計算公式 print(L) print(gen_L) print(next(gen_L)) # 取值 print(next(gen_L)) """ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] <generator object <genexpr> at 0x0000000001DFDF10> generator 就是生成器的意思 0 1 """
generator保存的是算法,每次調用next(g)
就計算出g
的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration
的錯誤,使用for循環可以解決異常
g = (x * x for x in range(10)) for n in g: print(n)
generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現。
def fib(max): n, a, b = 0, 0, 1 while n < max: print('before yield') yield b # 把函數的執行過程凍結在這一步,並且把b的值 返回給外面的next() print(b) a, b = b, a + b n = n + 1 return 'done' f = fib(15) # turn function into a generator next(f) # first time call next() next 喚醒凍結的函數執行過程,繼續執行,直到遇到下一個yield next(f) # first time call next() """ before yield 1 before yield """
注意a, b = b, a + b 相當於
a, b = 0, 1 a, b = b, a + b print(a, b) a, b = 0, 1 t = a a = b b = t + b print(a, b) """ 1 1 1 1 """
函數方式創建
def range2(n): count = 0 while count < n : print(count) stop_flag = yield count #中斷並返回 return count if stop_flag == 'stop': print('stop iteration...') break count += 1 b = range2(5) # 得到生成器 next(b) next(b) """ 0 Traceback (most recent call last): File "C:/Users/jingjing/PycharmProjects/py3Project/路飛/第二模塊/函數練習/生成器.py", line 53, in <module> next(b) StopIteration: 0 """
生成器常用方法
next() # 喚醒生成器並繼續執行 send("stop") """ 1. 喚醒並繼續執行 2. 發送一個信息到生成器內部 注意生成器在剛開始,函數沒有執行到yield成為掛起狀態時,不能調用send("stop") 只能send('None')相當於next()使函數執行到yield """
send使用舉例
def range2(n): count = 0 while count < n: print('count', count) count += 1 sign = yield count # return if sign == 'stop': print("---sign", sign) break print('sin...', sign) return 3333 new_range = range2(3) next(new_range) new_range.send(None) new_range.send("stop") """ count 0 sin... None count 1 ---sign stop Traceback (most recent call last): File "C:/Users/jingjing/PycharmProjects/py3Project/路飛/第二模塊/函數練習/函數生成器.py", line 22, in <module> new_range.send("stop") StopIteration: 3333 """
生產者與消費者問題
import time def consume(name): print("%s 准備吃包子啦!" % name) while True: y = yield print("包子[%s]來了,被[%s]吃了!" % (y, name)) def producer(name): c = consume(name) next(c) # 函數執行到yield for i in range(1, 3): time.sleep(1) print("做了{}個包子!".format(i)) c.send(i) # 把i傳到yield producer('qian')
""" qian 准備吃包子啦! 做了1個包子! 包子[1]來了,被[qian]吃了! 做了2個包子! 包子[2]來了,被[qian]吃了! """
日志記錄
def logger(filename): """ 日志方法 :param filename: log filename :param channel: 輸出的目的地,屏幕(terminal),文件(file),屏幕+文件(both) :return: """ print('start logger') while True: msg = yield print("msg", msg) l = logger('USER.TXT') l.__next__() l.send('hi') l.send('hi,file')
計算移動平均值
# 必須先用next再用send def average(): total=0 #總數 day=0 #天數 average=0 #平均數 while True: day_num = yield average #average=0 print('average', average) total += day_num day += 1 average = total/day avg=average() #直接返回生成器 next(avg)#激活生成器,avg.send(None),什么都不傳的時候send和next的效果一樣 print(avg.send(10)) print(avg.send(20))#send 1.傳值 2.next print(avg.send(30)) """ average 0 10.0 average 10.0 15.0 average 15.0 20.0 """
帶裝飾器的計算移動平均值
# 讓裝飾器去激活 def wrapper(func): def inner(*args, **kwargs): print('execute wrapper') a = func(*args, **kwargs) next(a) return a return inner @wrapper def average(): total=0 #總數 day=0 #天數 average=0 #平均數 while True: day_num = yield average #average=0 print('average', average) total += day_num day += 1 average = total/day avg = average() print(avg.send(10)) print(avg.send(20))#send 1.傳值 2.next print(avg.send(30)) """ execute wrapper average 0 10.0 average 10.0 15.0 average 15.0 20.0 """
yield from
def gen1(): for c in 'AB': yield c for i in range(3): yield i print(gen1()) print(list(gen1())) def gen2(): yield from 'AB' # 相當於 for c in 'AB': yield c yield from range(3) print(gen2()) print(list(gen2()))
處理異常
def fib(max): n, a, b = 0, 0, 1 while n < max: print('before yield') yield b # 把函數的執行過程凍結在這一步,並且把b的值 返回給外面的next() a, b = b, a + b n = n + 1 return 'done' g = fib(6) while True: try: x = next(g) print('g:', x) except StopIteration as e: print('Generator return value:', e.value) break
生成器小結
生成器的創建方式
1. 類似於列表生成式()
2. 函數中使用yield
優點:節省內存空間
函數有了yield之后
1. 函數調用之后就得到了一個生成器,
2. return 在生成器里,代表生成器的中止,直接報錯
注意
python3 range相當於python2 xrange 生成器
python2 range相當於list
yield vs return
return 返回並中止function
yield 返回數據 ,並凍結當前的執行過程 。。
生成器常用方法
1.next() 2.send("stop") """ 1. 喚醒並繼續執行 2. 發送一個信息到生成器內部 注意生成器在剛開始,函數沒有執行到yield成為掛起狀態時,不能調用send("stop") 只能send('None')相當於next()使函數執行到yield """
迭代器
可迭代對象與迭代器
迭代:可以將某個數據集內的數據“一個挨着一個的取出來”,就叫做迭代。就像for循環一樣取值。
可迭代協議:可以被迭代要滿足要求的就叫做可迭代協議。內部實現了__iter__方法
iterable:可迭代的------對應的標志
字符串、列表、元組、字典、集合都可以被for循環,說明他們都是可迭代的。
可以使用isinstance()
判斷一個對象是否是Iterable
對象:
from collections import Iterable print(isinstance([], Iterable)) print(isinstance(123, Iterable)) print(isinstance('345', Iterable)) # """ # True # False # True # """
而生成器不但可以作用於for循環,還可以被next()函數不斷調用並返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了
迭代器協議:內部實現了__iter__,__next__方法。
迭代器的優點:如果用了迭代器,節約內存,方便操作,生成器是一種迭代器(Iterator)。
可迭代和迭代器的相同點:都可以用for循環
可迭代和迭代器的不同點:就是迭代器內部多實現了一個__next__方法
判斷迭代器和可迭代的方法:
第一種:判斷內部是不是實現了__next__方法
print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法執行之后內部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法執行之后內部是否有__next__
第二種:
Iterable 判斷是不是可迭代對象
Iterator 判斷是不是迭代器
from collections import Iterable from collections import Iterator #比如給一個字符串 s='abc' print(isinstance(s,Iterable))#isinstance判斷類型的 print(isinstance(s,Iterator))
判斷range函數和map函數
深入了解Iterator對象
生成器都是Iterator
對象,但list
、dict
、str
雖然是Iterable
,卻不是Iterator
。
把list
、dict
、str
等Iterable
變成Iterator
可以使用iter()
函數:
你可能會問,為什么list
、dict
、str
等數據類型不是Iterator
?
這是因為Python的Iterator
對象表示的是一個數據流,Iterator對象可以被next()
函數調用並不斷返回下一個數據,
直到沒有數據時拋出StopIteration
錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,
只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator
的計算是惰性的,只有在需要返回下一個數據時它才會計算。
Iterator
甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
迭代器小結
凡是可作用於for
循環的對象都是Iterable
類型;
凡是可作用於next()
函數的對象都是Iterator
類型,它們表示一個惰性計算的序列;
集合數據類型如list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函數獲得一個Iterator
對象。
Python3的for
循環本質上就是通過不斷調用next()
函數實現的,例如:
for x in [1, 2, 3, 4, 5]: pass
等價於
# 首先獲得Iterator對象: iter_object = iter([1, 2, 3, 4, 5]) # 循環: while True: try: # 獲得下一個值: x = next(iter_object) except StopIteration: # 遇到StopIteration就退出循環 break