python學習筆記:第12天 列表推導式和生成器

来源:https://www.cnblogs.com/zpzhue1/archive/2018/11/01/9892600.html
-Advertisement-
Play Games

[TOC] 1. 迭代器 什麼是生成器呢,其實生成器的本質就是迭代器;在python中有3中方式來獲取生成器(這裡主要介紹前面2種) 通過生成器函數獲取 通過各種推導式來實現生成器 生成器函數 我們來看一個普通的函數: 那麼生成器函數跟普通函數有什麼不同呢,我們只要把其中的 換成 關鍵字參數就是生成 ...


目錄

1. 迭代器

什麼是生成器呢,其實生成器的本質就是迭代器;在python中有3中方式來獲取生成器(這裡主要介紹前面2種)

  • 通過生成器函數獲取
  • 通過各種推導式來實現生成器

生成器函數

我們來看一個普通的函數:

In[2]: def func1():
  ...:     print('aaaa')
  ...:     return 1111
  ...: 
In[3]: fun = func1()
aaaa
In[4]: print(fun)
1111

那麼生成器函數跟普通函數有什麼不同呢,我們只要把其中的return換成yield關鍵字參數就是生成器函數了:

In[5]: def func1():
  ...:     print('aaaa')
  ...:     yield 1111
  ...: 
In[6]: fun = func1()          # 此時並沒有任何列印信息,可以說明函數並沒有執行
In[7]: print(fun)             # 從輸出可以看出這是一個生成器對象
<generator object func1 at 0x0000016F900D6DB0>

從上面的結果來看,我們發現函數func1根本就沒有執行,而最後列印的是一個記憶體地址,這個就是生成器很明顯的一個特性:惰性計算,那麼我們要怎麼執行它呢?我們可以回顧一下迭代器的取值方法:使用迭代器的__next__的方法可以取到迭代器的一個值,那生成器的本質就是迭代器,那我們也可以試下可以這樣取值

In[8]: fun.__next__() # 從輸出可以看出,yield也和return一樣可以有返回值
aaaa                  # 這裡我們就可以看到函數中的aaaa也列印了,表示函數在此處才執行
Out[8]: 1111

我們再來看個例子,觀察下生成器是怎麼工作的:

In[9]: def func1():
  ...:     print('aaaa')
  ...:     yield '我是第一個yield'
  ...:     print('bbbb')
  ...:     yield '我是第二個yield'
  ...:     print('cccc')
  ...:     
In[10]: gen = func1()           # 這裡得到的是一個生成器,此處並不會運行函數
   ...: print(gen)
<generator object func1 at 0x0000016F900F8BA0>
In[11]: print(gen.__next__())   # 首次執行生成器的__netx__()函數時,開始執行函數,
aaaa                            # 直到遇到yield時返回,並且yield也可以有返回值
我是第一個yield
In[12]: print(gen.__next__())   # 再次運行__netx__()函數時,會繼續執行函數(從上次yield的位置繼續執行)
bbbb
我是第二個yield
In[13]: print(gen.__next__())   # 再次執行__next__()方法繼續執行,此處再往下執行時沒有了yield關鍵字,
cccc                            # 會拋出StopIteration異常(但時會執行後面的代碼)
Traceback (most recent call last):
  File "D:\Environment\python-virtualenv\jupyter\lib\site-packages\IPython\core\interactiveshell.py", line 3265, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-13-9340d28f24b7>", line 1, in <module>
    print(gen.__next__())
StopIteration

從上面我們呢可以總結出:

  • yield也可以像return一樣也是返回值
  • yield執行完之後會返回到調用者,執行後續的代碼,直到再次調用__next__方法,此時生成器函數再從上次停止的位置繼續執行
  • 當執行__next__方法後沒有yield關鍵字時,會拋出StopIteration異常,但是會執行yield後面的代碼

send方法

接下來我們來看send⽅法, send和__next__()⼀樣都可以讓⽣成器執⾏到下⼀個yield

In[14]: def eat():
   ...:     print("aaaa")
   ...:     a = yield 1111
   ...:     print("a=",a)
   ...:     b = yield "bbbb"
   ...:     print("b=",b)
   ...:     c = yield "cccc"
   ...:     print("c=",c)
   ...:     yield "GAME OVER"
   ...:     
