為什么寫這篇文章
我從2021年6月13號寫下第一篇Python的系列專欄算起,陸續更新了二十七篇Python系列文章,在此感謝讀者朋友們的支持和閱讀,特別感謝一鍵三連的小伙伴,
本專欄起名【Python從入門到精通】,主要分為基礎知識和專案實戰兩個部分,目前基礎知識部分已經完全介紹完畢,下一階段就是寫Python專案實戰以及爬蟲相關的知識點,
為了對前期學習的Python基礎知識做一個總結歸納,以幫助讀者朋友們更好的學習下一部分的實戰知識點,故在此我寫下此文,共勉,同進,
同時為了方便大家交流學習,我這邊還建立了一個Python的學習群,群里都是一群熱愛學習的小伙伴,不乏一些牛逼的大佬,大佬帶飛,我相信進入群里的小伙伴一定會走的更快,飛的更高, 歡迎掃碼進群,

本專欄寫了什么
下面就通過一個思維導圖,展示本專欄Python基礎知識部分的總覽圖,

本專欄從零基礎出發,從環境的搭建到高級知識點的學習,一步步走來,相信各位讀者朋友們早已掌握相關的知識點,接下來就做一個詳細的回顧,
0.何為Python
Python是一門開源免費的,通用型的腳本編程語言,它需要在運行時將代碼一行行決議成CPU能識別的機器碼,它是一門決議型的語言,何為決議型語言呢?就是在運行時通過決議器將源代碼一行行決議成機器碼,而像C語言,C++等則是編譯型的語言,即通過編譯器將所有的源代碼一次性編譯成二進制指令,生成一個可執行的程式,決議型語言相對于編譯型語言的好處就是天然具有跨平臺的特點,一次編碼,到處運行,
1. 開發環境配置
- 下載Python解釋器
如同Java需要安裝JDK 編譯器一樣,Python也需要安裝解釋器來運行Python程式,
官方的下載網址是: https://www.python.org/downloads/,映入眼簾的是最新的發布版本,如果想下載其他版本的話,可以下來找到如下圖所示的資訊,當前的最新版本是 python 3.9.5 版本,根據你開發電腦的系統選擇不同系統的安裝包,


安裝包下載之后雙擊運行進行安裝,需要注意的是在Window下安裝需要勾選 Add Python 3.8 to PATH,如下圖1.2所示

安裝完成之后在命令列中輸入python3驗證安裝的結果,如果出現如下結果就表明安裝Python編譯器安裝成功了,

詳細內容可以查看【Python從入門到精通】(一)就簡單看看Python吧
2. 工具安裝
2.1. 安裝PyCharm開發工具
工欲善其事必先利其器,在實際開發中我們都是通過IDE(集成開發環境)來進行編碼,為啥要使用IDE呢?這是因為IDE集成了語言編輯器,自動建立工具,除錯器等等工具可以極大方便我們快速的進行開發,打個比方 我們可以將集成開發環境想象成一個臺式機,雖然只需要主機就能運行起來,但是,還是需要顯示幕,鍵盤等才能用的爽,
PyCharm就是這樣一款讓人用的很爽的IDE開發工具,下面就簡單介紹一下它的安裝程序
下載安裝包
點擊鏈接 https://www.jetbrains.com/pycharm/download/
進入下來頁面,PyCharm 有專業版和社區版,其中,專業版需要購買才能使用,而社區版是免費的,社區版對于日常的Python開發完全夠用了,所以我們選擇PyCharm的社區版進行下載安裝,點擊如下圖所示的按鈕進行安裝包的下載,

安裝
安裝包下載好之后,我們雙擊安裝包即可進行安裝,安裝程序比較簡單,基本只需要安裝默認的設定每一步點擊Next按鈕即可,不過當出現下圖的視窗時需要設定一下,

設定好之后點擊 Next 進入下一步的安裝,知道所有的安裝都完成,
使用
這里使用只需要注意一點,就是設定解釋器,默認的話在Project Interpreter的選擇框中是提示的是 No interpreter,即沒有選中解釋器,所以,我們需要手動添加,
所以需要點擊設定按鈕設定解釋器,這里選擇 Add Local 設定本地的解釋器,

打開解釋器的設定頁面之后,默認選中的是Virtualenv Environment 這個tab頁,
這里Location是用來設定專案的虛擬環境,具體可以參考pycharm的使用小結,設定虛擬環境,更換鏡像源
Base interpreter 用來設定解釋器的路徑,

至此,開發Python的腳手架已經弄好,接下來就是編碼了,
如下創建了一個名為demo_1.py的檔案,然后在檔案中寫入了如下幾行代碼
print("你好,世界")
a = 120
b = 230
print(a + b)
運行這些代碼只需要簡單的右鍵選中 Run ‘demo_1’ 或者 Debug ‘demo_1’ ,這兩者的區別是Run demo_1是以普通模式運行代碼,而 Debug ‘demo_1’ 是以除錯模式運行代碼,

運行結果就是:

詳細內容可以查看【Python從入門到精通】(二)怎么運行Python呢?有哪些好的開發工具
3. 編碼規范&注釋
3.1.注釋
首先介紹的是Python的注釋,Python的注釋分為兩種:單行注釋和多行注釋,
- 單行注釋
Python使用 # 號作為單行注釋的符號,其語法格式為:#注釋內容從#號開始直到這行結束為止的所有內容都是注釋,例如:
# 這是單行注釋
- 多行注釋
多行注釋指一次注釋程式中多行的內容(包含一行) ,Python使用三個連續的 單引號’’’ 或者三個連續的雙引號""" 注釋多行內容,其語法格式是如下:
'''
三個連續的單引號的多行注釋
注釋多行內容
'''
或者
"""
三個連續的雙引號的多行注釋
注釋多行內容
"""
多行注釋通常用來為Python檔案、模塊、類或者函式等添加著作權或者功能描述資訊(即檔案注釋)
3.2.縮進規則
不同于其他編程語言(如Java,或者C)采用大括號{}分割代碼塊,Python采用代碼縮進和冒號 : 來區分代碼塊之間的層次,如下面的代碼所示:
a = -100
if a >= 0:
print("輸出正數" + str(a))
print('測驗')
else:
print("輸出負數" + str(a))
其中第一行代碼a = -100和第二行代碼if a >= 0:是在同一作用域(也就是作用范圍相同),所以這兩行代碼并排,而第三行代碼print("輸出正數" + str(a)) 的作用范圍是在第二行代碼里面,所以需要縮進,第五行代碼也是同理,第二行代碼通過冒號和第三行代碼的縮進來區分這兩個代碼塊,
Python的縮進量可以使用空格或者Tab鍵來實作縮進,通常情況下都是采用4個空格長度作為一個縮進量的,
這里需要注意的是同一個作用域的代碼的縮進量要相同,不然會導致IndentationError例外錯誤,提示縮進量不對,如下面代碼所示:第二行代碼print("輸出正數" + str(a)) 縮進了4個空格,而第三行代碼print('測驗')只縮進了2個空格,
if a >= 0:
print("輸出正數" + str(a))
print('測驗')
在Python中,對于類定義,函式定義,流程控制陳述句就像前面的if a>=0:,例外處理陳述句等,行尾的冒號和下一行縮進,表示下一個代碼塊的開始,而縮進的結束則表示此代碼的結束,
詳細內容可以查看【Python從入門到精通】(三)Python的編碼規范,識別符號知多少?
4. 資料型別
4.1.各種資料型別總覽

4.2.整數(int)
Python3中的整數是不分型別,也就是說沒有長整數型別(Long)或者短整數型別(short)之分,它的取值范圍是是無限的,即不管多大或者多小的數字,Python都能輕松的應對,如下就是兩個極大或者極小的整數,
>>> 100000-0000000000000000000000000000000000000000
1000000000000000000000000000000000000000000000
>>> print(-1000000000000000000000000000000000000000000000)
-1000000000000000000000000000000000000000000000
可以看出再大或者再小的數字都不會出現溢位的情況,這說明了Python對整數的處理能力非常強,
整數的不同進制
Python中可以用多種進制的來表示整數,
- 十進制形式
我們平時常見的整數就是十進制形式,它由 0~9 共十個數字排列組合而成,
注意,使用十進制形式的整數不能以 0 作為開頭,除非這個數值本身就是 0, - 二進制形式
由 0 和 1 兩個數字組成,書寫時以0b或0B開頭,例如,1001對應十進制數是 9, - 八進制形式
八進制整數由 0~7 共八個數字組成,以0o或0O開頭,注意,第一個符號是數字 0,第二個符號是大寫或小寫的字母 O, - 十六進制形式
由 0~9 十個數字以及 A~F(或 a~f)六個字母組成,書寫時以0x或0X開頭,
# 二進制
a=0b1001
print('a=',a)
# 八進制
b=0o207
print('b=',b)
# 十六進制
c=0x45
print('c=',c)
運行結果是:
a= 9
b= 135
c= 69
Python 3.x允許使用下劃線_作為數字(包括整數和小數)的分隔符,通常每隔三個數字添加一個下劃線,比如:click = 1_301_547
4.3. 浮點數/小數(float)
在編程語言中,小數通常以浮點數的形式存盤,浮點數和定點數是相對的;小數在存盤程序中如果小數點發生移動,就稱為浮點數;如果小數點不動,就稱為定點數,
小數的書寫形式
Python中的小數有兩種書寫形式:
- 十進制形式
這就是我們經常看到的小數形式,比如101.1;234.5;0.23 - 指數形式
Python小數點指數形式的寫法為:aEn或aen
a為尾數部分,是一個十進制,n為指數部分,是一個十進制,E或者e是固定的字符,用于分割尾數部分和指數部分,真的運算式是 a×10n,
舉個栗子:
2.3E5=2.3x10的5次方
依然還舉個栗子:
x=10.01
print('x=',x)
y=-0.031
print('y=',y)
z=2.3E10
print('z=',z)
w=-0.00000001
print('w=',w)
運行結果是:
x= 10.01
y= -0.031
z= 23000000000.0
w= -1e-08
4.4.布爾型別(bool)
布爾型別用來表示真(對)或假(錯),比如常見的3>2 比較算式,這個是正確的,Python中使用True來代表;再比如2>3 比較算式,這個是錯誤的,用False來代表,
print(3>2)
print(2>3)
print('True==1的結果是:',True==1)
print('False==0的結果是:',False==0)
運行結果是:
True
False
True==1的結果是: True
False==0的結果是: True
詳細內容可以查看【Python從入門到精通】(四)Python的內置資料型別有哪些呢?數字了解一下
5. 序列
序列(sequence)指的是一塊可存放多個元素的記憶體空間,這些元素按照一定的順序排列,每個元素都有自己的位置(索引),可以通過這些位置(索引)來找到指定的元素,如果將序列想象成一個酒店,那么酒店里的每個房間就相當于序列中的每個元素,房間的編號就相當于元素的索引,可以通過編號(索引)找到指定的房間(元素),
5.1.有哪些序列型別呢?
了解完了序列的基本概念,那么在Python中一共有哪些序列型別呢?如下圖所示:

從圖中可以看出在Python中共有7種序列型別,分別是文本序列型別(str);二進制序列型別 bytes和bytearray;串列(list);元組(tuple);集合型別(set和frozenset);范圍型別(range)以及字典型別(dict),
5.2. 按照能存盤的元素劃分
按照能存盤的元素可以將序列型別劃分為兩大類:分別是:容器序列和扁平序列
容器序列:即可容納不同資料型別的元素的序列;有 list;tuple;set;dict
舉個栗子:
list=['runoob',786,2.23,'john',70.2]
這里的list保存的元素有多種資料型別,既有字串,也有小數和整數,
扁平序列:即只能容納相同資料型別的元素的序列;有bytes;str;bytearray,以str為例,同一個str只能都存盤字符,
5.2. 按照是否可變劃分
按照序列是否可變,又可分為可變序列和不可變序列,
這里的可變的意思是:序列創建成功之后,還能不能進行修改操作,比如插入,修改等等,如果可以的話則是可變的序列,如果不可以的話則是不可變序列,
可變序列有串列( list);字典(dict)等,
不可變的序列有元祖(tuple),后面的文章會詳細的對這些資料型別做詳細介紹,
5.3. 序列的索引
在介紹序列概念的時候,說到了序列中元素的索引,那么什么是序列的索引呢?其實就是位置的下標, 如果對C語言中的陣列有所了解的話,我們知道陣列的索引下標都是從0開始依次遞增的正數,即第一個元素的索引下標是0,第n個元素的索引下標是n-1,序列的索引也是同理,默認情況下都是從左向右記錄索引,索引值從0開始遞增,即第一個元素的元素的索引值是0,第n個元素的索引值是n-1,如下圖所示:

當然與C語言中陣列不同的是,Python還支持索引值是負數,該類的索引是從右向左計數,換句話說,就是從最后一個元素開始計數,從索引值-1開始遞減,即第n個元素的索引值是-1,第1個元素的索引值是-n,如下圖所示:

5.4.序列切片
切片操作是訪問序列元素的另一種方式,它可以訪問一定范圍內的元素,通過切片操作,可以生成一個新的序列,切片操作的語法格式是:
sname[start : end : step]
各個引數的含義分別是:
- sname: 表示序列的名稱
- start:表示切片的開始索引位置(包括該位置),此引數也可以不指定,不指定的情況下會默認為0,也就是從序列的開頭開始切片,
- end:表示切片的結束索引位置(不包括該位置),如果不指定,則默認為序列的長度,
- step: 表示步長,即在切片程序中,隔幾個存盤位置(包括當前位置)取一次元素,也就是說,如果step的值大于1,比如step為3時,則在切片取元素時,會隔2個位置去取下一個元素,
還是舉個栗子說明下吧:
str1='好好學習,天天向上'
# 取出索引下標為7的值
print(str1[7])
# 從下標0開始取值,一直取到下標為7(不包括)的索引值
print(str1[0:7])
# 從下標1開始取值,一直取到下標為4(不包括)的索引值,因為step等于2,所以會隔1個元素取值
print(str1[1:4:2])
# 取出最后一個元素
print(str1[-1])
# 從下標-9開始取值,一直取到下標為-2(不包括)的索引值
print(str1[-9:-2])
運行的結果是:
向
好好學習,天天
好習
上
好好學習,天天
5.5.序列相加
Python支持型別相同的兩個序列使用"+"運算子做想加操作,它會將兩個序列進行連接,但是不會去除重復的元素,即只做一個簡單的拼接,
str='他叫小明'
str1='他很聰明'
print(str+str1)
運行結果是:他叫小明他很聰明
5.6.序列相乘
Python支持使用數字n乘以一個序列,其會生成一個新的序列,新序列的內容是原序列被重復了n次的結果,
str2='你好呀'
print(str2*3)
運行結果是:你好呀你好呀你好呀 ,原序列的內容重復了3次,
5.7.檢查元素是否包含在序列中
Python中可以使用in關鍵字檢查某個元素是否為序列中的成員,其語法格式為:
value in sequence
其中,value表示要檢查的元素,sequence表示指定的序列,
舉個栗子:查找天字是否在字串str1中,
str1='好好學習,天天向上'
print('天' in str1)
運行結果是:True
6. 字串
*由若干個字符組成的集合就是一個字串(str)**,Python中的字串必須由雙引號""或者單引號’'包圍,其語法格式是:
"字串內容"
'字串內容'
如果字串中包含了單引號需要做特殊處理,比如現在有這樣一個字串
str4='I'm a greate coder' 直接這樣寫有問題的,
處理的方式有兩種:
- 對引號進行轉義,通過轉義符號
\進行轉義即可:
str4='I\'m a greate coder'
- 使用不同的引號包圍字串
str4="I'm a greate coder"
這里外層用雙引號,包裹字串里的單引號,
6.1.字串拼接
通過+運算子
現有字串碼農飛哥好,,要求將字串碼農飛哥牛逼拼接到其后面,生成新的字串碼農飛哥好,碼農飛哥牛逼
舉個例子:
str6 = '碼農飛哥好,'
# 使用+ 運算子號
print('+運算子拼接的結果=',(str6 + '碼農飛哥牛逼'))
運行結果是:
+運算子拼接的結果= 碼農飛哥好,碼農飛哥牛逼
6.2.字串截取(字串切片)
切片操作是訪問字串的另一種方式,它可以訪問一定范圍內的元素,通過切片操作,可以生成一個新的字串,切片操作的語法格式是:
sname[start : end : step]
各個引數的含義分別是:
- sname: 表示字串的名稱
- start:表示切片的開始索引位置(包括該位置),此引數也可以不指定,不指定的情況下會默認為0,也就是從序列的開頭開始切片,
- end:表示切片的結束索引位置(不包括該位置),如果不指定,則默認為序列的長度,
- step: 表示步長,即在切片程序中,隔幾個存盤位置(包括當前位置)取一次元素,也就是說,如果step的值大于1,比如step為3時,則在切片取元素時,會隔2個位置去取下一個元素,
還是舉個栗子說明下吧:
str1='好好學習,天天向上'
# 取出索引下標為7的值
print(str1[7])
# 從下標0開始取值,一直取到下標為7(不包括)的索引值
print(str1[0:7])
# 從下標1開始取值,一直取到下標為4(不包括)的索引值,因為step等于2,所以會隔1個元素取值
print(str1[1:4:2])
# 取出最后一個元素
print(str1[-1])
# 從下標-9開始取值,一直取到下標為-2(不包括)的索引值
print(str1[-9:-2])
運行的結果是:
向
好好學習,天天
好習
上
好好學習,天天
6.3.分割字串
Python提供了split()方法用于分割字串,split() 方法可以實作將一個字串按照指定的分隔符切分成多個子串,這些子串會被保存到串列中(不包含分隔符),作為方法的回傳值反饋回來,該方法的基本語法格式如下:
str.split(sep,maxsplit)
此方法中各部分引數的含義分別是:
- str: 表示要進行分割的字串
- sep: 用于指定分隔符,可以包含多個字符,此引數默認為None,表示所有空字符,包括空格,換行符"\n"、制表符"\t"等
- maxsplit: 可選引數,用于指定分割的次數,最后串列中子串的個數最多為maxsplit+1,如果不指定或者指定為-1,則表示分割次數沒有限制,
在 split 方法中,如果不指定 sep 引數,那么也不能指定 maxsplit 引數,
舉例說明下:
str = 'https://feige.blog.csdn.net/'
print('不指定分割次數', str.split('.'))
print('指定分割次數為2次',str.split('.',2))
運行結果是:
不指定分割次數 ['https://feige', 'blog', 'csdn', 'net/']
指定分割次數為2次 ['https://feige', 'blog', 'csdn.net/']
6.4.合并字串
合并字串與split的作用剛剛相反,Python提供了join() 方法來將串列(或元組)中包含的多個字串連接成一個字串,其語法結構是:
newstr = str.join(iterable)
此方法各部分的引數含義是:
- newstr: 表示合并后生成的新字串
- str: 用于指定合并時的分隔符
- iterable: 做合并操作的源字串資料,允許以串列、元組等形式提供,
依然是舉例說明:
list = ['碼農飛哥', '好好學習', '非常棒']
print('通過.來拼接', '.'.join(list))
print('通過-來拼接', '-'.join(list))
運行結果是:
通過.來拼接 碼農飛哥.好好學習.非常棒
通過-來拼接 碼農飛哥-好好學習-非常棒
6.5.檢索字串是否以指定字串開頭(startswith())
startswith()方法用于檢索字串是否以指定字串開頭,如果是回傳True;反之回傳False,其語法結構是:
str.startswith(sub[,start[,end]])
此方法各個引數的含義是:
- str: 表示原字串
- sub: 要檢索的子串‘
- start: 指定檢索開始的起始位置索引,如果不指定,則默認從頭開始檢索
- end: 指定檢索的結束位置索引,如果不指定,則默認一直檢索到結束,
舉個栗子說明下:
str1 = 'https://feige.blog.csdn.net/'
print('是否是以https開頭', str1.startswith('https'))
print('是否是以feige開頭', str1.startswith('feige', 0, 20))
運行結果是:
是否是以https開頭 True
是否是以feige開頭 False
6.6.檢索字串是否以指定字串結尾(endswith())
endswith()方法用于檢索字串是否以指定字串結尾,如果是則回傳True,反之則回傳False,其語法結構是:
str.endswith(sub[,start[,end]])
此方法各個引數的含義與startswith方法相同,再此就不在贅述了,
6.7.字串大小寫轉換(3種)函式及用法
Python中提供了3種方法用于字串大小寫轉換
- title()方法用于將字串中每個單詞的首字母轉成大寫,其他字母全部轉為小寫,轉換完成后,此方法會回傳轉換得到的字串,如果字串中沒有需要被轉換的字符,此方法會將字串原封不動地回傳,其語法結構是
str.title() - lower()用于將字串中的所有大寫字母轉換成小寫字母,轉換完成后,該方法會回傳新得到的子串,如果字串中原本就都是小寫字母,則該方法會回傳原字串, 其語法結構是
str.lower() - upper()用于將字串中的所有小寫字母轉換成大寫字母,如果轉換成功,則回傳新字串;反之,則回傳原字串,其語法結構是:
str.upper(),
舉例說明下吧:
str = 'feiGe勇敢飛'
print('首字母大寫', str.title())
print('全部小寫', str.lower())
print('全部大寫', str.upper())
運行結果是:
首字母大寫 Feige勇敢飛
全部小寫 feige勇敢飛
全部大寫 FEIGE勇敢飛
6.8.去除字串中空格(洗掉特殊字符)的3種方法
Python中提供了三種方法去除字串中空格(洗掉特殊字符)的3種方法,這里的特殊字符,指的是指表符(\t)、回車符(\r),換行符(\n)等,
- strip(): 洗掉字串前后(左右兩側)的空格或特殊字符
- lstrip():洗掉字串前面(左邊)的空格或特殊字符
- rstrip():洗掉字串后面(右邊)的空格或特殊字符
Python的str是不可變的,因此這三個方法只是回傳字串前面或者后面空白被洗掉之后的副本,并不會改變字串本身
舉個例子說明下:
str = '\n碼農飛哥勇敢飛 '
print('去除前后空格(特殊字串)', str.strip())
print('去除左邊空格(特殊字串)', str.lstrip())
print('去除右邊空格(特殊字串)', str.rstrip())
運行結果是:
去除前后空格(特殊字串) 碼農飛哥勇敢飛
去除左邊空格(特殊字串) 碼農飛哥勇敢飛
去除右邊空格(特殊字串)
碼農飛哥勇敢飛
6.9.encode()和decode()方法:字串編碼轉換
最早的字串編碼是ASCll編碼,它僅僅對10個數字,26個大小寫英文字母以及一些特殊字符進行了編碼,ASCII碼最多只能表示256個字符,每個字符只需要占用1個位元組,為了兼容各國的文字,相繼出現了GBK,GB2312,UTF-8編碼等,UTF-8是國際通用的編碼格式,它包含了全世界所有國家需要用到的字符,其規定英文字符占用1個位元組,中文字符占用3個位元組,
- encode() 方法為字串型別(str)提供的方法,用于將 str 型別轉換成 bytes 型別,這個程序也稱為“編碼”,其語法結構是:
str.encode([encoding="utf-8"][,errors="strict"]) - 將bytes型別的二進制資料轉換成str型別,這個程序也稱為"解碼",其語法結構是:
bytes.decode([encoding="utf-8"][,errors="strict"])
舉個例子說明下:
str = '碼農飛哥加油'
bytes = str.encode()
print('編碼', bytes)
print('解碼', bytes.decode())
運行結果是:
編碼 b'\xe7\xa0\x81\xe5\x86\x9c\xe9\xa3\x9e\xe5\x93\xa5\xe5\x8a\xa0\xe6\xb2\xb9'
解碼 碼農飛哥加油
默認的編碼格式是UTF-8,編碼和解碼的格式要相同,不然會解碼失敗,
6.9.序列化和反序列化
在實際作業中我們經常要將一個資料物件序列化成字串,也會將一個字串反序列化成一個資料物件,Python自帶的序列化模塊是json模塊,
- json.dumps() 方法是將Python物件轉成字串
- json.loads()方法是將已編碼的 JSON 字串解碼為 Python 物件
舉個例子說明下:
import json
dict = {'學號': 1001, 'name': "張三", 'score': [{'語文': 90, '數學': 100}]}
str = json.dumps(dict,ensure_ascii=False)
print('序列化成字串', str, type(str))
dict2 = json.loads(str)
print('反序列化成物件', dict2, type(dict2))
運行結果是:
序列化成字串 {"name": "張三", "score": [{"數學": 100, "語文": 90}], "學號": 1001} <class 'str'>
反序列化成物件 {'name': '張三', 'score': [{'數學': 100, '語文': 90}], '學號': 1001} <class 'dict'>
詳細內容可以查看
【Python從入門到精通】(五)Python內置的資料型別-序列和字串,沒有女友,不是保姆,只有拿來就能用的干貨
【Python從入門到精通】(九)Python中字串的各種騷操作你已經爛熟于心了么?【收藏下來就挺好的】
7. 串列&元組
7.1.串列(list)的介紹
串列作為Python序列型別中的一種,其也是用于存盤多個元素的一塊記憶體空間,這些元素按照一定的順序排列,其資料結構是:
[element1, element2, element3, ..., elementn]
element1~elementn表示串列中的元素,元素的資料格式沒有限制,只要是Python支持的資料格式都可以往里面方,同時因為串列支持自動擴容,所以它可變序列,即可以動態的修改串列,即可以修改,新增,洗掉串列元素,看個爽圖吧!
7.2.串列的操作
首先介紹的是對串列的操作:包括串列的創建,串列的洗掉等!其中創建一個串列的方式有兩種:
第一種方式:
通過[]包裹串列中的元素,每個元素之間通過逗號,分割,元素型別不限并且同一串列中的每個元素的型別可以不相同,但是不建議這樣做,因為如果每個元素的資料型別都不同的話則非常不方便對串列進行遍歷決議,所以建議一個串列只存同一種型別的元素,
list=[element1, element2, element3, ..., elementn]
例如:test_list = ['測驗', 2, ['碼農飛哥', '小偉'], (12, 23)]
PS: 空串列的定義是list=[]
第二種方式:
通過list(iterable)函式來創建串列,list函式是Python內置的函式,該函式傳入的引數必須是可迭代的序列,比如字串,串列,元組等等,如果iterable傳入為空,則會創建一個空的串列,iterable不能只傳一個數字,
classmates1 = list('碼農飛哥')
print(classmates1)
生成的串列是:['碼', '農', '飛', '哥']
7.3. 向串列中新增元素
向串列中新增元素的方法有四種,分別是:
第一種: 使用**+運算子將多個串列**連接起來,相當于在第一個串列的末尾添加上另一個串列,其語法格式是listname1+listname2
name_list = ['碼農飛哥', '小偉', '小小偉']
name_list2 = ['python', 'java']
print(name_list + name_list2)
輸出結果是:['碼農飛哥', '小偉', '小小偉', 'python', 'java'],可以看出將name_list2中的每個元素都添加到了name_list的末尾,
第二種:使用append()方法添加元素
append()方法用于向串列末尾添加元素,其語法格式是:listname.append(p_object)其中listname表示要添加元素的串列,p_object表示要添加到串列末尾的元素,可以是字串,數字,也可以是一個序列,舉個栗子:
name_list.append('Adam')
print(name_list)
name_list.append(['test', 'test1'])
print(name_list)
運行結果是:
['碼農飛哥', '小偉', '小小偉', 'Adam']
['碼農飛哥', '小偉', '小小偉', 'Adam', ['test', 'test1']]
可以看出待添加的元素都成功的添加到了原串列的末尾處,并且當添加的元素是一個序列時,則會將該序列當成一個整體,
第三種:使用extend()方法
extend()方法跟append()方法的用法相同,同樣是向串列末尾添加元素,元素的型別只需要Python支持的資料型別即可,不過與append()方法不同的是,當添加的元素是序列時,extend()方法不會將串列當成一個整體,而是將每個元素添加到串列末尾,還是上面的那個例子:
name_list = ['碼農飛哥', '小偉', '小小偉']
name_list.extend('Adam')
print(name_list)
name_list.extend(['test', 'test1'])
print(name_list)
運行結果是:
['碼農飛哥', '小偉', '小小偉', 'A', 'd', 'a', 'm']
['碼農飛哥', '小偉', '小小偉', 'A', 'd', 'a', 'm', 'test', 'test1']
從結果看出,當添加字串時會將字串中的每個字符作為一個元素添加到串列的末尾處,當添加的串列時會將串列中的每個元素添加到末尾處,
第四種:使用insert()方法
前面介紹的幾種插入方法,都只能向串列的末尾處插入元素,如果想在串列指定位置插入元素則無能為力,insert()方法正式用于處理這種問題而來的,其語法結構是listname.insert(index, p_object) 其中index表示指定位置的索引值,insert()會將p_object插入到listname串列第index個元素的位置,與append()方法相同的是,如果待添加的元素的是序列,則insert()會將該序列當成一個整體插入到串列的指定位置處,舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉']
name_list.insert(1, 'Jack')
print(name_list)
name_list.insert(2, ['test', 'test1'])
print(name_list)
運行結果是:
['碼農飛哥', 'Jack', '小偉', '小小偉']
['碼農飛哥', 'Jack', ['test', 'test1'], '小偉', '小小偉']
7.4. 修改串列中的元素
說完了串列中元素新增的方法,接著讓我們來看看修改串列中的元素相關的方法,修改串列元素的方法有兩種:
第一種:修改單個元素:
修改單個元素的方法就是對某個索引上的元素進行重新賦值,其語法結構是:listname[index]=newValue,就是將串列listname中索引值為index位置上的元素替換成newValue,
舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉']
name_list[1] = 'Sarah'
print(name_list)
運行結果:['碼農飛哥', 'Sarah', '小小偉'] 從結果可以看出索引為1處的元素值被成功修改成了Sarch,
第二種:通過切片語法修改一組元素
通過切片語法可以修改一組元素,其語法結構是:listname[start:end:step],其中,listname表示串列名稱,start表示起始位置,end表示結束位置(不包括),step表示步長,如果不指定步長,Python就不要求新賦值的元素個數與原來的元素個數相同,這意味著,該操作可以為串列添加元素,也可以為串列洗掉元素,舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉']
name_list[0:1] = ['飛哥', '牛逼']
print(name_list)
運行結果是:['飛哥', '牛逼', '小偉', '小小偉'] ,從結果可以看出將原串列中索引為0處的元素值已經被替換為飛哥,并且插入了牛逼 這個元素,
7.5. 洗掉串列中的元素
洗掉串列中元素的方法共有四種,
第一種:根據索引值洗掉元素的del關鍵字
根據索引值洗掉元素的del關鍵字有兩種形式,一種是洗掉單個元素,del listname[index],一種是根據切片洗掉多個元素del listname[start : end],其中,listname表示串列名稱,start表示起始索引,end表示結束索引,del會洗掉從索引start到end之間的元素,但是不包括end位置的元素,還是舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
name_list2 = name_list
print('原始的name_list={0}'.format(name_list))
print('原始的name_list2={0}'.format(name_list2))
# 洗掉索引0到2之間的元素,即洗掉索引0和索引1兩個位置的元素
del name_list[0:2]
print('使用del洗掉元素后name_list={0}'.format(name_list))
print('使用del洗掉元素后name_list2={0}'.format(name_list2))
del name_list
print('使用del洗掉串列后name_list2={0}'.format(name_list2))
運行結果是:
原始的name_list=['碼農飛哥', '小偉', '小小偉', '超人']
原始的name_list2=['碼農飛哥', '小偉', '小小偉', '超人']
使用del洗掉元素后name_list=['小小偉', '超人']
使用del洗掉元素后name_list2=['小小偉', '超人']
使用del洗掉串列后name_list2=['小小偉', '超人']
可以看出用del洗掉串列元素時是真實的洗掉了記憶體資料的,但是用del洗掉串列時,則只是洗掉了變數,name_list2所指向的記憶體資料還是存在的,
第二種:根據索引值洗掉元素的pop()方法
根據索引值洗掉元素的pop()方法的語法結構是:listname.pop(index),其中,listname表示串列名稱,index表示索引值,如果不寫index引數,默認會洗掉串列中最后一個元素,類似于資料結構中的出堆疊操作,舉個例子:
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
# 洗掉list末尾的元素
name_list.pop()
print(name_list)
# 洗掉指定位置的元素,用pop(i)方法,其中i是索引位置
name_list.pop(1)
print(name_list)
運行結果是:
['碼農飛哥', '小偉', '小小偉']
['碼農飛哥', '小小偉']
第三種:根據元素值進行洗掉的remove()方法
根據元素值進行洗掉的remove()方法,其語法結構是:listname.remove(object),其中listname表示串列的名稱,object表示待洗掉的元素名稱,需要注意的是:如果元素在串列中不存在則會報ValueError的錯誤,舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
name_list.remove('小小偉')
print(name_list)
運行結果是:['碼農飛哥', '小偉', '超人'],
第四種:洗掉串列中的所有元素clear()方法
通過clear()方法可以洗掉掉串列中的所有元素,其語法結構是:listname.clear(),其中listname表示串列的名稱,還是舉個栗子吧:
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
name_list.clear()
print(name_list)
運行結果是:[],可以看出串列中元素被全部清空了,
7.6.串列中元素的查找以及訪問
說完了第五淺串列元素的洗掉,略感疲憊,接著進行第六淺吧!看看串列中元素的查找以及訪問,看完這個之后,串列相關的內容也就告一段落了,
訪問串列中的元素
訪問串列中的元素有兩種方式,分別是通過索引定位訪問單個元素,通過切片訪問多個元素,
第一種:通過索引定位訪問單個元素,其語法結構是:listname[index] ,其中listname表示串列的名字,index表示要查找元素的索引值,
第二種:通過切片的方式訪問多個元素,其語法結構是:listname[start:end:step],其中,listname表示串列的名字,start表示開始索引,end表示結束索引(不包括end位置),step表示步長,同樣是舉個栗子:
list2 = ['碼農飛哥', '小偉', '小小偉',123]
print(list2[0]) # 輸出串列的第一個元素
print(list2[1:3]) # 輸出第二個至第三個元素
print(list2[2:]) # 輸出從第三個開始至串列末尾的所有元素
運行結果是:
碼農飛哥
['小偉', '小小偉']
['小小偉', 123]
查找某個元素在串列中出現的位置 index()
indext()方法用來查找某個元素在串列中出現的位置(也就是索引),如果該元素在串列中不存在,則會報ValueError錯誤,其語法結構是:listname.index(object, start, end) 其中listname表示串列的名字,object表示要查找的元素,start表示起始索引,end表示結束索引(不包括),
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
print(name_list.index('小偉', 0, 2))
運行結果是:1
7.8. Python新增元素中各個方法的區別
前面介紹了使用+運算子,使用append方法,使用extend方法都可以新增元素,那么他們到底有啥區別呢?還是舉例說明吧;
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
name_list2 = ['牛魔王']
name_list3 = name_list + name_list2
print("原始的name_list的值={0};記憶體地址={1}".format(name_list, id(name_list)))
print("使用+運算子后name_list3的值={0};記憶體地址={1}".format(name_list3, id(name_list3)))
print("使用+運算子后name_list的值{0};記憶體地址={1}".format(name_list, id(name_list)))
name_list4 = name_list.append('牛魔王')
print('使用append方法后name_list4的值={0};記憶體地址={1}'.format(name_list4, id(name_list4)))
print("使用append方法后name_list的值{0};記憶體地址={1}".format(name_list, id(name_list)))
name_list5 = name_list.extend('牛魔王')
print('使用extend方法后name_list5的值={0};記憶體地址={1}'.format(name_list4, id(name_list4)))
print("使用extend方法后name_list的值{0};記憶體地址={1}".format(name_list, id(name_list)))
運行結果是:
原始的name_list的值=['碼農飛哥', '小偉', '小小偉', '超人'];記憶體地址=2069467533448
使用+運算子后name_list3的值=['碼農飛哥', '小偉', '小小偉', '超人', '牛魔王'];記憶體地址=2069467533896
使用+運算子后name_list的值['碼農飛哥', '小偉', '小小偉', '超人'];記憶體地址=2069467533448
使用append方法后name_list4的值=None;記憶體地址=2012521616
使用append方法后name_list的值['碼農飛哥', '小偉', '小小偉', '超人', '牛魔王'];記憶體地址=2069467533448
使用extend方法后name_list5的值=None;記憶體地址=2012521616
使用extend方法后name_list的值['碼農飛哥', '小偉', '小小偉', '超人', '牛魔王', '牛', '魔', '王'];記憶體地址=2069467533448
從運行結果可以看出如下幾點:
- 使用+運算子是創建一個新的串列,新串列的地址與原串列的地址不相同,并且原始串列的內容不會改變,
- append方法和extend方法都是修改原始串列的內容,并且都沒有回傳值,所以兩者都不能使用鏈式運算式,
- 當待添加的元素是串列時,append方法會將串列當成一個整體,而extend不會,
8. 元組(tuple)
8.1. 元組(tuple)的介紹
說完了串列,接著讓我們來看看另外一個重要的序列–元組(tuple),和串列類似,元組也是由一系列按特定書序排序的元素組成,與串列最重要的區別是,元組屬于不可變序列,即元組一旦被創建,它的元素就不可更改了,
8.2.元組的創建方式
第一種:使用()直接創建
使用()創建元組的語法結構是tuplename=(element1,element2,....,elementn),其中tuplename表示元組的變數名,element1~elementn表示元組中的元素,小括號不是必須的,只要將元素用逗號分隔,Python就會將其視為元組,還是舉個栗子:
#創建元組
tuple_name = ('碼農飛哥', '小偉', '小小偉', '超人')
print(tuple_name)
#去掉小括號創建元組
tuple2 = '碼農飛哥', '小偉', '小小偉', '超人'
print(type(tuple2))
運行結果是:
('碼農飛哥', '小偉', '小小偉', '超人')
<class 'tuple'>
第二種:使用tuple()函式創建
與串列類似的,我們可以通過tuple(iterable)函式來創建元組,如果iterable傳入為空,則創建一個空的元組,iterable 引數必須是可迭代的序列,比如字串,串列,元組等,同樣的iterable不能傳入一個數字,舉個栗子:
name_list = ['碼農飛哥', '小偉', '小小偉', '超人']
print(tuple(name_list))
print(tuple('碼農飛哥'))
運行結果是:
('碼農飛哥', '小偉', '小小偉', '超人')
('碼', '農', '飛', '哥')
由于元組是不可變序列,所以沒有修改元素相關的方法,只能對元組中的元素進行查看,查看元素的方式也與串列類似,共兩種方式:
第一種:通過索引(index)訪問元組中的元素,其語法結構是tuplename[index]
第二種:通過切片的方式訪問,其語法結構是:tuplename[start:end:step]
相關引數的描述在此不再贅述了,依然是舉例說明:
tuple_name = ('碼農飛哥', '小偉', '小小偉', '超人')
# 獲取索引為1的元素值
print(tuple_name[1])
#獲取索引為1到索引為2之間的元素值,不包括索引2本身
print(tuple_name[0:2])
運行結果是:
小偉
('碼農飛哥', '小偉')
元組中的元素不能修改,不過可以通過 + 來生成一個新的元組,
詳細內容可以查看【Python從入門到精通】(六)Python內置的資料型別-串列(list)和元組(tuple),九淺一深,十個章節,不信你用不到
9.字典
9.1.創建一個字典
創建字典的方式有很多種,下面羅列幾種比較常見的方法,
第一種:使用 {} 符號來創建字典,其語法結構是dictname={'key1':'value1', 'key2':'value2', ..., 'keyn':valuen}
第二種:使用fromkeys方法,其語法結構是dictname = dict.fromkeys(list,value=None), 其中,list引數表示字典中所有鍵的串列(list),value引數表示默認值,如果不寫則為所有的值都為空值None,
第三種:使用dict方法,其分為四種情況:
- dict() -> 創建一個空字典
- dict(mapping) -> 創建一個字典,初始化時其鍵值分別來自于mapping中的key,value,
- dict(iterable) -> 創建一個字典,初始化時會遍歷iterable得到其鍵值,
for k, v in iterable:
d[k] = v
dict(**kwargs)->**kwargs是可變函式,其呼叫的語法格式是:dict(key1=value1,key2=value2,...keyn=valuen),例如:dict(name='碼農飛哥', age=17, weight=63)
這三種創建字典的方式都介紹完了,下面就來看看示例說明吧:
#1. 創建字典
d = {'name': '碼農飛哥', 'age': 18, 'height': 185}
print(d)
list = ['name', 'age', 'height']
# 2. fromkeys方法
dict_demo = dict.fromkeys(list)
dict_demo1 = dict.fromkeys(list, '測驗')
print(dict_demo)
print(dict_demo1)
# 通過dict()映射創建字典,傳入串列或者元組
demo = [('name', '碼農飛哥'), ('age', 19)]
dict_demo2 = dict(demo)
print(dict_demo2)
dict_demo21 = dict(name='碼農飛哥', age=17, weight=63)
print(dict_demo21)
運行結果是:
{'name': '碼農飛哥', 'age': 18, 'height': 185}
{'name': None, 'age': None, 'height': None}
{'name': '測驗', 'age': '測驗', 'height': '測驗'}
{'name': '碼農飛哥', 'age': 19}
{'name': '碼農飛哥', 'age': 17, 'weight': 63}
9.2.字典的訪問
說完了字典的創建之后,接著就讓我們來看看字典的訪問,字典不同于串列和元組,字典中的元素不是依次存盤在記憶體區域中的;所以,字典中的元素不能通過索引來訪問,只能是通過鍵來查找對應的值, ,其有兩種不同的寫法,
- 第一種方式的語法格式是
dictname[key],其中dictname表示字典的名稱,key表示指定的鍵,如果指定的鍵不存在的話,則會報KeyError 錯誤, - 第二種方式的語法格式是
dictname.get(key),其中dictname表示字典的名稱,key表示指定的鍵,如果指定的鍵不存在的話,則會回傳None,
舉個栗子說明下吧,下面代碼的意思是根據鍵名為name查找其對應的值,
dict_demo5 = {'name': '碼農飛哥', 'age': 18, 'height': 185}
print(dict_demo5['name'])
print(dict_demo5.get('name'))
print('鍵不存在的情況回傳結果=',dict_demo5.get('test'))
運行結果是:
碼農飛哥
碼農飛哥
鍵不存在的情況回傳結果= None
9.3.添加和修改鍵值對
添加鍵值對的方法很簡單,其語法結構是dictname[key]=value,如果key在字典中不存在的話,則會新增一個鍵值對,如果key在字典中存在的話,則會更新原來鍵所對應的值,依然是舉例說明下:本例中代碼的結果是增加鍵值對 sex='男',把鍵height對應的值改成了190,
# 添加鍵值對
dict_demo6 = {'name': '碼農飛哥', 'age': 18, 'height': 185}
dict_demo6['sex'] = '男'
print('新增鍵值對的結果={0}'.format(dict_demo6))
# 修改鍵值對
dict_demo6['height'] = 190
print('修改鍵值對的結果={0}'.format(dict_demo6))
運行結果是:
新增鍵值對的結果={'age': 18, 'name': '碼農飛哥', 'height': 185, 'sex': '男'}
修改鍵值對的結果={'age': 18, 'name': '碼農飛哥', 'height': 190, 'sex': '男'}
當然修改和洗掉鍵值對也可以通過update方法來實作,其具體的語法格式是:dictname.update(dict) ,其中,dictname為字典的名稱,dict為要修改的字典的值,該方法既可以新增鍵值對,也可以修改鍵值對, 該方法沒有回傳值,即是在原字典上修改元素的,下面例子中就是將鍵name的值改成了飛飛1024,鍵age對應的值改成了25,并新增了鍵值對 like=學習,
# update方法
dict_demo7 = {'name': '碼農飛哥', 'age': 18, 'height': 185, 'width': 100}
dict_demo7.update({'name': '飛飛1024', 'age': 25, 'like': '學習'})
print('update方法回傳結果={}', dict_demo7)
運行結果為:
update方法回傳結果={} {'height': 185, 'like': '學習', 'width': 100, 'name': '飛飛1024', 'age': 25}
9.4.洗掉鍵值對
洗掉鍵值對的方法有三種:
- 第一種是
del dictname[key],使用del關鍵字,其中dictname為字典的名稱,key為要洗掉的鍵,如果鍵不存在的話則會報KeyError錯誤, - 第二種方式是通過pop方法,其語法結構是:
dictname.pop(key),該方法是用于洗掉指定鍵值對,沒有回傳值,如果key不存在的話不會報錯, - 第三種方式是通過popitem方法,其語法結構是:
dictname.popitem(),該方法用于洗掉字典中最后一個鍵值對,舉例說明下吧:
dict_demo10 = {'name': '碼農飛哥', 'age': 18, 'height': 185, 'width': 100}
# 洗掉鍵值對
del dict_demo6['height']
print('洗掉鍵height對之后的結果=', dict_demo6)
# pop()方法和popitem()方法
dict_demo10.pop('width')
print('pop方法呼叫洗掉鍵width之后結果=', dict_demo10)
dict_demo10 = {'name': '碼農飛哥', 'age': 18, 'height': 185, 'width': 100}
dict_demo10.popitem()
print('popitem方法呼叫之后結果=', dict_demo10)
運行結果是:
洗掉鍵height對之后的結果= {'name': '碼農飛哥', 'sex': '男', 'age': 18}
pop方法呼叫洗掉鍵width之后結果= {'name': '碼農飛哥', 'height': 185, 'age': 18}
popitem方法呼叫之后結果= {'name': '碼農飛哥', 'age': 18, 'height': 185}
可以看出popitem方法洗掉的鍵是最后一個鍵width,
詳細內容可以查看【Python從入門到精通】(七)Python字典(dict)讓人人都能找到自己的另一半(鍵值對,成雙成對)
10. 推導式&生成器
10.1.range快速生成串列推導式
串列推導式的語法格式是
[運算式 for 迭代變數 in 可迭代物件 [if 條件運算式] ]
此格式中,[if 條件運算式]不是必須的,可以使用,也可以省略,下面就是輸出1~10的串列的乘積的一個例子:
L = [x * x for x in range(1, 11)]
print(L)
此運算式相當于
L = []
for x in range(1, 11):
L.append(x * x)
print(L)
運行結果是:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
來點復雜的吧,下面就是輸出
print([x for x in range(1, 11) if x % 2 == 0])
運行結果是[2, 4, 6, 8, 10]
再來點復雜的,使用多個回圈,生成推導式,
d_list = [(x, y) for x in range(5) for y in range(4)]
print(d_list)
運行結果是:
[(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3), (4, 0), (4, 1), (4, 2), (4, 3)]
上面代碼,x是遍歷range(5)的迭代變數(計數器),因此該x可迭代5次,y是遍歷range(4)的計數器,因此該y可迭代4次,因此,該(x,y)運算式一共迭代了20次,它相當于下面這樣一個嵌套運算式,
dd_list = []
for x in range(5):
for y in range(4):
dd_list.append((x, y))
print(dd_list)
10.2.range快速生成元組推導式
元組推導式與串列推導式類似,其語法結構是:
(運算式 for 迭代變數 in 可迭代物件 [if 條件運算式] )
此格式中,[if 條件運算式]不是必須的,可以使用,也可以省略,下面就是輸出1~10的元組的乘積的一個例子:
d_tuple = (x * x for x in range(1, 11))
print(d_tuple)
運行結果是:
<generator object <genexpr> at 0x103322e08>
從上面的執行結果可以看出,使用元組推導式生成的結果并不是一個元組,而是一個生成器物件,
使用tuple()函式,可以直接將生成器物件轉換成元組,例如:
d_tuple = (x * x for x in range(1, 11))
print(tuple(d_tuple))
輸出結果是(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
10.4. 字典推導式
字典推導式的語法結構是:
{運算式 for 迭代變數 in 可迭代物件 [if 條件運算式]}
其中[if 條件運算式]可以使用,也可以省略,舉個例子:
key_list = ['姓名:碼農飛哥', '年齡:18', '愛好:寫博客']
test_dict = {key.split(':')[0]: key.split(':')[1] for key in key_list}
print(test_dict)
運行結果是:
{'愛好': '寫博客', '年齡': '18', '姓名': '碼農飛哥'}
10.5.生成器另外一種方式 yield
通過yield關鍵字配合回圈可以做一個生成器,就像下面這樣
def generate():
a = 2
while True:
a += 1
yield a
b = generate()
print(b)
print(next(b))
print(next(b))
print(next(b))
運行結果
<generator object generate at 0x7fc01af19b30>
3
4
5
這里generate方法回傳的就是一個生成器物件generator,因為它內部使用了yield關鍵字,
呼叫一次next()方法回傳一個生成器中的結果,這就很像單例模式中的懶漢模式,他并并不像餓漢模式一樣事先將串列資料生成好,
11. 判斷以及回圈
11.1. 流程控制
流程控制有三種結構,一種是順序結構,一種是選擇(分支)結構,一種是回圈結構,
順序結構:就是讓程式按照從頭到尾的順序執行代碼,不重復執行任何一行代碼,也不跳過任何一行代碼,一步一個腳印表示的就是這個意思,
選擇(分支)結構:就是讓程式根據不同的條件執行不同的代碼,比如:根據年齡判斷某個人是否是成年人,
回圈結構: 就是讓程式回圈執行某一段代碼,順序的流程這里不做介紹了,
11.2. 選擇結構(if,else):
if陳述句
只使用if陳述句是Python中最簡單的形式,如果滿足條件則執行運算式,則跳過運算式的執行,其偽代碼是:
if 條件為真:
代碼塊
如果if 后面的條件為真則執行代碼塊,否則則跳過代碼的執行,
其流程圖是:

就是說只使用if的話,則運算式成立的話執行代碼塊,不成立的話就結束,
下面就是一個簡單的例子,如果滿足a==1這個條件則列印a,否則跳過該陳述句,
a = 1
if a == 1:
print(a)
if else陳述句
if else陳述句是if的變體,如果滿足條件的話則執行代碼塊1,否則則執行代碼塊2,其偽代碼是:
if 條件為真:
代碼塊1
else
代碼塊2
流程圖是:

同時使用if和else的話,則運算式成立的話執行一個代碼塊,運算式不成立的話則執行另一個代碼塊,
舉個簡單的例子吧,
age = 3
if age >= 18:
print('your age is', age)
print('adult')
else:
print("your age is", age)
print('kid')
根據輸入的年齡判斷某人是否是成年人,如果age大于等于18歲,則輸出adult,否則輸出kid,
if elif else陳述句
if elif else陳述句針對的就是多個條件判斷的情況,如果if條件不滿足則執行elif的條件,如果elif的條件也不滿足的話,則執行else里面的運算式,其偽代碼是:
if 條件為真:
運算式a
elif 條件為真:
運算式b
....
elif 條件為真:
表達是n-1
else
運算式n
其中elif可以有多個,但是elif不能單獨使用,必須搭配if和else一起使用,
需要注意的是if,elif和else后面的代碼塊一定要縮進,而且縮進量要大于if,elif和else本身,建議的縮進量是4個空格,同一代碼中所有陳述句都要有相同的縮進, 依然是舉例說明:
bmi = 80.5 / (1.75 * 1.75)
if bmi < 18.5:
print('過輕')
elif 18.5 <= bmi < 25:
print('正常')
elif 25 <= bmi < 28:
print('過重')
elif 28 <= bmi < 32:
print('肥胖')
else:
print('嚴重肥胖')
pass
下面就是根據bmi標準來判斷一個人是過輕,正常還是肥胖,pass是Python中的關鍵字,用來讓解釋器跳過此處,什么都不做,
11.3. while回圈陳述句詳解
while是作為回圈的一個關鍵字,其偽代碼是:
while 條件運算式:
代碼塊
一定要保證回圈條件有變成假的時候,否則這個回圈將成為一個死回圈,即該回圈無法結束, 其流程圖是:

如果while中的運算式成立的話則執行回圈體,否則的話則直接結束,
舉個栗子:計算從1~100的求和,這就是一個經典的運用回圈的場景
sum = 0
n = 1
while n <= 100:
sum = sum + n
n = n + 1
print('sum=', sum)
運行結果是sum= 5050,這個回圈的結束條件是n>100,也就是說當n>100是會跳出回圈,
11.4.for回圈
在介紹range函式時用到了for關鍵字,這里介紹一下for關鍵字的使用,其語法結構是:
for 迭代變數 in 字串|串列|元組|字典|集合:
代碼塊
字串,串列,元祖,字典,集合都可以還用for來迭代,其流程圖是:

for 回圈就是:首先根據in 關鍵字判斷序列中是否有項,如果有的話則取下一項,接著執行回圈體,如果沒有的話則直接結束回圈,
詳細內容可以查看【Python從入門到精通】(十)Python流程控制的關鍵字該怎么用呢?串列推導式,生成器【收藏下來,常看常新】
12. 函式
12.1. 函式定義
函式是按照固定格式封裝組織的可以重復使用的代碼段,它能提高應用的模塊性和代碼的重復利用率,
函式定義的語法格式是:
def 函式名(引數串列):
代碼塊
[return [回傳值]]
函式名:其實就是一個符合Python語法的識別符號,函式名最好能體現該函式的功能,比如: save_user,
形參串列:設定該函式可以接收多少個引數,多個引數之間用逗號(,)分割,需要注意的是沒有引數的話,也需要留一對空的()
[return[回傳值]]:整體作為函式的可選引數,用于設定該函式的回傳值,也就是說,一個函式,
可以有回傳值,也可以沒有回傳值,
12.2 函式呼叫
呼叫函式的語法格式是:
[回傳值]=函式名([形參值])
函式名即指的是要呼叫的函式的名稱,形參值指的是當初創建函式時要求傳入的各個形參的值,
如果該函式有回傳值,我們可以通過一個變數來接收該值,當然也可以不接收,需要注意的是,函式有多少個形參,那么呼叫的時候就需要傳入多少個值,
且順序必須和創建函式時一致,即便該函式沒有引數,函式名后的小括號也不能省略,
舉個栗子吧:
def my_abs(x):
"""
回傳絕對值
:param x:
:return:
"""
if not isinstance(x, (int, float)):
raise TypeError('傳入的資料型別不對')
if x >= 0:
return x
else:
return -x
呼叫代碼是:
x = my_abs(-3)
print(x)
print(my_abs.__doc__)
運行結果是:
3
回傳絕對值
:param x:
:return:
這是一個獲取絕對值的函式,其函式名是my_abs,通過函式名可以讓人大致明白函式的作用,形式引數是x,通過__doc__可以查看函式的說明檔案,其回傳值是處理后的值,
12.3 函式值傳遞和參考傳遞(形參和實參的介紹)
介紹函式值傳遞和參考傳遞之前首先需要了解兩個概念,
- 形式引數(簡稱形參):在定義函式時,函式名后面括號中的引數就是形式引數,可以將形參想象成劇本中的角色,
- 實際引數(簡稱實參):在呼叫函式時,函式名后面括號中的引數稱為實際引數,也就是函式的呼叫者給函式的引數,可以將實參想象成演角色的演員,
函式引數傳遞方式分為兩種:分別是值傳遞和參考傳遞: - 值傳遞:適用于實參型別為不可變型別(字串,數字,元組)
- 參考(地址)傳遞:適用于實參型別為可變型別(串列,字典)
值傳遞和參考傳遞的區別是:函式引數進行值傳遞時,若形參發生改變,不會影響實參的值,而應用傳遞的話,改變形參的值,實參的值也會一同改變,依然是舉例說明:
函式param_test會將形參obj變成 obj+obj,如果是值傳遞則呼叫函式param_test之后,實參的值不變,如果是參考傳遞的話則呼叫param_test之后,實參的值也會變成 obj+obj,
def param_test(obj):
obj += obj
print('形參值為:', obj)
print('*******值傳遞*****')
a = '碼農飛哥'
print('a的值為:', a)
param_test(a)
print('實參的值為:', a)
print("*******參考傳遞*****")
b = [1, 2, 3]
print('b的值為:', b)
param_test(b)
print('實參的值為:', b)
運行結果是:
*******值傳遞*****
a的值為: 碼農飛哥
形參值為: 碼農飛哥碼農飛哥
實參的值為: 碼農飛哥
*******參考傳遞*****
b的值為: [1, 2, 3]
形參值為: [1, 2, 3, 1, 2, 3]
實參的值為: [1, 2, 3, 1, 2, 3]
12.4. Python位置引數
位置引數,有時也被稱為必備引數,指的是必須按照正確的順序將實參傳到函式中,換句話說,呼叫函式時傳入實參的數量和位置必須和定義函式時保持一致,如果不一致的話,則在程式運行時Python解釋器會報TypeError例外,舉個例子,下面演示呼叫函式事引數傳入的數量不對的情況,
def girth(width , height):
return 2 * width+height
#呼叫函式時,必須傳遞 2 個引數,否則會引發錯誤
print(girth(3))
運行之后直接報Traceback錯誤,
Traceback (most recent call last):
File "/Volumes/Develop/Python_learn/PycharmProjects/python_demo_1/demo/function/locate_fun.py", line 6, in <module>
print(girth(3))
TypeError: girth() missing 1 required positional argument: 'height'
傳入引數的位置不對的情況,本例中本想傳入name的值為碼農飛哥,age的值為18,結果入參順序不對導致得到的結果不對,
def print_info(name, age):
print('姓名=' + name + " 年齡=" + str(age))
print_info(18,'碼農飛哥')
那么怎么處理這種情況呢?有兩種方式:
- 嚴格按照形參的數量和位置入參,
- 按照關鍵字引數入參,所謂的關鍵字引數就是指使用形參的名字來確定輸入的引數值,通過此方式制定函式實參時,不再需要與形參的位置完全一致,只要將引數名寫正確即可,還是以上面的函式為例:
利用關鍵字引數來呼叫函式的話則是這樣寫:
def print_info(name, age):
print('姓名=' + name + " 年齡=" + str(age))
print_info(age=18,name='碼農飛哥')
運行結果是:
姓名=碼農飛哥 年齡=18
可以看出關鍵字引數入參時,不需要保證入參的順序跟形參的順序保持一致,

12.5. 默認引數設定
前面介紹的位置引數,就是說呼叫函式時必須要傳入該引數,但是有些場景下我們并不想傳入所有的引數,這種情況下就可以使用默認引數了,不過需要注意的是:指定有默認值的形式引數必須在所有沒默認值的引數的最后,否則會產生語法錯誤,其語法格式是:
def 函式名(...,形參名,形參名=默認值):
代碼塊
下面給出一個示例,該函式是記錄學生的資訊,有兩個有默認值的引數,分別是age和city,它們都被置于函式的形參串列最后處,
def enroll(name, gender, age=6, city='Beijing'):
print('name:', name)
print("gender:", gender)
print("age:", age)
print("city:", city)
print(enroll('張三', '一年級'))
print('************************** ')
print(enroll('李四', '二年級', 7))
運行結果是:
name: 張三
gender: 一年級
age: 6
city: Beijing
None
**************************
name: 李四
gender: 二年級
age: 7
city: Beijing
None
從上面代碼可以看出:1. 可以不用傳入有默認值的引數,2. 如果傳入默認的引數,則會覆寫掉默認值,
12.5. 可變引數
Python函式可變引數(*args,**kwargs),又稱為不定長引數,即傳入函式中的實際引數可以是任意多個,Python定義可以變引數,主要有以下兩種形式:
- 在形參前添加一個
*,格式是*args,表示創建一個名為args的空元組,該元組可以接受任意多個外界傳入的非關鍵字實參,必須以非關鍵字引數的形式給普通引數傳值,否則Python解釋器會把所有引數都優先傳給可變引數, **kwargs表示創建一個名為kwargs的空字典,該字典可以接受任意多個以關鍵字引數賦值的實參,舉個🌰,下面就是根據傳入的值求和,
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
print(calc(10, 9))
運行的結果是:19,
再舉個例子唄:
def record(str, **kwargs):
print('str=', str)
print('kwargs=', kwargs)
record('測驗', name='碼農飛哥', age=20)
record('測驗2')
運行結果是:
str= 測驗
kwargs= {'age': 20, 'name': '碼農飛哥'}
str= 測驗2
kwargs= {}
從上面代碼可以看出,可變引數可以不用傳入,不傳的話則會創建一個空元組或者空字典,
12.6.逆向引數收集
Python不僅有可變引數,將多個引數打包到一個元組或者字典中,還支持逆向引數收集,即直接將串列,元組,字典作為函式引數,不過呼叫函式時要對實參加上*號,就像下面這樣:
def param_test(name, age):
print('name=', name)
print('age=', age)
data = ['碼農飛哥', 18]
param_test(*data)
運行結果是:
name= 碼農飛哥
age= 18
12.7. return函式回傳值
一個函式可以有回傳值,也可以沒有回傳值,有回傳值的語法結構是:
return [回傳值]
回傳值可以指定,也可以省略不寫,如果不寫的話就默認為是None,即空值,
12.8. Python函式回傳多個值的方法
通常情況下,一個函式只有一個回傳值,實際上Python也是如此,
只不過Python函式能以回傳串列或元組的方式,將要回傳的多個值保存到序列中,從而間接實作回傳多個值的目的,
- 在函式中,提前將要回傳的多個值存盤到一個串列或元組中,然后函式回傳該串列或元組
- 函式直接回傳多個值,之間用逗號(,)分隔,Python會自動將多個值封裝到一個元組中,其回傳值仍是一個元組,下面就舉例說明下:
def multi_return():
return_tuple = ('張三', 12)
return return_tuple
def multi_return2():
return '張三', 12
print(multi_return())
result = multi_return2()
print('multi_return2回傳值是=,型別是=', result, type(result))
運行結果是
('張三', 12)
multi_return2回傳值是=,型別是= ('張三', 12) <class 'tuple'>
12.9 Python函式引數傳遞機制
Python函式引數傳遞機制有兩種:分別是值傳遞和參考傳遞,那么這兩種方式有啥區別呢?各自具體的引數傳遞機制又是啥呢?這個章節就將來解答這兩個問題,首先來看看值傳遞,如下代碼定義了一個swap函式,有兩個入參a,b,這個函式的作業就是交換入參a,b的值,
def swap(a, b):
a, b = b, a
print("形參a=", a, 'b=', b)
return a, b
a, b = '碼農飛哥', '加油'
print("呼叫函式前實參的a=", a, 'b=', b)
swap(a, b)
print("呼叫函式后實參的a=", a, 'b=', b)
運行結果是:
呼叫函式前實參的a= 碼農飛哥 b= 加油
形參a= 加油 b= 碼農飛哥
呼叫函式后實參的a= 碼農飛哥 b= 加油
可以看出形參被成功的改變了,但是并沒有影響到實參,這到底是為啥呢?這其實是由于swap函式中形參a,b的值分別是實參a,b值的副本,也就是說在呼叫swap之后python會對入參a,b分別copy一份給swap函式的形參,對副本的改變當然不影響原來的數值啦, 語言的描述是空洞的,畫個圖說明下吧:在Python中一個方法對應一個堆疊幀,堆疊是一種后進先出的結構,上面說的程序可以用下面的呼叫圖來表示:

可以看出當執行a, b = '碼農飛哥', '加油' 代碼是,Python會在main函式堆疊中初始化a,b的值,當呼叫swap函式時,又把main函式中a,b的值分別copy一份傳給swap函式堆疊,當swap函式對a,b的值進行交換時,也就只影響到a,b的副本了,而對a,b本身沒影響,
但是對于串列,字典這兩的資料型別的話,由于資料是存盤在堆中,堆疊中只存盤了參考,所以在修改形參資料時實參會改變,,如下代碼演示:
def swap(dw):
# 下面代碼實作dw的a、b兩個元素的值交換
dw['a'], dw['b'] = dw['b'], dw['a']
print("swap函式里,a =", dw['a'], " b =", dw['b'])
dw = {'a': '碼農飛哥', 'b': '加油'}
print("呼叫函式前外部 dw 字典中,a =", dw['a'], " b =", dw['b'])
swap(dw)
print("呼叫函式后外部 dw 字典中,a =", dw['a'], " b =", dw['b'])
運行結果是:
呼叫函式前外部 dw 字典中,a = 碼農飛哥 b = 加油
swap函式里,a = 加油 b = 碼農飛哥
呼叫函式后外部 dw 字典中,a = 加油 b = 碼農飛哥
可以清晰的看出呼叫函式之后傳入的實參dw的值確實改變了,這說明他是參考傳遞的,那么參考傳遞與值傳遞有啥區別呢?

從上圖可以看出字典的資料是存盤在堆中的,在main函式的堆疊中通過參考來指向字典存盤的記憶體區域,當呼叫swap函式時,python會將dw的參考復制一份給形參,當然復制的參考指向的是同一個字典存盤的記憶體區域,當通過副本參考來操作字典時,字典的資料當然也改變,綜上所述:參考傳遞本質上也是值傳遞,只不過這個值是指參考指標本身,而不是參考所指向的值, 為了驗證這個結論我們可以稍微改造下上面的代碼:
def swap(dw):
# 下面代碼實作dw的a、b兩個元素的值交換
dw['a'], dw['b'] = dw['b'], dw['a']
print("swap函式里,a =", dw['a'], " b =", dw['b'])
dw = None
print("洗掉形參對字典的參考",dw)
dw = {'a': '碼農飛哥', 'b': '加油'}
print("呼叫函式前外部 dw 字典中,a =", dw['a'], " b =", dw['b'])
swap(dw)
print("呼叫函式后外部 dw 字典中,a =", dw['a'], " b =", dw['b'])
運行的結果是:
呼叫函式前外部 dw 字典中,a = 碼農飛哥 b = 加油
swap函式里,a = 加油 b = 碼農飛哥
洗掉形參對字典的參考
呼叫函式后外部 dw 字典中,a = 加油 b = 碼農飛哥
洗掉了形參對字典的參考后,實參還是能獲取到字典的值,這就充分說明了傳給形參的是實參的參考的副本,
詳細內容可以查看:【Python從入門到精通】(十一)Python的函式的方方面面【收藏下來保證有用!!!】
【Python從入門到精通】(十二)Python函式的高級知識點,更深入的吸收知識,不做知識的牙簽(不淺嘗輒止)【收藏下來保證有用!!!】
13.面向物件的開發
13.1. 面向物件的概念
面向物件(Object-oriented Programming,簡稱 OOP)的思想其本質上是一種對事物抽象封裝的思想,
封裝就是將隱藏具體的細節內容,就好像用戶使用電腦,只需要簡單的操作鍵盤和滑鼠就可以實作一些功能,而無需了解計算機內部是如何實作的,
而在Python語言中的面向物件的封裝是將描述特性的資料(屬性)和描述行為(方法)封裝在一起,
比如現在要描述一個人的話,我們首先會從兩方面描述:
- 從表面特征描述:例如: 膚色,身高,體重,年齡
- 從所具有的行為描述:例如:會吃飯,會走路,會說話,
如果通過代碼來實作的話,其表面特征可以用變數來表示,其行為可以用各種方法來表示,
class Person:
# 膚色
colour = '黃色'
# 身高
height = '180cm'
# 體重
weight = '60KG'
# 年齡
age = 18
def eat(self):
print('吃飯')
def walk(self):
print('走路')
def speak(self):
print('說話')
zhangsan=Person()
lisi=Person()
wanger=Person()
通過構建一個Person類就可以將人的特性和行為都封裝起來,人都是有共性的,也就是說可以通過這個類來創建出各種不同的物件,這里不得不說面向物件里的幾個核心概念,
13.2. 類
類可以理解成可以理解成是一個模板,根據這個模板可以創建出許許多多的具體物件,可以把類想象成是一個模子亦或者是一張圖紙,
13.3.物件
類并不能直接直接被使用, 通過類創建出來的具體的實體(又稱為物件)才能被使用,這就像汽車的圖紙與車本身的關系,大多數情況下,圖紙并不能被普通人所使用,但是通過圖紙創建出的一輛輛汽車能被使用,就像上面通過Person類可以創造出張三,李四,王二麻子,
zhangsan=Person()
lisi=Person()
wanger=Person()
13.4.屬性
類中所有的變數都是類的屬性,比如上面的身高height,體重weight等屬性,
13.5.方法
類中的所有函式都被稱為方法,不過,和函式有所不同的是類方法至少要包含一個 self 引數(后續會做詳細介紹),就像上面Person類的eat方法,walk方法,另外,就是類中的方法不能單獨使用,必須通過類創建的物件呼叫,
13.5. 類的定義
類的定義必須要通過class關鍵字修飾,類名的命名規范建議是每個單詞的首字母大寫,其余字母小寫,
class 類名:
多個(>=0)類屬性
多個(>=0)類的方法
就像上面的Person類,其有屬性height,weight等幾個屬性,也有eat,walk等多個方法,
13.6.構造方法
不過這里有個隱藏的方法,那就是類構造方法__init__(),該方法是一個特殊的類實體方法,Python是通過該方法來創建類的實體,該方法的方法名是固定的,開頭和結尾各有2個下劃線并且中間不能有空格,
在創建類之后,Python會創建一個默認的構造方法__init__(),如果沒有手動添加__init__()方法的話則使用默認的構造方法創建類的實體(即物件), __init__()方法的語法結構是:
def __init__(self,...):
代碼塊
__init__()方法可以有多個引數,但必須包含一個名為self的引數,并且self的引數必須為第一個引數,如果沒有其他引數則是默認構造方法,以上面的Person類為例,可以手動添加一個帶其他引數的__init__()方法,
def __init__(self, head):
self.hand = head
如果手動添加了其他的__init__()方法,則會覆寫掉默認的構造方法,創建物件時會使用你手動添加的__init__()方法,
13.7. 類物件的創建和使用
創建類物件的程序被稱為類的實體化,其語法結構是類名(引數), 當使用的是默認構造方法時或者添加的__init__()方法中僅有一個self引數,則創建類物件時引數可以不寫,
但是如果__init__()方法中除了self引數還有其他引數的話,則引數必傳,不然,就會報類似于下面的錯誤,
Traceback (most recent call last):
File "/Python_learn/PycharmProjects/python_demo_1/demo/oop/person.py", line 15, in <module>
person = Person()
TypeError: __init__() missing 1 required positional argument: 'head'
這里就必須要傳入head引數,
person = Person('頭')
person.eat()
13.8. self引數的詳解
前面幾個章節我們多次提到了self引數,那么這個引數到底有啥作用呢?我們知道類是一個模板,通過類這個模板可以創建出許許多多的物件,那么該如何區分這些物件呢?就像每個人都有自己的名字一樣,實體化的物件在Python中也有屬于自己的代號(自身的參考),而python正是通過self引數來系結呼叫物件本身,說白了就是通過self引數可以知道當前的方法是被誰呼叫了,專業一點的解釋是,當某個物件呼叫類方法時,該方法會把自身的參考作為第一個引數自動傳給該方法,
class Person:
def __init__(self):
print('正在執行構造方法')
def eat(self):
print(self, '正在吃飯')
zhangsan = Person()
zhangsan.eat()
lisi = Person()
lisi.eat()
其運行結果是:
正在執行構造方法
<__main__.Person object at 0x1031cd0f0> 正在吃飯
正在執行構造方法
<__main__.Person object at 0x103338da0> 正在吃飯
這里實體化了zhangsan和lisi兩個物件,當他們同時呼叫eat方法時,Python會將其自身的參考系結到self引數上,可以明顯的看出不同的呼叫者其self引數是不同的,這里的self引數就相當于是Java里的this引數,
13.9. 類的變數
類的變數有三種,分別是類變數,實體變數和區域變數,下面直接舉個例子吧!下面代碼定義了一個Person類,該類定義了了三個變數,兩個方法,
class Person:
name = '張三'
def __init__(self):
self.age = 18
def get_info(self):
sex = '男'
print('區域變數sex=', sex)
return sex
print('類屬性name=', Person.name)
person = Person()
print('實體屬性age=', person.age)
person.get_info()
運行結果是:
類屬性name= 張三
實體屬性age= 18
區域變數sex= 男
這里name,age以及sex三個變數分別是三種不同的變數,下面就分別介紹一下
其中定義在了類體中,所有函式之外;此范圍定義的變數就稱為類變數,就像上面代碼中的name變數,類變數屬于整個類,可以直接通過類名.變數名來獲取,就像上面的Person.name,在實際開發中類變數用的比較少,
定義在類體中,函式內部并且以self.變數名定義的變數就稱為實體變數,就像上面代碼中的age變數,實體變數只作用于呼叫方法的物件,只能通過物件名訪問,無法通過類名訪問,呼叫方式如上面實體中的person.age,
定義在類體中,函式內部的變數以"變數名=變數值"的方式定義的變數就稱為區域變數,就像上面代碼中的sex變數,區域變數只能在函式內部使用,
詳細內容可參考:
【Python從入門到精通】(十三)Python面向物件的開發,沒有物件怎么能行呢?
【Python從入門到精通】(十四)Python面向物件的開發2,封裝,多繼承,多型都了解了么
14. 例外機制
14.1. 例外類之間的繼承關系
例外類的個數和種類有很多,但是這些例外類之間不是相互獨立的,它們的繼承關系如下圖所示:

所有的例外類都繼承自基類BaseException類,這個基類BaseException類的父類是object類,基類BaseException類有四個子類,分別是GeneratorExit類,Exception類,SystemExit類,KeyboardInterrupt類,其中Exception類又是實際開發中最常接觸到的例外類,程式中可能出現的各種例外,都繼承自Exception.它以及它的子類構成了Python例外類結構的基礎,其余三個例外類比較少見,
Exception類同樣有三個子類,ArithmeticError類用來處理數字例外,BufferError用來處理字符例外,LookupError用來記錄關鍵字例外,
- SyntaxError 語法錯誤,說白了就是撰寫的代碼不符合語法格式
- IndentationError:縮進錯誤,Python是根據縮進來決定代碼的作用范圍的,
- TypeError:型別錯誤,如果資料型別用錯則會報這個錯誤,
- NameError:變數名錯誤,忘記定義變數就會報這種錯誤
- AttributeError:屬性錯誤,特性參考和賦值失效會引發屬性錯誤
- IndexError:索引錯誤,使用的索引不存在,或索引超出序列范圍,
- KeyError:關鍵字錯誤,使用了映射中不存在的關鍵字(鍵)時引發的關鍵字錯誤,
14.2. 例外機制的使用
介紹完了各種例外類的繼承關系,接著就是介紹例外類的使用了,例外類的作用就是捕獲程式運行時的各種例外的,如果不手動捕獲例外的話,Python程式在遇到例外之后就會拋出例外并停止程式的運行,捕獲例外的語法結構如下:
try:
可能產生例外的代碼塊
except [ (Error1, Error2, ... ) [as e] ]:
處理例外的代碼塊1
except [ (Error3, Error4, ... ) [as e] ]:
處理例外的代碼塊2
except [Exception]:
處理其它例外
括號[]括起來的部分可以使用,也可以省略,其中:
- (Error1, Error2,…) 、(Error3, Error4,…):其中,Error1、Error2、Error3和Error4都是具體的例外型別,顯然,一個except塊可以同時處理多種例外,
- [as e]:作為可選引數,表示給例外型別起一個別名e,這樣做的好處是方便在except塊中呼叫例外型別,
它的執行程序是:
- 首先執行try中的代碼塊,如果執行程序中出現例外,系統會自動生成一個例外型別,并將該例外提交給Python解釋器,此程序稱為捕獲例外,
- 當Python解釋器收到例外物件時,會尋找能處理該物件的except塊,如果找到合適的except塊,則把該例外交給該except塊處理,這個程序稱為處理例外,如果Python解釋器找不到處理例外的except塊,則程式運行終止,Python解釋器也將退出,
還是舉個簡單的例子:
try:
print('try....')
r = 10 / 0
print('result', r)
except ZeroDivisionError as e:
print('ZeroDivisionError', e)
except Exception as e:
print('Exception', e)
print('END')
運行結果是:
try....
ZeroDivisionError division by zero
END
可以看出當ZeroDivisionError例外能夠匹配Python解釋器自動生成的例外程式就會進入該except塊中,這里需要注意的如果將Exception的except塊寫到ZeroDivisionError的except塊前面的話,則運行結果會變成下面的結果,這是因為
ZeroDivisionError是Exception類的子類,
try....
Exception division by zero
END
14.3. 獲取特定例外資訊
每種例外型別都提供了如下幾個屬性和方法,通過呼叫它們就可以獲取當前例外型別的相關資訊,
- args: 回傳例外的錯誤編號和描述符號
- str(e):回傳例外資訊,但不包括例外資訊的型別,
- repr(e):回傳較全的例外資訊,包括例外資訊的型別,
try:
print('try....')
r = 10 / 0
print('result', r)
except ZeroDivisionError as e:
print('', e.args)
print('', str(e))
print('', repr(e))
運行結果是:
try....
('division by zero',)
division by zero
ZeroDivisionError('division by zero')
14.4. finally
finally代碼塊,無論try塊是否發生例外,最終都要進入finally陳述句中,并執行其中的代碼塊,在實際開發中可以將資源回收的作業放入finally塊中,這樣可以保證當不可預知的例外發生時,資源可以被正常回收,這里的資源指的是資料庫連接,檔案流的關閉等,
還是以上面的代碼為例:
try:
print('try....')
r = 10 / 0
print('result', r)
except Exception as e:
print('Exception', e)
except ZeroDivisionError as e:
print('ZeroDivisionError', e)
finally:
print('發生例外時finally陳述句塊執行...')
print('END')
try:
print('try....')
r = 10 / 1
print('result', r)
except Exception as e:
print('Exception', e)
except ZeroDivisionError as e:
print('ZeroDivisionError', e)
finally:
print('沒發生例外時finally陳述句塊執行...')
print('END')
運行結果是:
try....
Exception division by zero
發生例外時finally陳述句塊執行...
END
try....
result 10.0
沒發生例外時finally陳述句塊執行...
END
可以看出,無論try中是否發生例外,finally塊中的代碼都會執行,當然finally塊只需要搭配try塊使用就可以了,
14.5. raise用法
有時候我們自定義了一個業務例外類,當觸發該例外時將該例外拋給其呼叫者,又或者當我們捕獲到一個未知例外時,需要將該例外封裝并拋給其呼叫者,這時候就可以使用raise關鍵字了,其語法結構是:
raise [exceptionName [(reason)]]
其有三種用法:
- raise: 單獨一個raise,該陳述句引發當前背景關系中捕獲的例外(比如except塊中)或默認引發RuntimeError例外,
- raise例外名稱:raise后帶一個例外類名稱,表示引發執行型別的例外,
- raise例外類名稱(描述資訊):在引發指定型別的例外的同時,附帶例外的描述資訊,
try:
a = input("輸入一個數:")
# 判斷用戶輸入的是否為數字
if (not a.isdigit()):
raise ValueError("a 必須是數字")
except ValueError as e:
print("引發例外:", repr(e))
當輸入一個字母或者漢字時就會拋出ValueError例外,
當然也可以在except中使用raise,將例外重新拋出,
詳細內容可參考:
【Python從入門到精通】(十五)Python例外機制,給代碼加上安全TAO,不放過一個例外
【Python從入門到精通】(十六)Python例外機制2,正確使用Python例外機制的姿勢是啥
15. 模塊和包
15. 1.模塊的介紹
什么是模塊呢?簡單理解的話:模塊就是一個后綴名是.py的模板檔案,模塊主要是用于封裝函式和變數,我們可以將實作某一個功能的所有函式封裝放在同一個.py檔案中以作為一個模塊提供給其他模塊使用,比如操作日期的模塊time,其對應模板檔案就是time.py,
15.2.匯入模塊
匯入模塊的方式有兩種:
import 模塊名1 [as 別名1], 模塊名2 [as 別名2],…
使用這種格式的import陳述句,會匯入指定模塊中所有的成員(包括變數,函式、類等)from 模塊名 import 成員名1 [as 別名1],成員名2 [as 別名2],…
使用這種格式的import陳述句,只會匯入模塊中指定的成員,而不是全部成員,
用第一種方式匯入time模塊,可以使用該模塊下所有的函式
import time
# 記錄當前的毫秒數
start_time = time.time()
print(start_time)
# 睡眠4秒
time.sleep(4)
# 計算耗時
print(time.time() - start_time)
可以看出匯入time之后可以使用其模塊內的所有成員
用第二種方式匯入time模塊中的sleep函式,則time函式是使用不了的,
from time import sleep
print(sleep())
PS:如果模塊名中出現空格就無法使用import引入模塊了,這是因為Python 是以空格來隔離一行陳述句中的不同元素的,針對有空格的模塊名的匯入,可以通過__import__函式來匯入,比如:現在有一個名為hello test.py的模板檔案,可以以__import__("hello test") 這種方式匯入,
15.3.自定義模塊
自定義模塊說白了就是自行創建一個模板檔案,然后使用其檔案名作為模塊名匯入到其他模板檔案中,定義了一個名為hello的模板檔案,然后在hello_test.py檔案中匯入,
hello.py檔案
def say():
print('苦逼程式員唱著苦逼的歌')
say()
print(__name__)
這里需要注意的是hello.py檔案是放在Python專案的根目錄下,
hello_test.py檔案
import hello
hello.say()
運行hello_test之后,輸出的結果是:
苦逼程式員唱著苦逼的歌
hello
苦逼程式員唱著苦逼的歌
我們看到輸出了兩遍,這顯然不是我們想要的結果,我們期望的是say函式只執行一遍,也就是在hello_test呼叫的地方執行,而不是在hello.py檔案中還執行一遍,那么這個問題該如何處理呢?
【Python從入門到精通】(十七)Python模塊和包的基本使用,簡單一文,一分鐘看完
16. 檔案的基本操作
16.1. open函式
Python對檔案的操作比較方便,沒有像Java那樣整那么多IO操作類,首先歡迎open函式粉墨登場,該函式主要是用來創建或者打開檔案的,其語法格式是:
file=open(filename, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)
file: 表示要創建的檔案物件,
file_name:要創建或打開的檔案的檔案名稱,該名稱要用引號括起來,需要注意的是,如果要打開的檔案和當前執行的代碼檔案位于同一目錄,則直接寫檔案名即可,否則此引數需要指定打開檔案所在的完整路徑,
mode:可選引數,用于指定檔案的打開模式,可選的打開模式如下表所示,如果不寫,則默認以只讀(r)模式打開檔案,
buffering:可選引數,用于指定對檔案做讀寫操作時,是否使用緩沖區,如果buffering引數的值為0(或者False)則表示在打開指定檔案時不使用緩沖區;如果buffering引數值為大于1的整數,該整數用于緩沖區的大小(單位是位元組);如果buffering引數的值為負數,則表示使用默認的緩沖區大小,默認情況下open函式是打開緩沖區的,
encoding: 手動設定打開檔案時所使用的編碼格式,不同平臺的ecoding引數值也不同,以Windows為例,其默認的GBK編碼,
表1. open函式支持的檔案打開模式
| 模式 | 含義 | 注意事項 |
|---|---|---|
| r | 只讀模式打開檔案,讀檔案內容的指標會放在檔案的開頭 | 操作的檔案必須存在 |
| rb | 以二進制格式,采用只讀模式打開檔案,讀檔案內容的指標位于檔案的開頭,一般用于非文本檔案,如圖片檔案、音頻檔案 | 操作的檔案必須存在 |
| r+ | 打開檔案后,既可以從頭讀取檔案內容,也可以從開頭向檔案中寫入新的內容,寫入的新內容會覆寫檔案中等長度的原有內容 | 操作的檔案必須存在 |
| rb+ | 以二進制格式、采用讀寫模式打開檔案,讀寫檔案的指標會放在檔案的開頭,通常針對非文本檔案 (如音頻檔案) | 操作的檔案必須存在 |
| w | 以只寫模式打開檔案,若該檔案存在,打卡時會清空檔案中原有的內容 | 若檔案存在,會清空其原有內容(覆寫檔案);反之,則創建新檔案, |
| wb | 以二進制格式,只寫模式打開檔案,一般用于非文本檔案(如音頻檔案) | 若檔案存在,會清空其原有內容(覆寫檔案);反之,則創建新檔案, |
| w+ | 打開檔案后,會對原有內容進行清空,并對該檔案有讀寫權限 | 若檔案存在,會清空其原有內容(覆寫檔案);反之,則創建新檔案, |
| wb+ | 以二進制格式,讀寫模式打開檔案,一般用于非文本檔案 | |
| a | 以追加模式打開一個檔案,對檔案只有寫入權限,如果檔案已經存在,檔案指標將放在檔案的末尾(即新寫入內容會位于已有內容之后);反之,則會創建新檔案 | |
| ab | 以二進制格式打開檔案,并采用追加模式,對檔案只有寫權限,如果該檔案已存在,檔案指標位于檔案末尾(新寫入檔案會位于已有內容之后);反之,則創建新檔案 | |
| a+ | 以讀寫模式打開檔案,如果檔案存在,檔案指標放在檔案的末尾(新寫入檔案會位于已有內容之后);反之,則創建新檔案 | |
| ab+ | 以二進制模式打開檔案,并采用追加模式,對檔案具有讀寫權限,如果檔案存在,則檔案指標位于檔案的末尾(新寫入檔案會位于已有內容之后);反之,則創建新檔案 |
從上表我們可以得出如下結論:
- 模式中帶b的就是以二進制格式打開檔案,比如模式 rb;wb;ab
- 模式中帶+的就是以讀寫模式打開檔案,說白了就是既可以讀檔案也可以寫檔案,比如模式 r+;w+;a+;rb+;wb+;ab+
- 不帶b,或者+的模式,比如r模式就是只讀,w模式就是只寫,a模式就是只追加檔案,
16.2.檔案的讀取
檔案的讀取有三種方法:
- file.read(size)方法,逐個位元組或者字符讀取檔案中的內容,file表示已打開的檔案物件,size作為一個可選引數,用于指定一次最多可讀取的字符(位元組)個數,如果省略,則默認一次性讀取所有內容,
- file.readline(size)方法:逐行讀取檔案中的內容,file表示已打開的檔案物件,size為可選引數,用于指定讀取每一行時,一次最多讀取的字符(位元組)數,可以通過回圈的方式來讀取檔案中的全部內容
- file.readlines() 方法:一次性讀取檔案中多行內容,讀取的結果以串列的形式回傳,
舉個例子吧!現創建一個名為my_file.txt的檔案,檔案中有如下內容:
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/
下面分別用介紹的三種方法來讀取該檔案中的所有內容:
# 讀取文本檔案
print("*********read方法讀取全部內容**********")
f = open('my_file.txt', encoding='utf-8')
print(f.read())
f.close()
print("*********readline方法讀取全部內容**********")
f = open('my_file.txt', encoding='utf-8')
line_txt = f.readline()
while line_txt:
print(line_txt)
line_txt = f.readline()
f.close()
print("*********readline方法讀取全部內容**********")
f = open('my_file.txt', encoding='utf-8')
print(f.readlines())
運行的結果是:
*********read方法讀取全部內容**********
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/
*********readline方法讀取全部內容**********
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/
*********readline方法讀取全部內容**********
['全網同名:碼農飛哥\n', '這是Python系列的第十八篇文章\n', 'https://feige.blog.csdn.net/']
一般而言,readline函式和readlines函式用于讀取文本檔案,而read函式則即可讀取文本檔案又可以讀取非文本檔案,
16.3.檔案寫入
現有檔案write_file.txt,檔案中有如下兩行內容,
碼農飛哥
這是Python系列的第十九篇文章
- file.write(str): 其中,file表示已經打開的檔案物件;str表示要寫入檔案的字串(或位元組串,僅適用寫入二進制檔案中)
需要注意的是在使用write()向檔案中寫入資料,需保證使用open()函式是以r+、w、w+、a或者a+模式打開檔案,否則執行write()函式會拋出io.UnsupportedOperation 錯誤,
file = open('write_file.txt', mode='w', encoding='utf-8')
file.write('日拱一卒')
file.close()
通過w模式打開執行write方法寫入日拱一卒之后,檔案中原有的內容就被清空了,這里需要注意的這里要指定編碼為utf-8,因為在window下檔案的默認open函式指定的默認編碼格式是gbk,用utf-8格式打開檔案可能會出現亂碼,

- file.writelines(str):
其中,file表示已經打開的檔案物件,str表示要寫入檔案的內容,可以支持多行資料的寫入,
file = open('write_file.txt', mode='w', encoding='utf-8')
file2 = open("my_file.txt", encoding='utf-8')
file.writelines(file2.read())
file.close()
這里讀取的檔案的編碼和寫入的檔案編碼需要保持一致,不然,就會報UnicodeDecodeError編碼錯誤,寫入之后會清空檔案的原有內容,
16.4.各種模式的介紹
只讀模式(只讀資料r)
創建一個名為my_file.txt的檔案,檔案有如下內容:
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/
- 通過二進制的方式(rb)打開圖片,針對二進制檔案(圖片,視頻等)的話只能使用rb方式來讀取檔案,
f = open('my_file.txt', 'rb')
f_bytes = f.read()
print(f_bytes)
print(f_bytes.decode('utf-8'))
f.close()
運行結果是:
b'\xe5\x85\xa8\xe7\xbd\x91\xe5\x90\x8c\xe5\x90\x8d\xef\xbc\x9a\xe7\xa0\x81\xe5\x86\x9c\xe9\xa3\x9e\xe5\x93\xa5\n\xe8\xbf\x99\xe6\x98\xafPython\xe7\xb3\xbb\xe5\x88\x97\xe7\x9a\x84\xe7\xac\xac\xe5\x8d\x81\xe5\x85\xab\xe7\xaf\x87\xe6\x96\x87\xe7\xab\xa0\nhttps://feige.blog.csdn.net/'
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/
*********readline方法讀取全部內容**********
全網同名:碼農飛哥
這是Python系列的第十八篇文章
只寫模式(重寫資料w)
- 以二進制的方式寫入資料
如下代碼就是將圖片demo.jpg的內容寫入到圖片demo1.jpg中,如果demo1.jpg檔案不存在則會先創建一個檔案,
write_file = open('demo1.jpg', mode='wb')
read_file = open('demo.jpg', mode='rb')
write_file.write(read_file.read())
read_file.close()
write_file.close()
- 只寫模式追加資料
如下就是在append_file.txt檔案中追加好好加油資料,a的話是只寫模式
append_file = open('append_file.txt', mode='a', encoding='utf-8')
append_file.write("\n好好加油")
append_file.close()
讀寫模式(追加資料a)
前面介紹的以w開頭的模式,如果被寫入檔案中原有資料則會被覆寫,如果不想原有資料被覆寫可以使用追加寫入資料的方式,
讀寫模式下可以呼叫read()方法進行讀,也可以呼叫write方法進行寫,
read_write_file = open('append_file.txt', mode='r+', encoding='utf-8')
print(read_write_file.read())
read_write_file.write('\n努力向上,天天向上')
print(read_write_file.read())
read_write_file.close()
運行結果是:
全網同名:碼農飛哥
這是Python系列的第十八篇文章
https://feige.blog.csdn.net/

需要注意的是read方法只能呼叫一遍,read函式只能讀取寫入之前的資料,如果要讀取寫入之后的資料需要重新呼叫open函式,
16.5. with as用法詳解
我們注意檔案操作完之后需要手動呼叫close()方法關閉檔案流,實際開發中建議將呼叫close函式的代碼塊放在finally塊中以防止出現例外導致檔案流不能被關閉,標準的使用方式是:
f = open('my_file.txt', encoding='utf-8')
try:
print(f.read())
finally:
f.close()
那么,有沒有更加便捷的方式呢?答案是有的:那就是通過with as 陳述句來操作檔案,其語法格式是:
with 運算式 [as target]:
代碼塊
此格式中,用[]括起來的部分可以使用,也可以省略,其中,target引數用于指定一個變數,該陳述句會將運算式指定的結果保存到該變數中,with as 陳述句中的代碼塊如果不想執行任何陳述句,可以直接使用 pass 陳述句代替,
用with as改造上面的代碼就是:
with open('my_file.txt', encoding='utf-8') as f:
print(f.read())
不用手動關閉檔案流,
【Python從入門到精通】(十八)Python的檔案夾操作,創建檔案夾復制檔案等等
【Python從入門到精通】(十九)Python對檔案的讀寫操作一覽表,非常實用,非常簡單
17. 執行緒
17.1. 行程和執行緒
行程是什么?行程說白了就是應用程式的執行實體,你在電腦上聽著歌兒,敲著代碼,掛著微信,這些任務都是由不同的應用程式來執行,作業系統會給每個應用程式分配不同的行程,通過CPU的調度來使這些動作可以“同時”進行,這里說是同時進行實際上不是的,因為CPU在同一時間內只能有一條指令執行,但是因為CPU執行的速度太快了,給用戶的感覺就是在同時進行,行程是可以占用物理記憶體的,
執行緒是行程的組成部分,一個行程可以擁有多個執行緒.CPU調度行程的最小粒度是執行緒,其中由主執行緒來完成從開始到結束的全部操作,其他執行緒在主執行緒運行時被創建或者結束,
主執行緒在程式初始化之后就會被創建,如果一個程式只有一個主執行緒就稱為單執行緒,如果有多個執行緒則稱之為多執行緒, 創建多個執行緒之后,每個執行緒的執行都是獨立的,
17.2.執行緒的創建
Python創建執行緒的方式有兩種: 創建執行緒都需要引入threading模塊,首先讓我們來看看Thread類的構造方法,
__init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
此構造方法中,以上所有引數都是可選引數,即可以使用,也可以忽略,
其中各個引數的含義如下:
- group:指定所創建的執行緒隸屬于哪個執行緒組,
- target:指定所創建的執行緒要呼叫的目標方法,
- args:以元組的方式,為target指定的方法傳遞引數,如果傳入的是元組中有多個引數的話則傳入方式是
(arg1,arg2,....argn,) - kwargs:以字典的方法,為target指定的方法傳遞引數,
- daemon:指定所創建的執行緒是否為后臺執行緒,
- name: 指定執行緒的名稱
- 第一種方式:就是直接呼叫Thread類的構造方法創建一個執行緒的實體,
import threading
# 定義執行緒要呼叫的方法
def async_fun(*add):
for arc in add:
print(threading.current_thread().getName() + " " + arc)
my_tuple = ("碼農飛哥", "好好學習", "早日突破職業瓶頸")
# 創建執行緒
thread = threading.Thread(target=async_fun, args=my_tuple)
# 啟動執行緒
thread.start()
for i in range(4):
print(threading.current_thread().getName() + "執行" + str(i)+ "次")
運行結果是:
Thread-1 碼農飛哥MainThread執行0次
MainThread執行1次
MainThread執行2次
MainThread執行3次
Thread-1 好好學習
Thread-1 早日突破職業瓶頸
如上方法就是實體化一個執行緒Thread-1讓他異步呼叫async_fun方法,可以看出主執行緒MainThread和執行緒Thread-1是交替呼叫的(每次的執行結果都不同),這說明了這兩個執行緒是交替獲得CPU的執行權限的,需要特別注意的是執行緒必須要呼叫start()方法才能執行,
2. 第二種方式:就是繼承threading.Thread,然后,重寫run方法,
import threading
class MyThread(threading.Thread):
def __init__(self, add):
threading.Thread.__init__(self)
self.add = add
# 重寫run()方法
def run(self):
for arc in self.add:
print(threading.current_thread().getName() + " " + arc)
my_tuple = ("碼農飛哥", "好好學習", "早日突破職業瓶頸")
thread = MyThread(my_tuple)
thread.start()
for i in range(4):
print(threading.current_thread().getName() + "執行" + str(i)+ "次")
運行結果是:
Thread-1 碼農飛哥MainThread執行0次
MainThread執行1次
Thread-1 好好學習
Thread-1 早日突破職業瓶頸
MainThread執行2次
MainThread執行3次
這里定義了了MyThread類,并重寫了run方法,run方法就是執行緒真正要執行的任務,相當于上例中async_fun函式的內容移到了run方法中,
17.3.執行緒的生命周期
說完了如何創建一個執行緒接下來讓我們來看看執行緒的生命周期,一個執行緒一共有五個狀態,
分別是新建狀態(初始化狀態),就緒狀態,運行狀態,阻塞狀態以及死亡狀態,狀態之間的關系圖如下:

- 新建狀態:執行緒被剛剛創建,且未呼叫start()方法時的狀態,即上面的threading.Thread(target=async_fun, args=my_tuple) 時的執行緒狀態,
- 就緒狀態:呼叫了start()方法之后,執行緒就由新建狀態轉成就緒狀態,就緒狀態就是表示執行緒可以是隨時準備獲取CPU的狀態,
以下幾種情況下執行緒會進入就緒狀態:1.sleep()方法規定的時間已過,2.呼叫了notify()方法或者notify_all()方法發出通知,3.其他執行緒釋放了該同步鎖,并由該執行緒獲得, - 運行狀態:當就緒狀態的執行緒獲得了CPU的使用權之后,并開始執行target引數執行的目標函式或者run()方法,就表明執行緒處于運行狀態,
- 阻塞狀態:獲得CPU的調度但是沒有執行完任務的執行緒.
以下幾種情況下:執行緒都會進入阻塞狀態:1.呼叫了sleep()方法,2.呼叫了wait()方法,并且等待條件滿足,3.執行緒試圖獲取某個物件的同步鎖時,如果該鎖被其他執行緒占用,則當前執行緒進入阻塞狀態, - 執行緒死亡狀態:執行緒的任務執行完成或者程式執行程序中發生例外,執行緒都會進入死亡狀態,
17.4.Thread.join()用法詳解,
join()方法的功能是在程式指定位置,優先讓該方法的呼叫者使用CPU資源,即呼叫執行緒等待該執行緒完成后,才能繼續用下運行,
該方法的語法格式如下:thread.join( [timeout])
其中,thread為Thread類或其子類的實體化物件;timeout引數作為可選引數,
其功能是指定thread執行緒最多可以霸占CPU資源的時間,如果省略,則默認直到thread執行結束才釋放CPU資源,
import threading
# 定義執行緒要呼叫的方法
def async_fun(*add):
for arc in add:
print(threading.current_thread().getName() + " " + arc)
my_tuple = ("碼農飛哥", "好好學習", "早日突破職業瓶頸")
# 創建執行緒
thread = threading.Thread(target=async_fun, args=my_tuple, name="執行緒1")
thread2 = threading.Thread(target=async_fun, args=my_tuple, name='執行緒2')
# 啟動執行緒
thread.start()
# 等待執行緒1執行完
thread.join()
# 執行緒1執行完之后啟動執行緒2
thread2.start()
thread2.join()
運行結果是:
執行緒1 碼農飛哥
執行緒1 好好學習
執行緒1 早日突破職業瓶頸
執行緒2 碼農飛哥
執行緒2 好好學習
執行緒2 早日突破職業瓶頸
如上有執行緒1和執行緒2兩個執行緒,在執行緒1呼叫thread.join()之后執行緒1和執行緒2的執行由并行改成了串行,也就是說必須是執行緒1執行完之后才啟動執行緒2,現在把該陳述句去掉變成下面這樣:
# 啟動執行緒
thread.start()
# 執行緒1執行完之后啟動執行緒2
thread2.start()
thread2.join()
運行結果是:
執行緒1 碼農飛哥
執行緒2 碼農飛哥
執行緒2 好好學習
執行緒2 早日突破職業瓶頸
執行緒1 好好學習
執行緒1 早日突破職業瓶頸
可以看出執行緒1和執行緒2的運行是并行的,
17.5.sleep函式的用法
位于time模塊中的sleep(secs)函式,可以實作令當前執行的執行緒暫停secs秒后在繼續執行,所謂暫停,即令當前執行緒進入阻塞狀態,當達到sleep()函式規定的時間后,
再由阻塞狀態轉為就緒狀態,等待CPU調度,
# 定義執行緒要呼叫的方法
def async_fun(*add):
for arc in add:
start_time = time.time()
time.sleep(2)
print(str((time.time() - start_time)) + " 秒 " + threading.current_thread().getName() + " 結束呼叫" + arc)
my_tuple = ("碼農飛哥", "好好學習", "早日突破職業瓶頸")
# 創建執行緒
thread = threading.Thread(target=async_fun, args=my_tuple)
# 啟動執行緒
thread.start()
運行結果是:
2.0052337646484375 秒 Thread-1 結束呼叫碼農飛哥
2.004210948944092 秒 Thread-1 結束呼叫好好學習
2.002394199371338 秒 Thread-1 結束呼叫早日突破職業瓶頸
可以看出執行緒每次執行都花費了2秒的時間,
【Python從入門到精通】(二十)Python并發編程的基本概念-執行緒的使用以及生命周期
18. 執行緒池
18.1. 為什么要使用執行緒池呢?
前面幾篇文章介紹的執行緒都是直接通過代碼手動創建的執行緒,執行緒執行完任務之后就會被系統銷毀,下次再執行任務的時候再進行創建,這種方式在邏輯上沒有啥問題,但是系統啟動一個新執行緒的成本是比較高,因為其中涉及與作業系統的互動,作業系統需要給新執行緒分配資源,打個比方吧!就像軟體公司招聘員工干活一樣,當有活干時,就招聘一個外包人員干活,當活干完之后就把這個人員辭退掉,你說在這程序中所耗費的時間成本和溝通成本是不是很大,那么公司一般的做法是:當專案立項時就確定需要幾名開發人員,然后將這些人員配齊,然后這些人員就常駐在專案組,有活就干,沒活就摸魚,執行緒池也是同樣的道理,執行緒池可以定義最大執行緒數,這些執行緒有任務就執行任務,沒任務就進入執行緒池中歇著,
18.2. 執行緒池怎么用呢?
執行緒池的基類是concurrent.futures模塊中的Executor類,而Executor類提供了兩個子類,即ThreadPoolExecutor類和ProcessPoolExecutor類,其中ThreadPoolExecutor用于創建執行緒池,而ProcessPoolExecutor用于創建行程池,本文將重點介紹ThreadPoolExecutor類的使用,首先,讓我們來看看ThreadPoolExecutor類的建構式,這里使用的Python版本是:3.6.7,
def __init__(self, max_workers=None, thread_name_prefix=''):
"""Initializes a new ThreadPoolExecutor instance.
Args:
max_workers: The maximum number of threads that can be used to
execute the given calls.
thread_name_prefix: An optional name prefix to give our threads.
"""
if max_workers is None:
# Use this number because ThreadPoolExecutor is often
# used to overlap I/O instead of CPU work.
max_workers = (os.cpu_count() or 1) * 5
if max_workers <= 0:
raise ValueError("max_workers must be greater than 0")
self._max_workers = max_workers
self._work_queue = queue.Queue()
self._threads = set()
self._shutdown = False
self._shutdown_lock = threading.Lock()
self._thread_name_prefix = (thread_name_prefix or
("ThreadPoolExecutor-%d" % self._counter()))
他的建構式只有兩個引數:一個是max_workers引數,用于指定執行緒池的最大執行緒數,如果不指定的話則默認是CPU核數的5倍,另一個引數是thread_name_prefix,它用來指定執行緒池中執行緒的名稱前綴,其他引數:
_shutdown初始值值為False,默認情況下執行緒池不銷毀,即執行緒池的生命周期跟專案的生命周期一致,self._work_queue = queue.Queue()生成緩沖佇列,_threads沒有任務被提交時,執行緒的數量設定為0,_shutdown_lock指定執行緒池的鎖是Lock鎖,
說完了執行緒池的創建之后,接著來看看執行緒池中比較常用的幾個方法吧,submit(self, fn, *args, **kwargs):
該方法用提交任務,即將fn函式提交給執行緒池,*args代表傳給fn函式的引數,**kwargs代表以關鍵字引數的形式為fn函式傳入引數,shutdown(self, wait=True):
關閉執行緒池map(func, *iterables, timeout=None, chunksize=1):
該函式類似于全域函式map(func,*iterables),只是該函式將會啟動多個執行緒,以異步方式立即對iterables執行map處理,
程式將task函式通過submit方法提交給執行緒池之后,執行緒池會回傳一個Future物件,該物件的作用主要是用于獲取執行緒任務函式的回傳值,Future提供了如下幾個方法,
cancel():取消該Future代表的執行緒任務,如果該任務正在執行,不可取消,則該方法回傳False;否則,程式會取消該任務,并回傳True,result(timeout=None):獲取該 Future 代表的執行緒任務最后回傳的結果,如果 Future 代表的執行緒任務還未完成,該方法將會阻塞當前執行緒,其中 timeout 引數指定最多阻塞多少秒,add_done_callback(fn):為該 Future 代表的執行緒任務注冊一個“回呼函式”,當該任務成功完成時,程式會自動觸發該 fn 函式,done():如果該Future代表的執行緒任務被成功取消或執行完成,則該方法回傳True,
18.3.來個簡單的例子
該例中創建了一個最大執行緒數是2的執行緒池來執行async_add函式,
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def async_add(max):
sum = 0
for i in range(max):
sum = sum + i
time.sleep(1)
print(threading.current_thread().name + "執行求和操作求得的和是=" + str(sum))
return sum
# 創建兩個執行緒
pool = ThreadPoolExecutor(max_workers=2, thread_name_prefix='測驗執行緒')
# 向執行緒池提交一個task,20作為async_add()函式的引數
future1 = pool.submit(async_add, 20)
# 向執行緒池再提交一個task
future2 = pool.submit(async_add, 50)
# 判斷future1代表的任務是否執行完
time.sleep(2)
print(future1.done())
print(future2.done())
# 查看future1代表的任務回傳的結果
print('執行緒一的執行結果是=' + str(future1.result()))
# 查看future2代表的任務的回傳結果
print('執行緒二的執行結果是=' + str(future2.result()))
print("----" + threading.current_thread().name + "----主執行緒執行結束-----")
運行結果是:
測驗執行緒_0執行求和操作求得的和是=190
測驗執行緒_1執行求和操作求得的和是=1225
True
True
執行緒一的執行結果是=190
執行緒二的執行結果是=1225
----MainThread----主執行緒執行結束-----
本例中定義了一個最大執行緒數是2的執行緒池,并向執行緒池中提交了兩個任務,其中async_add函式就是要執行的任務,在async_add函式中添加 time.sleep(1) 休眠一秒是為了驗證done()方法回傳的結果,最后才列印主執行緒執行結束表明result()方法是阻塞的,如果將result()屏蔽掉,
改成如下形式:
# 創建兩個執行緒
pool = ThreadPoolExecutor(max_workers=2, thread_name_prefix='測驗執行緒')
# 向執行緒池提交一個task,20作為async_add()函式的引數
future1 = pool.submit(async_add, 20)
# 向執行緒池再提交一個task
future2 = pool.submit(async_add, 50)
# 判斷future1代表的任務是否執行完
print(future1.done())
print(future2.done())
print("----" + threading.current_thread().name + "----主執行緒執行結束-----")
則運行結果是:
False
False
----MainThread----主執行緒執行結束-----
測驗執行緒_0執行求和操作求得的和是=190
測驗執行緒_1執行求和操作求得的和是=1225
詳情可以查看
【Python從入門到精通】(二十二)Python執行緒池的正確使用姿勢
19.Pil庫
PIL庫 Python Imaging Library,已經是Python平臺事實上的影像處理標準庫了,PIL功能非常強大,但API卻非常簡單易用,但是PIL庫僅僅支持到Python 2.7,為了兼容Python 3.x開源社區提供了兼容版本Pillow,通過Pillow大家就可以愉快的在Python 3.x上使用PIL庫了,
19.1.安裝Pillow 以及版本兼容
通過pip命令安裝Pillow還是非常方便的,一行命令就可以
安裝最新版本的命令
pip install Pillow
安裝指定版面的命令pip install Pillow=={version} 這里的version需要替換成指定的版本號,比如要下載8.3.0版本,
pip install Pillow==8.3.0
如果你不知道有哪些版本可以通過pip install Pillow== 進行查看,
下表是Pillow與Python的版本對應表,

19.2. 常用模塊介紹
PIL庫有很多模塊,這里重點介紹一些常用的模塊,首先,總體來看下各個模塊的作用,
| 模塊名 | 主要作用 |
|---|---|
| Image | Image模塊提供了一個具有相同名稱的類用于表示PIL的image物件,它有許多工廠類,包括從檔案中加載image以及創建新的image |
| ImageColor | ImageColor 模塊包含了CSS3中的顏色說明符到RGB元組的顏色表和轉換器,這個模塊在PIL.Image.new()和ImageDraw模塊以及其他模塊使用 |
| ImageFont | ImageFont 用于設定字體,它主要用在PIL.ImageDraw.ImageDraw.text() 方法中, |
| ImageDraw | ImageDraw 模塊為Image模塊提供了簡單的2D圖形,利用該模塊可以創建新圖形,修飾現有圖形,然后生成新的圖形以供使用 |
下面就詳細介紹下各個模塊的
19.3. Image
Image是PIL庫的核心模塊,大部分圖片的操作都離不開它,利用它可以從檔案中加載image,以及創建新的image,以及將Images保存為圖片,
- 加載圖片檔案
PIL.Image.open(fp, mode='r', formats=None)
其中 fp是圖片檔案名稱,mode 表示圖片路徑的讀取模式,默認是’r’模塊,回傳Image物件
try:
img = Image.open("img1.jpeg")
finally:
# 這種打開方式需要手動關閉檔案流
img.close()
這里Image使用完成之后需要手動關閉,比較推薦下面的with … as … 的寫法
with Image.open('img1.jpeg') as img:
下面列舉的img都是前面通過open獲取到的Image物件,
2. 獲取圖片的寬,高,通過img.size 可以獲取圖片的寬,高,i
width, height = img.size
- 展示圖片
show()
img.show()
- 圖片旋轉,通過rotate方法對圖片進行旋轉,下面就是將圖片旋轉45度展示出來,
img.rotate(45).show()
- 圖片縮放,通過thumbnail方法可以實作對圖片的縮放,
img.thumbnail((width / 2, height / 2))
- 保存圖片
save(self, fp, format=None, **params)
該方法可以將Image物件保存為一個圖片檔案,其中:fp為圖片保存的路徑,**params是可變引數,一般是傳入圖片的后綴名,
img.save('thumbnail.jpeg')
- 創建新Image
PIL.Image.new(mode, size, color=0)
該方法有三個引數,mode用于指定生成的圖片是RGB還是RGBA,這里RGBA各個字母表示的意思是:r 表示red, g 表示gree, b表示blue,a 表示alpha 透明度,一般而言只需要指定RGB即可,如果需要創建一個透明底的圖片則需要傳入RGBA,
size 用于指定圖片的寬高,傳入的是一個元組,
color 用于指定圖片的顏色,如果前面mode傳入的是RGB的話,則該引數需要傳入含有三個元素的元組,比如:(255, 0, 0),如果前面mode傳入的是RGBA的話,則該引數需要傳入含有四個元素的元素,比如:(255,0,0,204),
下面的代碼就是創建一個寬高各為500的,背景色是紅色的圖片,
newImg = Image.new('RGB', (500, 500), (255, 0, 0))
newImg.save('newImg.png')
- 復制圖片,通過copy()方法,可以copy一個圖片,
# 復制圖片
copyImg = img.copy()
newImg.save(op.join(base_path, 'copyImg.png'))
- 粘貼圖片:通過 paste方法可以將一個圖片粘貼到另一個圖片之上,
ImageColor
該模塊主要是從CSS3中的顏色說明符中獲取到RGB值,這里說一個方法:getrgb 方法就是獲取RGB值,
# 獲取顏色的RBGA值
rgb_tup = ImageColor.getrgb("#ff0000cc")
print(rgb_tup)
運行結果是(255, 0, 0, 204)
19.4. ImageFont
ImageFont 用于設定字體,它主要用在PIL.ImageDraw.ImageDraw.text() 方法中,首先,這里介紹其最常用的方法
PIL.ImageFont.truetype (font = None , size = 10 , index = 0 , encoding = '' , layout_engine = None )
從檔案或類檔案物件加載 TrueType 或 OpenType 字體,并創建字體物件,該函式從給定的檔案或類檔案物件加載一個字體物件,并為給定大小的字體創建一個字體物件,
Pillow 使用 FreeType 打開字體檔案,如果您在 Windows 上同時打開多種字體,請注意 Windows 將可以在 C 中同時打開的檔案數限制為 512,如果接近該限制,OSError可能會拋出an ,報告 FreeType“無法打開資源”,
此功能需要 _imagingft 服務,
引數
font – 包含 TrueType 字體的檔案名或類似檔案的物件,如果在此檔案名中找不到該檔案,加載程式也可能會在其他目錄中進行搜索,例如fonts/ Windows 或 上的目錄/Library/Fonts/, /System/Library/Fonts/以及~/Library/Fonts/macOS上的目錄,
size – 請求的大小,以磅為單位,
index – 要加載的字體(默認是第一個可用的字體),
encoding—要使用的字體編碼(默認為 Unicode),可能的編碼包括(有關更多資訊,請參閱 FreeType 檔案):
這指定要使用的字符集,它不會改變后續操作中提供的任何文本的編碼,
layout_engine 要使用的布局引擎(如果可用): ImageFont.LAYOUT_BASIC或ImageFont.LAYOUT_RAQM.
回傳值
一個字體物件,
img_font = ImageFont.truetype('simsun.ttf', size=20)
這里代碼的意思是創建一個字體大小為20的宋體的字體,
19.5. ImageDraw
終于說到ImageDraw模塊了,這個模塊也是一個非常重要的模塊,它主要是可以給圖片添加文字以及劃線等,
- Draw方法
PIL.ImageDraw.Draw(im, mode=None)
給指定的Image物件創建一個draw物件,
引數:
im: 需要被繪畫的image物件
mode: 用于顏色值的可選模式,對于RGB影像,此引數可以是RGB或者RGBA(將繪圖混合到影像中),對于所有其他模式,此引數必須與影像模式相同,如果省略,模式默認是影像的模式,
2. text方法
ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align='left', direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
在給定的位置上添加文本
引數:
xy – 文本的錨點坐標,
text – 要繪制的字串,如果它包含任何換行符,則文本將傳遞給 multiline_text(),
fill- 用于文本的顏色,
font- 一個ImageFont實體,
anchor—— 文本錨對齊方式,確定錨點與文本的相對位置,默認對齊方式是左上角,有關有效值,請參閱文本錨點,對于非 TrueType 字體,將忽略此引數,
此引數存在于 Pillow 的早期版本中,但僅在 8.0.0 版中實作,
spacing– 如果文本傳遞到multiline_text(),則為 行之間的像素數,
align- 如果文本被傳遞到 multiline_text(), “left”,“center"或"right”,確定線條的相對對齊方式,使用anchor引數指定對齊到xy,
direction——文本的方向,它可以是"rtl"(從右到左)、“ltr”(從左到右)或"ttb"(從上到下),需要 libraqm,
features—— 要在文本布局期間使用的 OpenType 字體功能串列,這通常用于打開默認情況下未啟用的可選字體功能,例如"dlig"或"ss01",但也可用于關閉默認字體功能,例如"-liga"禁用連字或"-kern" 禁用字距調整,要獲取所有支持的功能,請參閱OpenType 檔案,需要 libraqm,
language—— 文本的語言,不同的語言可能使用不同的字形形狀或連字,此引數告訴字體文本使用的語言,并根據需要應用正確的替換(如果可用),它應該是BCP 47 語言代碼,需要 libraqm,
stroke_width–文本筆劃的寬度,
stroke_fill – 用于文本筆劃的顏色,如果沒有給出,將默認為fill引數,
embedded_color– 是否使用字體嵌入顏色字形(COLR、CBDT、SBIX),8.0.0 版中的新功能,
# 給圖片上添加文字
with Image.open(op.join(base_path, 'img4.jpeg')) as im:
font = ImageFont.truetype(op.join(base_path, 'simsun.ttf'), size=80)
rgb_tup = ImageColor.getrgb("#ff0000cc")
draw = ImageDraw.Draw(im)
text = "瑪莎拉蒂"
draw.text((650, 550), text, fill=rgb_tup, font=font)
im.save(op.join(base_path, '瑪莎拉蒂.png'), 'png')
運行結果是:

19.6. 合并美女照片
現在有這兩張美女照片分別是:img2.jpeg和img3.png,我想把img3.png粘貼到img2.jpeg上,該如何操作呢? 其中img3.png還是透明底的,

- 直接上paste方法
# 將兩張圖貼起來
img2 = Image.open('img2.jpeg')
img3 = Image.open('img3.png')
img2.paste(img3)
img2.save('beautiful_paste.jpeg')
運行結果是:

img3.png 圖片粘貼到img2上之后背景色變成了黑色,這顯然沒有達到我們期望的結果,這該如何處理呢?
問題不大,只需要小小的修改一下代碼.
3. 小小修改一下,將背景改成透明底
# 透明底
img2 = Image.open('img2.jpeg').convert('RGBA')
img3 = Image.open('img3.png').convert('RGBA')
# 獲取r,g,b,a的值
r, g, b, a = img3.split()
# 傳入透明值
img2.paste(img3, box=(0, 0), mask=a)
img2.save('beautiful_paste2.png')
運行結果是:

這下就變成了透明底了,兩位美女都可以盡情欣賞了,
詳細內容可以查看
??【Python從入門到精通】(二十六)用Python的PIL庫(Pillow)處理影像真的得心應手??
??【Python從入門到精通】(二十七)更進一步的了解Pillow吧!
總結
至此Python的基礎內容已經全部介紹完了,
干貨太多,編輯器都有點卡頓了,
還是那句話,收藏下來邁出了學習的第一步,
B站的小姐姐看一千遍還是別人的,C站的文章看一遍就是自己的了,
干貨握在手,妹子跟你走,
Python知識圖譜
為了更好幫助更多的小伙伴對Python從入門到精通,我從CSDN官方那邊搞來了一套 《Python全堆疊知識圖譜》,尺寸 870mm x 560mm,展開后有一張辦公桌大小,也可以折疊成一本書的尺寸,有興趣的小伙伴可以了解一下------掃描下圖中的二維碼即可購買,

我本人也已經用上了,感覺非常好用,圖譜桌上放,知識心中留,

我是碼農飛哥,再次感謝您讀完本文,
需要原始碼的小伙伴關注下方公眾號,回復【python】
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/312121.html
標籤:java
上一篇:兩萬字Vue基礎知識總結,小白零基礎入門,跟著路線走,不迷路(建議收藏)
下一篇:你的聊天記錄是怎么被公司監控的?