In[15]: gen = eat() # 獲取⽣成器
In[16]: ret1 = gen.__next__()
   ...: print(ret1)
aaaa
1111
In[17]: ret2 = gen.send("我send了一個參數給a")
   ...: print(ret2)
a= 我send了一個參數給a              # 可以看出send的數據是被上一個yield前的a給接收了
bbbb
In[18]: ret3 = gen.send("我send了一個參數給b")
   ...: print(ret3)               # 這裡send的數據也是被b接收了
b= 我send了一個參數給b
cccc
In[19]: ret4 = gen.send("我send了一個參數給c")
   ...: print(ret4)
c= 我send了一個參數給c
GAME OVER

**send和__next__()**:

  1. send和next()都是讓⽣成器向下走⼀次
  2. send可以給上⼀個yield的位置傳遞值, 不能給最後⼀個yield發送值. 在第⼀次執⾏⽣成器代碼的時候不能使⽤send()

2. 推導式

列表推導式

關於列表推導式,其實之前的文章中已經使用過,這裡再正式介紹下;假設我們要列印1到20之間的奇數,照之前正常的寫法我們要這麼寫:

# 假設有一個需求,要寫一個迴圈遍歷1到20之間所有的奇數
lst = []
for i in range(1, 21):
    if i % 2 == 1:
        lst.append(i)
print(lst)
# 結果:
# [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] 

列表推導式的語法為:

  • 第一種只使用for迴圈遍歷
[expr for item in itratorable]

# 相當於以下代碼
ret = []
for item in iterable:
  ret.append(expr)
  • 第二種for迴圈遍歷再加if條件判斷
[expr for item in iterable if cond]

# 相當於以下結構代碼
ret = []
for item in iterable:
    if cond:
        ret.append(expr)

第三種for迴圈加if雙分支結構,註意此時的if/else語句要寫在for語句前面

[expr1 if cond else expr2 for item in iterable ]

# 相當於以下代碼
ret = []
for item in iterable:
    if cond:
        ret.append(expr1)
    else:
        ret.append(expr2)

對於上面的例子使用列表推導式可以這樣寫:

# 使用推導式:
lst = [i for i in range(1, 21) if i % 2 == 1]
print(lst)
# 結果:
# [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

使用列表推導式我們可以發現代碼時精簡了許多,而且代碼的可讀性更高了,其實還有一個優勢是推導式速度更快:

In [1]: %%timeit
   ...: lst1 = []
   ...: for i in range(10000):
   ...:     lst1.append(i)
   ...:
788 µs ± 14.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [2]: %%timeit
   ...: lst1 = [i for i in range(10000)]
   ...:
307 µs ± 1.84 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [3]:

從上面的結果分析,使用列表推導式生成列表的方式要比普通for迴圈的效率要高很多

字典推導式

字典跟列表推導式的語法非常相似,使用{}括起來,然後在裡面想列表推導式一樣寫自己的表達式即可:

dic = {expr for k, v in iterable if cond}        # 這裡的expr表達式可以寫成:k: v的形式

# 相當於以下代碼
dic = dict()
for k, v in iterable:
    if cond:
        expr(dic)

例如,把字典中的鍵值對都調換以下可以用如下方法:

dic = {"張無忌":"趙敏", "楊過":"小龍女", "郭靖":"黃蓉"}
# dic = {'k1':'v1', 'k2': 'v2', 'k3': 'v3'}

dic = {v: k for k, v in dic.items()}
print(dic)

生成器表達式

對於生成器表達式來說,只需要把列表推導式的中括弧換成小括弧就可以了:

In[20]: def inc(x):
   ...:     print('inc {0}'.format(x))
   ...:     return x+1
   ...: 
In[21]: g = (inc(x) for x in range(10))             # 這裡的g就是一個生成器對象
In[22]: print(g)
<generator object <genexpr> at 0x0000016F90161DB0>
In[23]: print(g.__next__())
inc 0
1
In[24]: print(g.__next__())                         # 也可以使用__next__方法取出一個值
inc 1
2
In[25]: print(g.__next__())
inc 2
3
In[26]: next(g)                                     # 使用netx()和__next__()方法是一樣的
inc 3
Out[26]: 4
In[27]: next(g)
inc 4
Out[27]: 5

當然,生成器表達式也可以跟其他推導式一樣套用if語句,其語法都是一樣的,這裡就不做介紹了。

⽣成器表達式和列表推導式的區別:

  • 列表推導式比較耗記憶體. ⼀次性載入. ⽣成器表達式⼏乎不占⽤記憶體. 使⽤的時候才分
    配和使⽤記憶體

  • 得到的值不⼀樣. 列表推導式得到的是⼀個列表. ⽣成器表達式獲取的是⼀個⽣成器.

⽣成器的惰性機制: ⽣成器只有在訪問的時候才取值. 說⽩了. 你找他要他才給你值. 不找他
要. 他是不會執⾏的.

def func():
  print(111)
  yield 222

g = func()            # ⽣成器g
g1 = (i for i in g)   # ⽣成器g1. 但是g1的數據來源於g
g2 = (i for i in g1)  # ⽣成器g2. 來源g1
print(list(g))        # 獲取g中的數據. 這時func()才會被執⾏. 列印111.獲取到222. g完畢.
print(list(g1))       # 獲取g1中的數據. g1的數據來源是g. 但是g已經取完了. g1 也就沒有數據了
print(list(g2))       # 和g1同理
                      # 註:list中有for的調用,可以迭代遍歷生成器元素
#結果:
# 1111
# [222]
# []
# []

訪問生成器的另一種方法

使用yield from iterator語句

In[28]: def test():
   ...:     l1 = [1, 2, 3, 4]
   ...:     l2 = ['a', 'b', 'c', 'd']
   ...:     yield from l1             # 
   ...:     yield from l2
   ...:     
In[29]: g = test()
In[30]: for i in g:
   ...:     print(i)
   ...:     
1
2
3
4
a
b
c
d

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我練習的demo是基於SSM+MySQL+Eclipse+Tomcat8+Maven3實現的; 創建項目 ## 創建Maven Project: Artifact Id: cn.com.demo Group Id: demo ## 完成項目的基本配置 ## 生成web.xml ## 添加Tomcat ...
  • 博主按: "《每天一個設計模式》" 旨在初步領會設計模式的精髓,目前採用 (_靠這吃飯_)和 (_純粹喜歡_)兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :) 1. 網速過慢的朋友請移步 "《每天一個設計模式之單例模式》原文地址" 2. 歡迎來我的小站看更多 ...
  • 歡迎大家前往 "騰訊雲+社區" ,獲取更多騰訊海量技術實踐乾貨哦~ 本文由 "落影" 發表於 "雲+社區專欄" 正文 本文介紹Metal和Metal Shader Language,以及Metal和OpenGL ES的差異性,也是實現入門教程的心得總結。 一、Metal Metal 是一個和 Ope ...
  • 一個項目,規定10天投產,預估5天開發5天測試(這裡估計的是手工測試),那麼接下來因為各種環境或者開發技術原因導致開發時間延長至8天,測試時間只剩2天,作為本項目的測試你只有2天的時間進行測試。此項目為緊急項目,必須保證到期投產。請問如何處理? ...
  • 題意 "題目鏈接" Sol 看不懂splay。。,看不懂樹狀數組。。。 只會暴力動態開節點線段樹 觀察之後不難發現,我們對於行和列需要支持的操作都是相同的:找到第$k$大的元素並刪除,在末尾插入一個元素 這樣我們可以維護$n+1$棵線段樹(對列單獨建一棵) 每次操作的時候,如果$y_i = m$,那 ...
  • tesserocr 是 python 的一個 OCR 庫,windows 下安裝 tesserocr 直接用 pip 安裝是不可以的,會報錯,只能用 .whl 的方式安裝。安裝 .whl 時,可能遇到 failed to create process 報錯,pip 版本等級過低,中文路徑無法識別等問... ...
  • 最近因項目需要,在使用任務隊列Celery的時候,出現如題錯誤,最終在github上里找到解決辦法,記錄一下。 運行環境環境:win10 + python3 + redis 2.10.6 + celery 4.2.1 win10上運行celery4.x會出現這個問題,開啟任務隊列一切正常(顯示rea ...
  • 創建項目,也就是網站 1、cmd,輸入:pip3 install Django==2.1.2 2、>>>import django 3、cmd進入需要建項目的文件夾,django-admin startproject 項目名mysite 4、cmd進入mysite目錄,python manage.p ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...