【全网力荐】堪称最易学的Python基础入门教程
目錄
數(shù)據(jù)的名字和種類——變量和類型
初探數(shù)據(jù)種類
數(shù)據(jù)類型
數(shù)值運算
比較運算
變量和賦值
變量的好處
用賦值更新變量
變量和數(shù)據(jù)類型的關(guān)系
總結(jié)
數(shù)據(jù)類型
數(shù)值運算
數(shù)值比較
變量和賦值
一串?dāng)?shù)據(jù)怎么存儲——列表和字符串
列表(List)
字符串(String)
總結(jié)
不只有一條路——分支和循環(huán)
input()、print() 和 int() 函數(shù)
分支
while 循環(huán)
條件的與、或、取反
for 循環(huán)
總結(jié)
將代碼放進(jìn)盒子——函數(shù)
函數(shù)的初步理解
函數(shù)如何定義
函數(shù)的調(diào)用
函數(shù)有什么用
什么時候用函數(shù)
總結(jié)
多語言比較
知錯能改——錯誤處理、異常機(jī)制
為什么需要錯誤處理
如何處理錯誤
常見的異常類型
raise 語句主動拋出異常
總結(jié)
定制一個模子——類
查看數(shù)據(jù)類型
類
類的定義
類的實例化
對象屬性
對象方法
總結(jié)
更大的代碼盒子——模塊和包
什么是模塊
模塊的導(dǎo)入
執(zhí)行模塊時傳入?yún)?shù)
什么是包
包的導(dǎo)入
為什么需要模塊和包
總結(jié)
練習(xí)——密碼生成器
密碼生成器要求
實現(xiàn)思路
實現(xiàn)
完整代碼
運行示例
補(bǔ)充說明
Hello,你好呀,我是灰小猿,一個超會寫bug的程序猿!
最近發(fā)現(xiàn)很多開始學(xué)習(xí)編程的小伙伴苦于編程入門比較困難,而且有很多想學(xué)習(xí)編程卻苦于沒有資源的小伙伴,所以今天在這里為大家爆肝Python基礎(chǔ)入門的相關(guān)技術(shù),適合剛開始接觸Python或苦于編程入門的小伙伴們,建議收藏認(rèn)真閱讀!相信會對大家的Python學(xué)習(xí)助一臂之力的!
本文持續(xù)更新,歡迎小伙伴收藏關(guān)注!
話不多說直接開肝!
?
數(shù)據(jù)的名字和種類——變量和類型
初探數(shù)據(jù)種類
在正式開始學(xué)習(xí)這個小節(jié)之前你要明白,現(xiàn)在我們是在學(xué)習(xí)寫程序。那么在寫程序之前你要知道程序的作用是什么?
程序的主要作用是處理數(shù)據(jù)。數(shù)據(jù)的種類有很多,我們在手機(jī)和電腦上看到的那些文字、數(shù)字、圖片、視頻、頁面樣式等等都是數(shù)據(jù)。這些數(shù)據(jù)都是由程序來處理并顯示到屏幕上的。
雖然數(shù)據(jù)的種類形形色色,并且有些看起來比較復(fù)雜,但是在編程時它們實際上都是由一些非常基本的數(shù)據(jù)形式(或經(jīng)過組合)來表示。這些基本數(shù)據(jù)形式有哪些呢?比如有常用到的數(shù)字和字符,以及其它的諸如數(shù)組、字節(jié)序列等形式。
以數(shù)字和字符為例,為大家介紹下在代碼中它們是怎么表示的。
對于數(shù)字,數(shù)字在代碼中的表示形式和平時的電腦輸入一樣,直接書寫即可:
123 3.14159對于字符,和平時的書寫稍有不同,Python 代碼中表示字符時一定要給字符括上單引號或雙引號:
'How are you?' '嗨!'這些不同的數(shù)據(jù)表示(書寫)形式,對應(yīng)著不同的數(shù)據(jù)種類,而不同的數(shù)據(jù)種類又具有不同的功能或者作用。
我們將代碼中的數(shù)據(jù)種類稱為數(shù)據(jù)類型,也就是數(shù)據(jù)的類型。
數(shù)據(jù)類型
代碼中的所有數(shù)據(jù)都是有類型的。
數(shù)字所對應(yīng)的數(shù)據(jù)類型有整數(shù)型以及浮點型。整數(shù)型表示整數(shù)數(shù)字,比如:0,-59,100。浮點型表示小數(shù)數(shù)字,如 -3.5,0.25,0.0。
字符所對應(yīng)的數(shù)據(jù)類型叫字符串,所謂字符串就是一串字符。它里面可以是任意語言的字符,比如 '哼哼哈嘿','Good Good Study'。當(dāng)然字符串里也可以只有一個字符,比如 'a'。
有一種表示「是」或「否」的類型,叫做布爾型。它的值叫布爾值,只有 True 和 False 兩種取值。這就好比考試時的判斷題,結(jié)果只能二選一,要么「是」要么「否」。
另外還有一種很特別的類型:None 型,表示什么都沒有,它就一個取值 None。
說明:為了不增加大家的記憶負(fù)擔(dān),這里只介紹這五種基本數(shù)據(jù)類型,后續(xù)的我們慢慢掌握。
考大家一個問題,在代碼中 1000 和 '1000' 是相同的東西嗎?答案是不同,一個是數(shù)字,一個是字符串,數(shù)據(jù)類型不同。
數(shù)值運算
對于整數(shù)型和浮點型,因為它們都被用來表示數(shù)值,理所應(yīng)當(dāng)這二者可以做數(shù)值運算,也就是加減乘除等操作。
我們進(jìn)入 Python 解釋器交互模式中,輸入代碼試驗一下這些數(shù)值運算:
-  加法 33+725>>> 33+725 
 758
-  減法 12-24>>> 12-24 
 -12
-  乘法 8*12.5>>> 8*12.5 
 100.0
-  除法 1/3>>> 1/3 
 0.3333333333333333
-  除余 10%3>>> 10%3 
 1
可以看到,數(shù)值的加(+)、減(-)、乘(*)、除(/)、除余(%)都可以被計算。這些操作也是多種程序語言所通用的,除此之外 Python 還內(nèi)置了次方運算(**)和整除(//):
-  次方 2**3>>> 2**3 
 8
-  整除 9//2>>> 9//2 
 4
這恐怕是 Python 的最簡單的用法了——當(dāng)作計算器!
說明:通常我們?yōu)榱嗣烙^,會在上面的運算符號的左右各加上一個空格,如 12 - 24,2 ** 3。
之后的代碼示例中我們會添加空格。
比較運算
整數(shù)型和浮點型除了數(shù)值運算外,還可以做比較運算,也就是比較兩個數(shù)值的大小。比較的結(jié)果是布爾值。如:
2 > 3>>> 2 > 3
 False
>>> 2 <= 3
 True
>>> 2 == 3
 False
比較運算的運算符可以是大于(>),小于(<),大于等于(>=),小于等于(<=),等于(==),不等于(!=)。其寫法與數(shù)學(xué)中的比較運算很相似,但不同的是「等于」和「不等于」,尤其注意「等于」是用兩個等號 == 表示。
變量和賦值
剛才我們學(xué)習(xí)了數(shù)值運算,那我們現(xiàn)在來算算一周有多少秒,一年有多少秒。
首先我們不難得出一天有 60 * 60 * 24 秒。我們可以暫時把這個結(jié)果用某種方式記錄下來,以便后續(xù)使用。用什么方式記錄呢?我們可以使用變量。
變量其實就是編程者給代碼中的某個數(shù)據(jù)所取的名字,之后的編程過程中使用這個名字就相當(dāng)于使用它背后的數(shù)據(jù)。簡單地來理解的話,我們可以把變量看作是代碼中用于保存數(shù)據(jù)的臨時容器。
創(chuàng)建變量的動作我們稱之為定義變量。如下是定義變量的方法:
seconds_per_day = 60 * 60 * 24在這里我們起了個名字 seconds_per_day,并且通過符號 = 把 60 * 60 * 24 的計算結(jié)果給了它。seconds_per_day 這個名字就是我們所定義的變量,它的值(也就是其背后的數(shù)據(jù))是 60 * 60 * 24 的實際運算結(jié)果。也就是說我們將一天的秒數(shù) 60 * 60 * 24 保存在了變量 seconds_per_day 中。
等號(=) 在代碼中是賦值的意思,表示將 = 右邊的值賦予 = 左邊的變量。注意賦值用等號 = 表示,而「等于」用 == (連續(xù)兩個等號)表示。
執(zhí)行剛才的代碼后,緊接著輸入 seconds_per_day 可以看到這個變量的值:
>>> seconds_per_day
 86400
回到「一周有多少秒」的問題上去。我們有了表示一天的秒數(shù)的 seconds_per_day 變量,那我們的程序就可以這樣寫下去:
seconds_per_day * 7>>> seconds_per_day * 7
 604800
一天的秒數(shù)乘以七(天),最終結(jié)果是 604800,沒有任何問題。
剛才的完整連貫代碼是:
seconds_per_day = 60 * 60 * 24 seconds_per_day * 7變量的好處
你可能會說「一周的秒數(shù),直接計算 60 * 60 * 24 * 7 不就好了,也用不著使用變量」?是的,有時確實可以不使用變量。但使用變量有一個好處,那就是可以暫存一個中間結(jié)果,方便之后去重復(fù)利用它。
比如我們現(xiàn)在還想要再算一下「一年有多少秒」,因為前面已經(jīng)算好了一天的秒數(shù) seconds_per_day,所以可以直接拿來利用:
seconds_per_day * 365>>> seconds_per_day * 365
 31536000
除此之外變量的好處還有,你可以通過妥當(dāng)?shù)淖兞棵謥砀纳瞥绦虻目勺x性(閱讀的容易程度)。比如我們在代碼里寫下 60 * 60 * 24,別人(包括未來的你自己)在閱讀時很難一下子理解這串運算表示什么。但是如果這樣寫呢: seconds_per_day = 60 * 60 * 24。噢,原來是指一天的秒數(shù)。
用賦值更新變量
前面內(nèi)容中的變量是在定義的時候被賦值的,其實變量被定義后也可以反復(fù)給這個變量賦予新的值,這樣變量中的數(shù)據(jù)就被更新了。如:
>>> day = 1
 >>> day
 1
 >>> day = 2
 >>> day
 2
 >>> day = 3
 >>> day
 3
變量和數(shù)據(jù)類型的關(guān)系
變量用來保存數(shù)據(jù),而數(shù)據(jù)類型用來指明數(shù)據(jù)的種類。
剛才我們使用了 seconds_per_day = 60 * 60 * 24 語句來定義變量 seconds_per_day,并將它賦值為 60 * 60 * 24。因為變量 seconds_per_day 中保存的是個整數(shù)型的值,所以我們說 seconds_per_day 是個整數(shù)型(的)變量。
總結(jié)
數(shù)據(jù)類型
這個章節(jié)中我們提到的 Python 基礎(chǔ)數(shù)據(jù)類型有:
| 整數(shù)型 | 整數(shù) | -59,100 | 
| 浮點型 | 小數(shù) | -3.5,0.01 | 
| 字符串 | 文本 | '哼哼哈嘿','Good Good Study' | 
| 布爾型 | 是與非 | True,False | 
| None 型 | 什么都沒有 | None | 
Python 中的數(shù)據(jù)類型不止這些,之后會漸漸涉及,表格中的這些類型也會在之后被應(yīng)用到。
數(shù)值運算
數(shù)值運算的符號有:
| + | 加法 | 1 + 1 | 
| - | 減法 | 2 - 3 | 
| * | 乘法 | 4 * 5 | 
| / | 除法 | 6 / 7 | 
| % | 取余 | 8 % 9 | 
| ** | 次方 | 2 ** 3(2 的 3 次方) | 
| // | 整除 | 5 // 4 | 
數(shù)值比較
數(shù)值比較的符號有:
| > | 大于 | 
| < | 小于 | 
| >= | 大于等于 | 
| <= | 小于等于 | 
| == | 等于 | 
| != | 不等于 | 
上面的內(nèi)容看起來羅列了很多,但其實不會帶來記憶負(fù)擔(dān)。數(shù)值運算和數(shù)值比較與數(shù)學(xué)上的概念和符號大致相同,略有區(qū)別而已。
變量和賦值
我們通過以下形式來定義變量和賦值:
變量名 = 數(shù)據(jù)值多語言比較:
「多語言比較」這部分內(nèi)容,是為讓大家了解本章節(jié)所介紹的語言基本特性在其它語言中是如何表達(dá)的。大家可以了解體會它們之間的相識之處。
不同于動態(tài)類型的 Python,在靜態(tài)類型的語言中數(shù)據(jù)類型還有長度一說,也就是類型所能容納的數(shù)據(jù)大小。并且變量在定義時還需先聲明它的類型。以整數(shù)型為例。Java 中的整數(shù)型根據(jù)長度的不同分為:byte(1 字節(jié))、short(2 字節(jié))、int(4 字節(jié))、long(8 字節(jié)),浮點型分為 float(4 字節(jié))、double(8 字節(jié))。其它語言也有一些類似。C/C++ 中的整數(shù)型有「有無符號」之分(如 unsigned int 表示無符號的 int 型,也就是說這只能表示 0 和正數(shù),不能表示負(fù)數(shù))。
Java 定義變量并初始化:
int yearDays = 365C/C++ 定義變量并初始化:
int yearDays = 365把 C 和 C++ 合并稱為 C/C++,是因為 C++ 基本上是 C 的強(qiáng)大很多的超集,雖然 C++ 嚴(yán)格來說不是 100% 兼容 C,但幾乎是兼容的。
Go 語言定義變量并初始化:
var yearDays int = 365Go 語言中的變量定義需要加上關(guān)鍵字 var,且數(shù)據(jù)類型(這里是 int)放在變量名后面。或者采用另一種寫法:
yearDays := 365這種寫法不但可以省略關(guān)鍵字 var 還可以省略數(shù)據(jù)類型,數(shù)據(jù)類型可直接由編譯器推導(dǎo)出來。
以上語言在變量定義后,都可通過下述語句再次賦值:
yearDays = 366?
一串?dāng)?shù)據(jù)怎么存儲——列表和字符串
上一節(jié)中講了數(shù)據(jù)類型,有一個問題,之前所介紹的數(shù)據(jù)類型大多是用來表示單個數(shù)據(jù)的。比如整數(shù)型,一個整數(shù)型的變量只能保存一個整數(shù)。又如布爾型,一個布爾型的變量只能保存一個布爾值。浮點型和 None 型也是如此。要是此刻有一系列的數(shù)據(jù),那該怎么在程序里保存和使用呢?
舉個栗子:當(dāng)我的只有一個電話號碼的時候,我可以使用整數(shù)型來表示,并保存在變量里:
tel = 13011110000但如果有十個電話號碼,該怎么來表示和使用它們呢?
13011110000
 18022221111
 13433332222
 13344443333
 17855554444
 13866665555
 15177776666
 13388887777
 18799998888
 17800009999
你可能會說,「那就用十個變量」,像這樣:
tel01 = 13011110000 tel02 = 18022221111 ... tel10 = 17800009999或者「把它們用逗號拼在一起然后放到字符串里」:
tels = '13011110000,18022221111,13433332222,13344443333,17855554444,13866665555,15177776666,13388887777,18799998888,17800009999'是的,看起來這似乎能解決問題。但是這兩種辦法的弊端也很明顯。第一種使用多個變量的方式,在數(shù)據(jù)量很大的情況下使用起來會十分繁瑣;第二種使用字符串的方式,如果我們需要對其中的某些數(shù)據(jù)做處理,那這種方式就很不方便靈活了。
這時我們可以選擇使用列表。
列表(List)
列表是一種用于保存批量數(shù)據(jù)的數(shù)據(jù)類型。它和整數(shù)型、布爾型等數(shù)據(jù)類型一樣都被內(nèi)置在 Python 中。
列表的寫法
列表的寫法為 [ 數(shù)據(jù)項1, 數(shù)據(jù)項2, ..., 數(shù)據(jù)項N ],方括號就代表列表,每個數(shù)據(jù)項放在方括號中并用逗號分隔。
如之前的那一串電話號碼可以這樣來保存:
tels = [13011110000, 18022221111, 13433332222, 13344443333, 17855554444, 13866665555, 15177776666, 13388887777, 18799998888, 17800009999]擴(kuò)展:為了方便閱讀,我們也可以把把這個列表寫成多行的形式:
tels = [13011110000, 18022221111, 13433332222, 13344443333, 17855554444, 13866665555, 15177776666, 13388887777, 18799998888, 17800009999 ]每個數(shù)據(jù)項一行,這樣是不是更好看了!
在解釋器的交互模式中輸入這樣的多行代碼時,我們會發(fā)現(xiàn)第一行的提示符是 >>>,之后每行的提示符會變成 ...,直到完成了多行輸入則又變回 >>>。如:
>>> tels = [
 … 13011110000,
 … 18022221111,
 … 13433332222
 … ]
 >>>
列表中的數(shù)據(jù)可以是任意類型的。比如整數(shù)型、字符串類型和布爾類型等:
[100, 'about', True]列表索引
列表中的每個數(shù)據(jù)項都是有先后次序的,最前面的數(shù)據(jù)項的位置編號為 0,之后依次是 1 ,2 …… N,這個位置編號在編程中的術(shù)語叫做索引(Index)。注意 Python 中索引是從 0 開始計數(shù)的,0 即代表第一個位置。
可以通過符號 [] 來獲取某個索引所對應(yīng)的數(shù)據(jù)項。比如:
>>> fruits = [‘a(chǎn)pple’, ‘banana’, ‘cherry’, ‘durian’]
 >>> fruits[0]
 ’apple’
 >>> fruits[2]
 ’cherry’
上面的 fruits 有 4 項數(shù)據(jù),所以最大的索引是 3。如果我們強(qiáng)行要用更大的索引值去取數(shù)據(jù)會怎樣呢,來試一下:
>>> fruits[4]
 Traceback (most recent call last):
 ???? File “”, line 1, in
 IndexError: list index out of range
可以看到代碼直接就報錯了,具體信息為「list index out of range」,列表索引超出范圍。
擴(kuò)展:這是 Python 的典型報錯形式,這里有三行內(nèi)容(也可能會有很多行),前兩行是錯誤定位,描述出錯的位置(如某文件的某行),后面是錯誤描述,指出這是個 IndexError 錯誤,具體信息為「list index out of range」。
若大家在寫代碼時遇到錯誤,可以按照這種方法嘗試自己分析錯誤信息。
除了通過索引去獲取值,也可以通過索引去改變列表中某項數(shù)據(jù)的值。通過賦值的方式來實現(xiàn):
fruits[0] = 'pear'>>> fruits[0]
 ‘a(chǎn)pple’
>>> fruits[0] = 'pear’
 >>> fruits[0]
 ‘pear’
列表的長度
列表中數(shù)據(jù)項的個數(shù),叫做列表(的)長度。
想要獲得列表的長度可以使用 len() 這個東西。像這樣:
len(fruits)>>> len(fruits)
 4
>>> len([1, 2, 3, 4, 5, 6, 7])
 7
說明:len() 是 Python 中的內(nèi)置函數(shù)。函數(shù)的概念會在之后的章節(jié)中介紹。
向列表添加數(shù)據(jù)
之前使用時,列表中的數(shù)據(jù)在一開始就已經(jīng)被確定下來了,并一直保持著這個長度。但在很多時候,我們需要隨時向列表中添加數(shù)據(jù)。
向列表的末尾添加數(shù)據(jù)可以用 .append()這個東西,它的寫法是:
列表.append(新數(shù)據(jù))看一個示例。這里首先創(chuàng)建了一個空的列表,將其變量命名為 fruits,然后通過 .append() 向其中添加內(nèi)容。
>>> fruits = []
 >>> fruits
 []
>>> fruits.append(‘pear’)
 >>> fruits
 [‘pear’]
>>> fruits.append(‘lemon’)
 >>> fruits
 [‘pear’, ‘lemon’]
擴(kuò)展:append() 是列表的方法。「方法」具體是什么我們在之后的面向?qū)ο笳鹿?jié)中介紹。這里暫且把方法理解為某個數(shù)據(jù)類型自帶的功能,如 append() 是列表自帶的功能。
?
字符串(String)
字符串也可以保存批量數(shù)據(jù),只不過其中的數(shù)據(jù)項只能是字符。
我們在前一個章節(jié)中介紹過字符串,字符串是用來表示文本的數(shù)據(jù)類型。字符串以單引號或雙引號以及包裹在其中的若干字符組成,如:
'good good study' '100' '江畔何人初見月,江月何年初照人'字符串索引
從形式上我們不難看出,字符串中的字符也是有先后次序的。字符串是字符的有序序列,所以也具有索引。也可以根據(jù)索引取出其中某一個字符。其索引使用方式和列表相同:
'good good study'[3]>>> ‘good good study’[3]
 ‘d’
也可以先把字符串保存在變量里,然后在變量上使用索引。結(jié)果是一樣的:
words = 'good good study' words[3]>>> words = ‘good good study’
 >>> words[3]
 ‘d’
有一點需要注意,字符串不能像列表那樣通過索引去改變數(shù)據(jù)項的值。因為字符串類型的值是不可變的(Immutable),我們不能在原地修改它其中的某個字符。
>>> words = ‘good good study’
 >>> words[3] = 'b’
 Traceback (most recent call last):
 ???? File “”, line 1, in
 TypeError: ‘str’ object does not support item assignment
上面報出一個 TypeError 錯誤,具體信息為「‘str’ object does not support item assignment」,其中「‘str’ object」指的就是字符串,它不支持直接為其中某一個項(字符)賦值。
字符串長度
字符串中字符的個數(shù)也就是字符串的長度(包括空格在內(nèi)的所有空白符號)。
獲取字符串長度的方式和列表一樣,也是使用 len():
len('good good study')>>> len(‘good good study’)
 15
?
總結(jié)
如果我們想要保存和表示批量數(shù)據(jù),可以使用 Python 中的列表(List)類型。列表是有序序列,能保存任意類型的數(shù)據(jù)項,可以通過索引(Index)來獲取和修改其中某一個數(shù)據(jù)項,可以通過 len() 函數(shù)來獲取列表的長度,也可以通過 .append() 在列表末尾追加數(shù)據(jù)項。
如果數(shù)據(jù)是文本,那么可以用字符串類型(String)來表示。字符串類型是字符的有序序列,可以通過索引獲取某個位置的字符,也可以通過 len() 函數(shù)來獲取長度。
Python 中的列表和字符串還有很多功能,之后講「數(shù)據(jù)結(jié)構(gòu)」時為大家一一介紹。
多語言比較:
數(shù)組是保存和表示批量數(shù)據(jù)的最基本的結(jié)構(gòu),它也是構(gòu)造字符串、集合和容器的基石。
Python 中沒有數(shù)組概念,取而代之的是列表這種更高級的數(shù)據(jù)結(jié)構(gòu),列表涵蓋了數(shù)組的功能并提供了更多且更強(qiáng)大的功能。
Java 中,用 類型[] 的寫法來表示數(shù)組:
// 定義數(shù)組 int numbers[];// 定義數(shù)組并用指定值初始化: int numbers[] = {1, 2, 3};C/C++ 定義數(shù)組:
// 定義數(shù)組 int numbers[3];// 定義數(shù)組并用指定值初始化: int numbers[] = {1, 2, 3};Go 語言定義數(shù)組:
// 定義數(shù)組 var numbers [3] int// 定義數(shù)組并用指定值初始化: var numbers = [3]int {1, 2, 3}?
不只有一條路——分支和循環(huán)
前面的章節(jié)中我們學(xué)習(xí)了數(shù)據(jù)類型、變量、賦值、數(shù)值運算,并且用這些知識編寫程序來做一些簡單的運算,比如「計算一年有多少秒」。像這樣的程序,執(zhí)行流程是完全固定的,每個步驟事先確定好,運行時一步一步地線性地向下執(zhí)行。
但是很多時候程序的功能會比較復(fù)雜,單一的執(zhí)行流程并無法滿足要求,程序在運行時可能需要對一些條件作出判斷,然后選擇執(zhí)行不同的流程。這時就需要分支和循環(huán)語句了。
?
input()、print() 和 int() 函數(shù)
在開始學(xué)習(xí)分支和循環(huán)前,為了可以讓程序與我們交互,先來學(xué)習(xí)三個函數(shù)。至于什么是函數(shù),我們暫且把它看作是程序中具有某種功能的組件,下一小節(jié)中將會詳細(xì)介紹函數(shù)的概念。
input() 函數(shù)
如果想要通過命令行與程序交互,可以使用 input() 函數(shù)。
input() 函數(shù)可以在代碼執(zhí)行到此處時輸出顯示一段提示文本,然后等待我們的輸入。在輸入內(nèi)容并按下回車鍵后,程序?qū)⒆x取輸入內(nèi)容并繼續(xù)向下執(zhí)行。讀取到的輸入內(nèi)容可賦值給變量,供后續(xù)使用。寫法如下:
讀取到的輸入 = input('提示文本')>>> age = input(‘請輸入你的年齡:’)
 請輸入你的年齡:30
 >>> age
 ’30’
這行代碼會在命令行中顯示「請輸入你的年齡:」,然后等待輸入,讀取到輸入內(nèi)容后賦值給 age 變量。
input() 返回的結(jié)果是字符串類型,如 '30'。如果我們需要整數(shù)型,可以使用 int() 函數(shù)進(jìn)行轉(zhuǎn)換。
int() 函數(shù)
int() 函數(shù)可以將字符串、浮點型轉(zhuǎn)換整數(shù)型。寫法為:
int(字符串或浮點數(shù))將字符串類型 ‘1000’ 轉(zhuǎn)換為整數(shù)型 1000:
>>> int(‘1000’)
 1000
將浮點數(shù) 3.14 轉(zhuǎn)化為整數(shù):
>>> int(3.14)
 3
print() 函數(shù)
print() 函數(shù)可以將指定的內(nèi)容輸出到命令行中。寫法如下:
print('要輸出的內(nèi)容')>>> print(‘Hello World!’)
 Hello World!
 >>> print(‘你的年齡是’, 20)
 你的年齡是 20
要輸出的內(nèi)容放在括號中,多項內(nèi)容時用逗號分隔,顯示時每項以空格分隔。
input()、print() 示例
我們可以把 input() 和 print() 結(jié)合起來。如下面這兩行代碼將在命令行中提示「請輸入你的年齡:」,然后等待輸入,手動輸入年齡后按下回車鍵,將顯示「你的年齡是 x」。
age = input('請輸入你的年齡:') print('你的年齡是', age)我們把代碼保存到文件中,文件命名為 age.py, 然后執(zhí)行下:
? ~ python3 age.py
 請輸入你的年齡:18
 你的年齡是 18
?
分支
上一個例子很簡單,接收一個輸入內(nèi)容然后把該內(nèi)容顯示出來。現(xiàn)在難度升級。在剛才代碼的基礎(chǔ)上,如果所輸入的年齡小于 18 歲,那么在最后再顯示一句勉勵語——「好好學(xué)習(xí),天天向上」。如何來實現(xiàn)?
if 語句
如果想要表達(dá)「如果……」或者「當(dāng)……」這種情形,需要用到 if 語句。它的寫法是:
if 條件:代碼塊它的執(zhí)行規(guī)則是,若「條件」?jié)M足,則執(zhí)行 if 下的「代碼塊」,若「條件」不滿足則不執(zhí)行。
條件滿足指的是,條件的結(jié)果為布爾值 True,或非零數(shù)字,或非空字符串,或非空列表。
代碼塊就是一段代碼(可以是一行或多行),這段代碼作為一個整體以縮進(jìn)的形式嵌套在 if 下面。按照通常的規(guī)范,縮進(jìn)以 4 個空格表示。
回到我們之前的需求上,「當(dāng)年齡小于 18 歲」就可以通過 if 語句來實現(xiàn)。完整代碼如下:
age = int(input('請輸入你的年齡:')) # 注意此處用 `int()` 將 `input()` 的結(jié)果由字符串轉(zhuǎn)換為整數(shù)型 print('你的年齡是', age)if age < 18:print('好好學(xué)習(xí),天天向上')保存在文件中,執(zhí)行一下看看:
? ~ python3 age.py
 請輸入你的年齡:17
 你的年齡是 17
 好好學(xué)習(xí),天天向上
? ~ python3 age.py
 請輸入你的年齡:30
 你的年齡是 30
可以看到,當(dāng)所輸入的年齡小于 18 時,程序在最后輸出了「好好學(xué)習(xí),天天向上」,而輸入年齡大于 18 時則沒有。
else 語句
又在上面的基礎(chǔ)上,如果輸入的年齡大于等于 18 歲,輸出「革命尚未成功,同志任需努力」。該如何實現(xiàn)?
我們可以在 if 語句之后緊接著使用 else 語句,當(dāng) if 的條件不滿足時,將直接執(zhí)行 else 的代碼塊。寫法如下:
if 條件:代碼塊 1 else:代碼塊 2若條件滿足,則執(zhí)行代碼塊 1,若不滿足則執(zhí)行代碼塊 2。所以之前的需求我們可以這樣實現(xiàn):
age = int(input('請輸入你的年齡:')) print('你的年齡是', age)if age < 18:print('好好學(xué)習(xí),天天向上') else:print('革命尚未成功,同志仍需努力')執(zhí)行下看看:
? ~ python3 age.py
 請輸入你的年齡:18
 你的年齡是 18
 革命尚未成功,同志任需努力
? ~ python3 age.py
 請輸入你的年齡:17
 你的年齡是 17
 好好學(xué)習(xí),天天向上
elif 語句
我們可以看到,if 和 else 表達(dá)的是「如果……否則……」這樣的二元對立的條件,非此即彼。但有時我們還需要表達(dá)「如果……或者……或者……否則……」這樣多個條件間的選擇。
舉個例子,下表是年齡和其對應(yīng)的人生階段。
| 0-6 歲 | 童年 | 
| 7-17 歲 | 少年 | 
| 18-40 歲 | 青年 | 
| 41-65 歲 | 中年 | 
| 65 歲之后 | 老年 | 
當(dāng)我們在程序中輸入一個年齡時,輸出對應(yīng)的人生階段。該如何實現(xiàn)?我們先用漢語來描述一下代碼邏輯(這種自然語言描述的代碼邏輯,也叫作偽代碼):
如果年齡小于等于 6:輸出童年 如果年齡介于 7 到 17:輸出少年 如果年齡介于 18 到 40:輸出青年 如果年齡介于 41 到 65:輸出中年 否則:輸出老年可以看到,我們需要依次進(jìn)行多個條件判斷。要實現(xiàn)它就要用到 elif 語句了,字面上它是 else if 的簡寫。
elif 置于 if 和 else 之間,可以有任意個:
if 條件 1:代碼塊 1 elif 條件 2:代碼塊 2 else代碼塊 3之前根據(jù)年齡輸出人生階段的需求,可以這樣實現(xiàn):
age = int(input('請輸入年齡:'))if age <= 6:print('童年') elif 7 <= age <=17:print('少年') elif 18 <= age <= 40:print('青年') elif 41 <= age <= 65:print('中年') else:print('老年')? ~ python3 age.py
 請輸入年齡:3
 童年
 ? ~ python3 age.py
 請輸入年齡:17
 少年
 ? ~ python3 age.py
 請輸入年齡:30
 青年
 ? ~ python3 age.py
 請輸入年齡:65
 中年
 ? ~ python3 age.py
 請輸入年齡:100
 老年
分支語句小結(jié)
如上所述,if 可以配合 elif 和 else 一起使用。代碼執(zhí)行時,將會從第一個條件開始依次驗證判斷,若其中某個條件滿足,則執(zhí)行對應(yīng)的代碼塊,此時后續(xù)條件將直接跳過不再驗證。
一個 if-elif-else 組合中,elif 可出現(xiàn)任意次數(shù),else 可出現(xiàn) 0 或 1 次。
?
while 循環(huán)
之前介紹的 if 語句,是根據(jù)條件來選擇執(zhí)行還是不執(zhí)行代碼塊。我們還有一種很重要的場景——根據(jù)條件來判斷代碼塊該不該被重復(fù)執(zhí)行,也就是循環(huán)。
在 Python 中可以使用 while 語句來執(zhí)行循環(huán)操作,寫法如下:
while 條件:代碼塊它的執(zhí)行流程是,從 while 條件這句出發(fā),判斷條件是否滿足,若滿足則執(zhí)行代碼塊,然后再次回到 while 條件,判斷條件是否滿足……循環(huán)往復(fù),直到條件不滿足。
可以看到,如果這里的條件一直滿足且固定不變,那么循環(huán)將無窮無盡地執(zhí)行下去,這稱之為死循環(huán)。一般情況下我們很少會刻意使用死循環(huán),更多的是讓條件處于變化中,在循環(huán)的某一時刻條件不被滿足然后退出循環(huán)。
循環(huán)示例
舉個例子,如何輸出 100 次「你很棒」?
顯然我們可以利用循環(huán)來節(jié)省代碼,對循環(huán)條件做一個設(shè)計,讓它剛好執(zhí)行 100 次后結(jié)束。
count = 0while count < 100:print('你很棒')count = count + 1利用一個計數(shù)器 count 讓它保存循環(huán)的次數(shù),當(dāng) count 小于 100 就執(zhí)行循環(huán),代碼塊每執(zhí)行一次就給 count 加 1。我們在大腦中試著來模擬這個流程,用大腦來調(diào)試(Debug)。
將代碼寫入文件 loop.py,執(zhí)行下看看:
? ~ python3 loop.py
 你很棒
 你很棒
 你很棒
 …
程序?qū)⑷珙A(yù)期輸出 100 行「你很棒」。
擴(kuò)展:count = count + 1 可以簡寫為 count += 1
?
條件的與、或、取反
if 語句和 while 語句中的條件可以由多個語句組合表達(dá)。
and 關(guān)鍵字
要表達(dá)多個條件同時滿足的情況,可以使用 and 關(guān)鍵字。使用 and 關(guān)鍵字時,在所有并列的條件均滿足的情況下結(jié)果為 True。至少一個條件不滿足時結(jié)果為 False。如:
>>> 2 > 1 and ‘a(chǎn)bc’ == ‘a(chǎn)bc’ and True
 True
 >>> 1 > 0 and 0 != 0
 False
在 if 語句中可以這樣使用 and 關(guān)鍵字 :
if 條件1 and 條件2 and 條件N:代碼塊上述 if 語句只有在所有的條件均滿足的情況下,代碼塊才會被執(zhí)行。
例如我們假設(shè)把年齡大于 30 并且為男性的人稱為大叔,「年齡大于 30 」和「男性」是兩個判斷條件,并且需要同時滿足,這種情況就可以用 and 關(guān)鍵字來表達(dá)。如:
if age > 30 and sex == 'male':print('大叔')or 關(guān)鍵字
要表達(dá)多個條件中至少一個滿足即可的情況,可以使用 or 關(guān)鍵字。使用 or 關(guān)鍵字時,并列的條件中至少有一個滿足時,結(jié)果為 True。全部不滿足時結(jié)果為 False。
在 if 語句中可以這樣使用 or 關(guān)鍵字 :
if 條件1 or 條件2 or 條件N:代碼塊上述 if 語句中只要有任意一個(或多個)條件滿足,代碼塊就會被執(zhí)行。
not 關(guān)鍵字
not 關(guān)鍵字可以將一個布爾值取反。如:
>>> not True
 False
 >>>
 >>> not 1 > 0
 False
用在 if 語句和 while 語句的條件上時,條件的結(jié)果被反轉(zhuǎn)。
在 if 語句中可以這樣使用 not 關(guān)鍵字 :
if not 條件:代碼塊上述 if 語句在條件不滿足時執(zhí)行代碼塊,條件滿足時反而不執(zhí)行,因為 not 關(guān)鍵字對結(jié)果取了反。
?
for 循環(huán)
前面介紹了 while 循環(huán),在 Python 中還有一種循環(huán)方式——for 循環(huán)。
for 循環(huán)更多的是用于從頭到尾地去掃描列表、字符串這類數(shù)據(jù)結(jié)構(gòu)中的每一個項,這種方式叫做遍歷或迭代。
for 循環(huán)寫法為:
for 項 in 序列:代碼塊其執(zhí)行過程是,反復(fù)執(zhí)行 for 項 in 序列 語句和其代碼塊,項 的值依次用序列的各個數(shù)據(jù)項替換,直到序列的所有項被遍歷一遍。
比如,有個列表為 ['apple', 'banana', 'cherry', 'durian'],我們想依次輸出它的每個列表項,就可以用 for 循環(huán)。
fruit_list = ['apple', 'banana', 'cherry', 'durian']for fruit in fruit_list:print(fruit)將代碼寫入 for.py,執(zhí)行下:
? ~ python3 for.py
 apple
 banana
 cherry
 durian
可以看到,
每次循環(huán)時 fruit 都自動被賦予新的值,直到 fruit_list 的所有列表項遍歷完,循環(huán)退出。
?
總結(jié)
input() 函數(shù)可以在程序運行到此處時輸出一段提示文本,然后停留在此等待我們的輸入,輸入內(nèi)容后按下回車鍵,程序?qū)⒆x取輸入內(nèi)容并向下執(zhí)行。寫法為:
age = input('請輸入你的年齡:')print() 函數(shù)可以將內(nèi)容輸出到命令行中,內(nèi)容放到括號中,多項內(nèi)容時可用逗號分隔。寫法為:
print('你的年齡是', 20)int() 函數(shù)可以將字符串、浮點型轉(zhuǎn)換整數(shù)型。寫法為:
int(字符串或浮點數(shù))if,elif,else 組合使用,根據(jù)條件來選擇對應(yīng)的執(zhí)行路徑。寫法為:
if 條件 1:代碼塊 1 elif 條件 2:代碼塊 2 else:代碼塊 3while 語句來用執(zhí)行循環(huán)操作,根據(jù)條件來判斷代碼塊該不該被重復(fù)執(zhí)行。寫法為:
while 條件:代碼塊for 循環(huán)通常用于執(zhí)行遍歷操作。寫法為:
for 項 in 序列:代碼塊多語言比較:
Java 中的分支語句:
if (條件1) {代碼塊1 } else if (條件2) {代碼塊2 } else {代碼塊3 }C/C++ 中的分支語句:
if (條件1) {代碼塊1 } else if (條件2) {代碼塊2 } else {代碼塊3 }Go 中的分支語句:
if 條件1 {代碼塊1 } else if 條件2 {代碼塊2 } else {代碼塊3 }Java 中的循環(huán):
for (int i=0; i < 100; i++) {代碼塊 }// 或 for each 形式 for (int number: numbers) {代碼塊 }C/C++ 中的循環(huán):
for (int i=0; i < 100; i++) {代碼塊 }Go 中的循環(huán):
for i := 0; a < 100; i++ {代碼塊 }// 相當(dāng)于 while for 條件 {代碼塊 }// for each 形式 for index, item := range numbers {代碼塊 }?
將代碼放進(jìn)盒子——函數(shù)
我們之前介紹過一些函數(shù),如 print()、int()、input() 等。直接使用它們就可以獲得一些功能,如向命令行輸出內(nèi)容、轉(zhuǎn)換數(shù)字、獲取命令行輸入,那么它們到底是什么呢?
?
函數(shù)的初步理解
大家應(yīng)該都非常熟悉數(shù)學(xué)上的函數(shù),簡單來說數(shù)學(xué)上的函數(shù)就是一個映射關(guān)系,給定一個 x,經(jīng)映射后將得到 y 值,至于這其中的映射關(guān)系我們可以直接把它抽象為 y=f(x)。
程序中的函數(shù)與數(shù)學(xué)上的函數(shù)有一絲類似,我們也可以把它抽象地看作一個映射關(guān)系,給定輸入?yún)?shù)后,經(jīng)函數(shù)映射,返回輸出結(jié)果。如之前我們使用過的 int() 和 len():
數(shù)字 = int(字符串) 長度 = len(列表)給定輸入值,經(jīng)函數(shù)處理,返回輸出值,這是函數(shù)最單純的模式。
?
函數(shù)如何定義
Python 中函數(shù)的定義方式如下:
def 函數(shù)名(參數(shù)1, 參數(shù)2, ...):代碼塊函數(shù)的輸入值叫做函數(shù)參數(shù),如上面的「參數(shù)1」、「參數(shù)2」。函數(shù)參數(shù)的個數(shù)可以是任意個,如 0 個、1 個或多個。需要注意參數(shù)是有順序的,使用時要按對應(yīng)位置傳遞參數(shù)。
函數(shù)內(nèi)部的代碼塊就是函數(shù)的實現(xiàn)。所有的函數(shù)功能都實現(xiàn)于此。
函數(shù)的輸出結(jié)果叫函數(shù)的返回值。函數(shù)可以沒有返回值,也可以有一個或多個返回值。返回值通過 return 語句傳遞到函數(shù)外部,如:
def add(x, y):return x + y函數(shù)定義示例
我們來編寫一個函數(shù)試試,這個函數(shù)的需要的參數(shù)是年齡,返回值是年齡對應(yīng)的人生階段。
| 0-6 歲 | 童年 | 
| 7-17 歲 | 少年 | 
| 18-40 歲 | 青年 | 
| 41-65 歲 | 中年 | 
| 65 歲之后 | 老年 | 
這個功能好像有點眼熟,沒錯,上一章節(jié)中我們完成過這個功能,現(xiàn)在把這個功能改寫成函數(shù)。
可以這樣來定義這個函數(shù):
def stage_of_life(age):if age <= 6:return '童年'elif 7 <= age <=17:return '少年'elif 18 <= age <= 40:return '青年'elif 41 <= age <= 65:return '中年'else:return '老年'我們給函數(shù)起名為 stage_of_life,需要一個參數(shù) age,最終通過 return 語句返回對應(yīng)的人生階段,這個人生階段就是函數(shù)的返回值。
這里雖然有多個 return 語句,但是實際上每次函數(shù)使用時,只會有一個 return 語句被執(zhí)行。
副作用
上面這個示例中,給定一個參數(shù) age,便返回對應(yīng)的人生階段。函數(shù)內(nèi)部只是做了一個映射,并沒有對程序和系統(tǒng)的狀態(tài)作出影響,這樣的函數(shù)是純函數(shù)。
純函數(shù)是函數(shù)的一個特例,更普遍的情況是,函數(shù)包含一些會引起程序或系統(tǒng)狀態(tài)變化的操作,如修改全局變量、命令行輸入輸出、讀寫文件等,這樣的變化叫做函數(shù)的副作用。
副作用并不是不好的作用,它只是函數(shù)在輸入值和輸出值間映射之外,所附帶的作用。副作用在有些時候是不可避免的。
因為有了副作用,函數(shù)就不必完全遵從 輸入 -> 映射 -> 輸出 這種模式,函數(shù)可以在沒有參數(shù)或返回值的情況下,擁有其功能。如果你看到一個函數(shù)沒有參數(shù)或返回值,要自然的想到,那是副作用在發(fā)揮作用。
沒有參數(shù)沒有返回值:
def say_hello():print('hello')有參數(shù)沒有返回值:
def say_words(words):print(words)沒有參數(shù)有返回值:
def pi():return 3.14159?
函數(shù)的調(diào)用
函數(shù)定義完成后,就可以在后續(xù)的代碼中使用它了,對函數(shù)的使用叫做函數(shù)調(diào)用。
以前我們調(diào)用過 int()、len(),它們是內(nèi)置在 Python 語言中的函數(shù),也就是內(nèi)置函數(shù)。現(xiàn)在我們自己定義了 stage_of_life,調(diào)用的方式是一樣的:
def stage_of_life(age):if age <= 6:return '童年'elif 7 <= age <=17:return '少年'elif 18 <= age <= 40:return '青年'elif 41 <= age <= 65: return '中年'else:return '老年'stage = stage_of_life(18) print(stage)用參數(shù)的形式將數(shù)據(jù)傳遞給函數(shù),用賦值語句來接收返回值。
需要說明的是
-  函數(shù)有多個參數(shù)時,參數(shù)是有順序的,要按對應(yīng)位置將參數(shù)傳遞進(jìn)去。 >>> def minus(x, y): 
 … return x - y
 …
 >>> minus(1, 3)
 -2
 >>> minus(3, 1)
 2
-  函數(shù)需要先經(jīng)過定義,之后才能被調(diào)用,否則解釋器將找不到這個函數(shù)名,也就無法調(diào)用它。 未定義函數(shù)便直接調(diào)用,解釋器將報出「名字未定義」的錯誤: >>> stage = abc(18) 
 Traceback (most recent call last):
 File “”, line 1, in
 NameError: name ‘a(chǎn)bc’ is not defined
?
函數(shù)有什么用
從形式上來看,函數(shù)將一段代碼包裹起來,調(diào)用函數(shù)就像當(dāng)于執(zhí)行那段代碼。代碼所需要的數(shù)據(jù)我們可以通過函數(shù)參數(shù)的形式傳遞進(jìn)去,代碼的執(zhí)行結(jié)果通過返回值出傳遞出來。那么函數(shù)到底有什么用呢?
抽象
函數(shù)的價值主要體現(xiàn)在調(diào)用時,而不是定義時。調(diào)用時函數(shù)就像個盒子,使用者不需要了解其中有什么代碼,是什么樣的邏輯,只要知道怎么使用它的功能就足夠了。以 len() 函數(shù)為例,我們不知道這個函數(shù)的原理,但是能用它達(dá)到我們獲取列表長度的目的,這就是它的重要價值。
簡單來說函數(shù)的主要作用是抽象,屏蔽繁雜的內(nèi)部細(xì)節(jié),讓使用者在更高的層次上簡單明了地使用其功能。我們之前說過「計算機(jī)的世界里最重要的原理之一就是抽象」,函數(shù)就是其一個體現(xiàn)。
代碼復(fù)用
因為具有抽象的好處,函數(shù)也延伸出另一個作用——復(fù)用,或者叫代碼復(fù)用。也就是便于重復(fù)使用,節(jié)省代碼。舉個例子,假如我們想用程序計算并輸出 -33,456,-0.03 的絕對值,不用函數(shù)時我們這樣寫:
number = -33 if number > 0:print(number) else:print(-number)number = 456 if number > 0:print(number) else:print(-number)number = -0.03 if number > 0:print(number) else:print(-number)顯然有大量重復(fù)的代碼,這些重復(fù)代碼是可以避免的。用函數(shù)修改后如下:
def print_absolute(number):if number > 0:print(number)else:print(-number) print_absolute(-33) print_absolute(456) print_absolute(-0.03)代碼量減少很多,也能應(yīng)對未來的相同需求,這就是復(fù)用的好處。
?
什么時候用函數(shù)
看了上面函數(shù)的好處之后,想必你已經(jīng)知道在什么時候用函數(shù)了吧?
-  需要通過函數(shù)的形式把這些復(fù)雜性隔離出來,之后在使用時只需調(diào)用這個函數(shù)即可,清晰且優(yōu)雅。 
-  需要復(fù)用時。一段相似甚至完全一致的代碼在多處被使用,可以考慮將這段代碼定義為函數(shù),然后在各處去調(diào)用它。 
?
總結(jié)
函數(shù)的主要作用是抽象和代碼復(fù)用。
Python 中函數(shù)的定義方法:
def 函數(shù)名(參數(shù)1, 參數(shù)2, ...):代碼塊返回值通過 return 語句傳遞到函數(shù)外部。
多語言比較
Java 中所有的函數(shù)都需要定義在類中,類中的函數(shù)也叫做方法。
Java 中定義函數(shù):
int add(int x, int y) {return x + y }C/C++ 中定義函數(shù):
int add(int x, int y) {return x + y }Go 中定義函數(shù):
func max(x, y int) int {return x + y }?
知錯能改——錯誤處理、異常機(jī)制
為什么需要錯誤處理
我們之前寫的代碼能夠正常運行是建立在一個前提之下的,那就是假設(shè)所有的命令行輸入或者函數(shù)參數(shù)都是正確無誤的,并且執(zhí)行過程中每個環(huán)節(jié)都是可靠和符合預(yù)期的。
當(dāng)然,在程序的實際開發(fā)和使用過程中,這個前提是不能成立的,所有的假設(shè)都無法完全保證。比如:
- 用戶與程序交互時輸入不滿足規(guī)則的內(nèi)容。如,本應(yīng)該輸入年齡的地方輸入了一個漢字,或者年齡的取值為負(fù)數(shù),或者年齡遠(yuǎn)遠(yuǎn)超出人的正常壽命
- 函數(shù)或模塊的使用者采用非預(yù)期的使用方式。如,函數(shù)期望的參數(shù)是整數(shù)型,結(jié)果傳遞了一個列表
- 程序外部的環(huán)境發(fā)生變化等。如:讀取文件時,系統(tǒng)中不存在該文件;網(wǎng)絡(luò)傳輸時,發(fā)生連接故障
- ……
這些錯誤發(fā)生在程序運行階段,無法在編碼階段預(yù)知到它們是否會發(fā)生,但我們可以未雨綢繆,在代碼中對潛在錯誤做出處理,以避免對程序運行造成破壞性影響。
說明:開發(fā)程序過程中還有一種常見的錯誤,就是開發(fā)者編寫代碼時的語法錯誤、編譯錯誤以及運行時的 Bug。這些錯誤可以在開發(fā)時通過測試、調(diào)試、日志診斷等手段予以發(fā)現(xiàn)和解決,并不屬于本章節(jié)所講的錯誤處理機(jī)制的范疇。且不能用錯誤處理機(jī)制來規(guī)避 Bug。
如何處理錯誤
首先錯誤發(fā)生時,需要先捕獲到該錯誤,然后根據(jù)具體的錯誤內(nèi)容或類型,選擇后續(xù)處理的方式。
在 Python 中大多數(shù)情況下,錯誤是以拋出異常的形式報告出來。如列表的索引越界異常:
>>> fruit = ['apple', 'banana'][2] Traceback (most recent call last):      File "<stdin>", line 1, in <module> IndexError: list index out of range上面提示發(fā)生了「IndexError」錯誤,這個 IndexError 就是異常的一種。在這里它直接被解釋器捕捉到,然后將錯誤信息輸出到了命令行中。
我們也可以自己來捕獲異常,然后自定義處理方式。
try-except 語句捕獲異常
異常的捕獲使用 try-except 語句:
try:代碼塊1 except:代碼塊2執(zhí)行流程是,從 try 下的 代碼塊1 開始執(zhí)行,若其中有異常拋出,那么異常將會被捕獲,直接跳轉(zhuǎn)并執(zhí)行 except 下的 代碼塊2 。若 代碼塊1 一切正常,并沒有異常拋出,那么 代碼塊2 將不會被執(zhí)行。
也就是說 代碼塊1 是我們想要正常運行的代碼,而 代碼塊2 是當(dāng)錯誤發(fā)生時用于處理錯誤的代碼。
來看一個使用 try-except 時發(fā)生異常的例子:
>>> try:
 … ???? fruit = [‘a(chǎn)pple’, ‘banana’][2]
 … ???? print(fruit)
 … except:
 … ???? print(‘列表索引越界啦’)
 …
 列表索引越界啦
這里的執(zhí)行流程是,執(zhí)行 try 下的 ['apple', 'banana'][2],此時由于索引越界而產(chǎn)生異常,代碼 print(fruit) 將被跳過,轉(zhuǎn)而執(zhí)行 except 下的 print('列表索引越界啦')。
再來看一個無異常的例子:
>>> try:
 … ???? fruit = [‘a(chǎn)pple’, ‘banana’, ‘cherry’][2]
 … ???? print(fruit)
 … except:
 … ???? print(‘列表索引越界啦’)
 …
 cherry
可以看到無異常拋出時,try 下的代碼被全部執(zhí)行,except 下的代碼不會被執(zhí)行。
捕獲指定的異常
之前我們沒有直接指定要捕獲的異常類型,所以所有類型的異常都會被捕獲。
我們也可以顯式地指定要捕獲的異常種類。方法是:
try:代碼塊1 except 異常X as e:代碼塊2和之前的區(qū)別在于,多出了 異常X as e 這一部分。異常X 是指定的要捕獲的異常名,如 IndexError、NameError。as e 語句是將異常對象賦予變量 e,這樣 e 就可以在 代碼塊2 中使用了,如獲取錯誤信息。
如下是捕獲指定異常的例子:
>>> try:
 … ???? fruit = [‘a(chǎn)pple’, ‘banana’][2]
 … except IndexError as e:
 … ???? print(‘出現(xiàn)索引越界錯誤:’, e)
 …
 出現(xiàn)索引越界錯誤: list index out of range
這里我們顯式地指定要捕獲 IndexError 異常,并且將異常中的錯誤信息輸出出來。
顯式指定異常時,只有被指定的異常會被捕獲,其余異常將會被忽略。
捕獲指定的多個異常
上面是指定并捕獲一個異常,當(dāng)然也可以在一個 try 語句下指定并捕獲多個異常。有兩種方式:
try:代碼塊1 except (異常X, 異常Y, 異常Z) as e:代碼塊2 try:代碼塊1 except 異常X as e:代碼塊2 except 異常Y as e:代碼塊3 except 異常Z as e:代碼塊4如上,第一種方式是將多個異常放在一個 except 下處理,第二種方式將多個異常分別放在不同的 except 下處理。無論用哪種方式,異常拋出時,Python 會根據(jù)異常類型去匹配對應(yīng)的 except 語句,然后執(zhí)行其中代碼塊,若異常類型未能匹配到,則異常會繼續(xù)拋出。那么這兩種方式有什么區(qū)別呢?
- 第一種方式適用于多種異常可用相同代碼進(jìn)行處理的情況。
- 第二種情況適用于每個異常需要用不同代碼進(jìn)行處理的情況。
try-except-finally 語句
在之前介紹的 try-except 語句之后,還可以緊跟 finall 語句,如下:
try:代碼塊1 except 異常X as e:代碼塊2 finally:代碼塊3它的執(zhí)行流程是,
也就是說在 try-except 執(zhí)行流程的基礎(chǔ)上,緊接著執(zhí)行 finally 下的代碼塊,且 finally 下的代碼必定會被執(zhí)行。
finally 有什么用?舉個例子,我們有時會在 try 下使用一些資源(比如文件、網(wǎng)絡(luò)連接),而無論過程中是否有異常產(chǎn)生,我們在最后都應(yīng)該釋放(歸還)掉這些資源,這時就可以將釋放資源的代碼放在 finally 語句下。
常見的異常類型
下表中是 Python 常見的內(nèi)置異常:
| Exception | 大多數(shù)異常的基類 | 
| SyntaxError | 無效語法 | 
| NameError | 名字(變量、函數(shù)、類等)不存在 | 
| ValueError | 不合適的值 | 
| IndexError | 索引超過范圍 | 
| ImportError | 模塊不存在 | 
| IOError | I/O 相關(guān)錯誤 | 
| TypeError | 不合適的類型 | 
| AttributeError | 屬性不存在 | 
| KeyError | 字典的鍵值不存在 | 
| ZeroDivisionError | 除法中被除數(shù)為 0 | 
除此之外內(nèi)置異常還有很多,待日后慢慢積累掌握。
raise 語句主動拋出異常
之前的示例中,異常是在程序遇到錯誤無法繼續(xù)執(zhí)行時,由解釋器所拋出,我們也可以選擇自己主動拋出異常。
主動拋出異常的方法是使用 raise 語句:
raise ValueError()也可以同時指明錯誤原因:
raise ValueError("輸入值不符合要求")我們用示例來學(xué)習(xí)為什么要主動拋出異常,以及如何主動拋出異常。
之前我們在學(xué)習(xí)函數(shù)的時候?qū)戇^這樣一個函數(shù):
def stage_of_life(age):if age <= 6:return '童年'elif 7 <= age <=17:return '少年'elif 18 <= age <= 40:return '青年'elif 41 <= age <= 65: return '中年'else:return '老年'顯然這個函數(shù)沒有應(yīng)對可能出錯的情況。比如函數(shù)的 age 參數(shù)不能任意取值,要符合人類的年齡范圍才行,如果取值超出范圍就需要向函數(shù)調(diào)用方報告錯誤,這時就可以采取主動拋出異常的方式。
我們在函數(shù)內(nèi)檢驗輸入值的有效性,若輸入有誤則向外拋出異常,新增第 2 和第 3 行代碼:
def stage_of_life(age):if age < 0 or age > 150:raise ValueError("年齡的取值不符合實際,需要在 0 到 150 之間")if age <= 6:return '童年'elif 7 <= age <=17:return '少年'elif 18 <= age <= 40:return '青年'elif 41 <= age <= 65: return '中年'else:return '老年'這里檢查 age 的范圍是否在 0~150 之間,若不是則使用 raise 拋出 ValueError 異常,表示取值錯誤。
用不為 0——150 的數(shù)字執(zhí)行下函數(shù)看看:
>>> stage_of_life(-11)
 Traceback (most recent call last):
 ???? File “”, line 1, in
 ???? File “”, line 3, in stage_of_life
 ValueError: 年齡的取值不符合實際,需要在 0 到 150 之間
>>> stage_of_life(160)
 Traceback (most recent call last):
 ???? File “”, line 1, in
 ???? File “”, line 3, in stage_of_life
 ValueError: 年齡的取值不符合實際,需要在 0 到 150 之間
總結(jié)
在 Python 中大多數(shù)情況下,錯誤是以拋出異常的方式報告出來,可以針對潛在的異常來編寫處理代碼。
可使用 try-except 語句捕獲異常
異常的捕獲使用 try-except 語句:
try:代碼塊1 except 異常X as e:代碼塊2捕獲多個異常:
try:代碼塊1 except (異常X, 異常Y, 異常Z) as e:代碼塊2 try:代碼塊1 except 異常X as e:代碼塊2 except 異常Y as e:代碼塊3 except 異常Z as e:代碼塊4finally 語句緊接著 try-except 的流程執(zhí)行:
try:代碼塊1 except 異常X as e:代碼塊2 finally:代碼塊3使用 raise 語句可主動拋出異常:
raise ValueError()?
定制一個模子——類
查看數(shù)據(jù)類型
Python 中內(nèi)置有這么一個函數(shù),通過它可以查看變量或值的數(shù)據(jù)類型,它就是 type()。像這樣來使用:
type(變量或值)執(zhí)行幾個例子看看:
>>> type(100)
 <class ‘int’>
>>> type(3.14)
 <class ‘float’>
>>> type(‘words’)
 <class ‘str’>
>>> type(True)
 <class ‘bool’>
>>> type(None)
 <class ‘NoneType’>
>>> type([1, 2, 3])
 <class ‘list’>
執(zhí)行的結(jié)果是 <class '類型'> 形式,其中類型的含義是:
| int | 整數(shù)型 | 
| float | 浮點型 | 
| str | 字符串類型 | 
| bool | 布爾型 | 
| NoneType | None 類型 | 
| list | 列表類型 | 
上表中的這些數(shù)據(jù)類型,都內(nèi)置在 Python 中。
那 <class '類型'> 中的 class 是指什么呢?
?
類
class 是指面向?qū)ο缶幊谭妒街械囊粋€概念——類。Python 中的數(shù)據(jù)類型就是類,一個類對應(yīng)一種數(shù)據(jù)類型。類的具體對象中可以保存若干數(shù)據(jù),以及用于操作這些數(shù)據(jù)的若干函數(shù)。
我們來看一個例子:
我們常用的字符串類型,就是名為 str 的類。一個 str 中可以保存若干字符,并且針對這些字符提供了一系列的操作函數(shù)。
如 'hello' 就是一個 str 對象,我們可以把這個對象賦值給變量:
>>> words = ‘hello’
 >>> words
 ’hello’
str 對象自帶的 find() 函數(shù),可用于獲取字符的索引:
>>> words.find(‘e’)
 1
str 對象自帶的 upper() 函數(shù),可用于獲取英文字符的大寫形式:
>>> words.upper()
 ‘HELLO’
除此 str 之外,前面列表中的那些數(shù)據(jù)類型也都是類。
?
類的定義
像 str、int、list 這樣的類,是被預(yù)先定義好并且內(nèi)置在 Python 中的。
當(dāng)然,我們也可以自己來定義類。
類的定義方法是:
class 類名:代碼塊如:
class A:pass這里定義了一個非常簡單的類,名為 A。pass 是占位符,表示什么都不做或什么都沒有。
?
類的實例化
我們把類看作是自定義的數(shù)據(jù)類型,既然是類型,那么它只能用來表示數(shù)據(jù)的種類,不能直接用于保存數(shù)據(jù)。想要保存數(shù)據(jù),就需要先創(chuàng)建一個屬于這種類型的類似于容器的東西,這種容器就叫做對象(或稱實例)。通過類產(chǎn)生對象的過程叫實例化。
打個比方,類就相當(dāng)于圖紙,對象就相當(dāng)于按照圖紙所生產(chǎn)出來的產(chǎn)品。圖紙能決定產(chǎn)品的內(nèi)部構(gòu)造以及所具有的功能,但圖紙不能替代產(chǎn)品被直接使用。類能決定對象能保存什么樣的數(shù)據(jù),以及能擁有什么樣的函數(shù),但類不直接用來保存數(shù)據(jù)。
定義好類以后,可以像這樣實例化對象:
變量 = 類名()通過 類名() 這樣類似函數(shù)調(diào)用的方式生成出對象,并將對象賦值給 變量。
如實例化之前的類 A 并將對象賦值為 a:
>>> class A:
 …???? pass
 …
 >>> a = A()
查看變量 a 的類型:
>>> type(a)
 <class ‘__main__.A’>
可以看到類型是 __main__.A,表示模塊 __main__ 下的 A 類。模塊的概念后續(xù)章節(jié)中介紹,現(xiàn)在只需關(guān)注類即可。
可以看看 a 是什么:
>>> a
 <__main__.A object at 0x103d8e940>
a 是 A 的對象,位于內(nèi)存的 0x103d8e940 地址。
?
對象屬性
之前定義的 A 類是一個空的類,像一個空殼子,它的對象 a 并沒有保存任何數(shù)據(jù)。
想要在對象中保存數(shù)據(jù)該怎么做呢?
可以像這樣來定義類,實例化的時候就可以用參數(shù)的形式將數(shù)據(jù)傳入,并保存在對象中:
class 類名:def __init__(self, 數(shù)據(jù)1, 數(shù)據(jù)2, ...):self.數(shù)據(jù)1 = 數(shù)據(jù)1self.數(shù)據(jù)2 = 數(shù)據(jù)2...和之前相比類的內(nèi)部多了一個函數(shù) __init__(),__init__() 函數(shù)一方面可以接收要保存在對象中的數(shù)據(jù),另一方面也可以在實例化類的時候做一些初始化工作。
我們通過實際例子來學(xué)習(xí)。之前介紹的類(數(shù)據(jù)類型)要么保存一個數(shù)據(jù),要么保存多個數(shù)據(jù),假如現(xiàn)在想要一個不多不少只保存兩個數(shù)據(jù)的類,這就需要我們自己來定義了。如下:
class Pair:def __init__(self, first, second):self.first = firstself.second = second我們將這個類命名為 Pair,即表示數(shù)據(jù)對。
它的 __init__() 函數(shù)有三個參數(shù):
實例化的時候像這樣傳入數(shù)據(jù):
pair = Pair(10, 20)這個過程中會自動調(diào)用 __init__() 函數(shù),并將 10 傳給了 first 參數(shù),將 20 傳給了 second 參數(shù),而 __init__() 的第一個參數(shù) self 是不需要傳值的,Python 會自動填充這個參數(shù)。
實例化之后我們可以通過 pair 對象來獲取數(shù)據(jù)對中的數(shù)據(jù),像這樣:
pair.first pair.second>>> pair = Pair(10, 20)
>>> pair.first
 10
 >>> pair.second
 20
通過 pair = Pair(10, 20) 來實例化 Pair 類,得到對象的變量 pair,使用 pair.first、pair.second 就可以獲得對象中保存的數(shù)據(jù)了。
first 和 second 叫做 Pair 類的對象屬性,一般也可以直接叫作屬性。
我們不僅可以通過對象獲取對象屬性的值,也能修改對象屬性值。如:
>>> pair = Pair(10, 20)
>>> pair.first = 1000
 1000
 >>> pair.first
 1000
?
對象方法
剛才在類中定義了對象屬性,也可以在類中定義一些函數(shù)。這樣的函數(shù)可直接由對象調(diào)用,例如我們之前學(xué)過的 list.append() 。
定義在類中,供對象調(diào)用的函數(shù)稱為對象方法,一般也可以直接叫作方法。定義方式如下:
class 類名:def 函數(shù)1(self, 參數(shù)1, 參數(shù)2):...定義對象方法時第一個參數(shù)默認(rèn)使用 self,定義時必須有這個參數(shù),但是調(diào)用時不必傳遞。之前介紹過的 __init__() 就是一個對象方法,不過是個特殊的對象方法。
我們在之前 Pair 類的基礎(chǔ)上定義一個方法,功能是交換對象的 first 和 second 屬性的值。來實現(xiàn)一下:
class Pair:def __init__(self, first, second):self.first = firstself.second = seconddef swap(self):self.first, self.second = self.second, self.first這個方法被命名為 swap,無需傳遞參數(shù),內(nèi)部通過
self.first, self.second = self.second, self.first實現(xiàn)了 self.first 和 self.second 兩個值的交換。
執(zhí)行下看看:
>>> pair = Pair(10, 20)
 >>> pair.first
 10
 >>> pair.second
 20
>>> pair.swap()
>>> pair.first
 20
 >>> pair.second
 10
?
總結(jié)
定義類的方式是:
class 類名:代碼塊在類中定義方法:
class 類名:def 方法(self, 參數(shù)1, ...):self.數(shù)據(jù)1 = 數(shù)據(jù)1...可以在 __init__ 方法中定義對象屬性,之后在實例化類的時候傳入數(shù)據(jù)。如:
class Pair:def __init__(self, first, second):self.first = firstself.second = secondpair = Pair(10, 20)?
更大的代碼盒子——模塊和包
什么是模塊
之前介紹過兩種運行 Python 代碼的方式,一種是解釋器的交互模式,另一種是直接運行 Python 代碼文件。
在 Python 中,每一個 Python 代碼文件就是一個模塊。寫程序時,我們可以將代碼分散在不同的模塊(文件)中,然后在一個模塊里引用另一個模塊的內(nèi)容。
?
模塊的導(dǎo)入
在一個模塊中引用(導(dǎo)入)另一個模塊,可以使用 import 語句:
import 模塊名這里的模塊名是除去 .py 后綴的文件名稱。如,想要導(dǎo)入模塊 abc.py,只需 import abc。
import 模塊之后,就可以使用被導(dǎo)入模塊中的名字(變量、函數(shù)類)。方式如下:
模塊名.變量 模塊名.函數(shù) 模塊名.類導(dǎo)入及使用模塊示例
我們用個例子來試驗下模塊的導(dǎo)入和使用,在這個例子中,農(nóng)民種下果樹,然后等待果樹結(jié)果收獲。
在同一個目錄下創(chuàng)建兩個模塊:
tree_farmer # 目錄名|___tree.py # 文件名|___farmer.py # 文件名第一個模塊名為 tree.py,內(nèi)容如下:
import randomfruit_name = ''def harvest():return [fruit_name] * random.randint(1, 9)代碼中各個變量和函數(shù)的功能如下:
- fruit_name 用來保存水果名稱。將在函數(shù) harvest() 中使用;
- random.randint(1, 9),隨機(jī)生成 1~9 中的一個數(shù);
- [fruit_name] * 數(shù)字,該形式是將列表項重復(fù)若干遍。比如執(zhí)行 ['X'] * 3 將得到 ['X', 'X', 'X'];
- 總體而言,harvest() 函數(shù)返回一個包含 1~9 個列表項的列表,其中每個項都是 fruit_name 的值。
第二個模塊名為 farmer.py,內(nèi)容如下:
import treeprint('種下一棵果樹。') tree.fruit_name = 'apple'print('等啊等,樹長大了,可以收獲了!') fruits = tree.harvest() print(fruits)代碼中,
- 第一行用 import tree 將 tree.py 模塊導(dǎo)入進(jìn)來(使用 import 導(dǎo)入時不需要寫 .py 后綴);
- 導(dǎo)入 tree 模塊后,就可以使用其中的變量和函數(shù)了。將 tree.fruit_name 設(shè)置為 apple,調(diào)用 tree.harvest() 來收獲 apple。
執(zhí)行下模塊 farmer.py 看看:
? ~ python3 farmer.py
 種下一棵果樹。
 等啊等,樹長大了,可以收獲了!
 [‘a(chǎn)pple’, ‘a(chǎn)pple’, ‘a(chǎn)pple’, ‘a(chǎn)pple’]
說明:apple 隨機(jī)出現(xiàn) 1~9 個,所以你的結(jié)果可能和這里不一樣。
可以看到,執(zhí)行 farmer.py 時,由于 tree.py 模塊被導(dǎo)入,所以可以在 farmer.py 中使用 tree.py 的內(nèi)容。
標(biāo)準(zhǔn)庫模塊的導(dǎo)入
上面的例子中,我們自己定義了模塊,然后在其它模塊中使用它。其中有個地方不知道你有沒有注意到,tree.py 的第一行代碼是 import random,random 并不是我們所定義的模塊,那它是從哪里來的呢?
random 是標(biāo)準(zhǔn)庫中的一個模塊。標(biāo)準(zhǔn)庫是由 Python 官方開發(fā)的代碼庫,和解釋器一起打包分發(fā),其中包含非常多實用的模塊,我們在使用時直接 import 進(jìn)來即可。
?
執(zhí)行模塊時傳入?yún)?shù)
剛才我們用這種方式來執(zhí)行模塊:
python3 模塊文件名其實我們還可以進(jìn)一步將參數(shù)傳遞到模塊中去,像這樣:
python3 模塊文件名 參數(shù)1 ...參數(shù)n參數(shù)傳遞到模塊中以后,我們可以通過 sys 模塊來取出這些參數(shù),參數(shù)放在 sys.argv 列表中:
import sys模塊文件名 = sys.argv[0] 參數(shù)1 = sys.argv[1] 參數(shù)N = sys.argv[N]首先需要導(dǎo)入 sys 模塊,這是個標(biāo)準(zhǔn)庫中的模塊。sys.argv 是個列表,執(zhí)行模塊時被傳遞進(jìn)來的參數(shù)保存在其中,它的列表項分別為:
- sys.argv[0] 保存當(dāng)前被執(zhí)行模塊的文件名
- sys.argv[1] 保存第 1 個參數(shù)
- sys.argv[2] 保存第 2 個參數(shù)
- 依次類推
之前種果樹那個例子中,farmer.py 固定種蘋果樹,我們可以改進(jìn)一下,具體種什么樹由傳遞的模塊參數(shù)來決定。
修改 farmer.py 的代碼,內(nèi)容如下:
import sys # 新增 import treeprint('種下一棵果樹。') tree.fruit_name = sys.argv[1] # 將 'apple' 改為 參數(shù) sys.argv[1]print('等啊等,樹長大了,可以收獲了!') fruits = tree.harvest() print(fruits)以「banana」為例執(zhí)行下看看:
? ~ python3 farmer.py banana
 種下一棵果樹。
 等啊等,樹長大了,可以收獲了!
 [‘banana’, ‘banana’, ‘banana’]
在這個例子中 sys.argv 的值是:
- sys.argv[0]: farmer.py
- sys.argv[1]: banana
?
什么是包
之前我們將定義的兩個模塊放在同一目錄下,然后通過 import 語句來相互引用,這是一種扁平的模塊組織結(jié)構(gòu),當(dāng)模塊數(shù)量很大的時候就很不靈活了,也難以維護(hù)。
Python 中可以用文件樹這樣的樹形結(jié)構(gòu)來組織模塊,這種組織形式下的模塊集合稱為包(Package)。
比如包的結(jié)構(gòu)可以是這樣的:
包/ ├── __init__.py ├── 模塊1.py ├── 模塊2.py ├── 子包1/├── __init__.py├── 模塊3.py└── 模塊4.py └── 子包2/├── __init__.py├── 模塊5.py└── 孫子包1/├── __init__.py└── 模塊6.py這是個很明顯的層級結(jié)構(gòu)——包里面包含子包、子包包含孫子包…… 單獨將子包或?qū)O子包拿出來,它們也是包。
包的存在形式是目錄,模塊的存在形式是目錄下的文件。所以我們可以很容易地構(gòu)造出這樣一個包,只要在文件系統(tǒng)中創(chuàng)建相應(yīng)的目錄和文件即可。
需要注意的是,每個層級的包下都需要有一個 __init__.py 模塊。這是因為只有當(dāng)目錄中存在 __init__.py 時,Python 才會把這個目錄當(dāng)作包。
?
包的導(dǎo)入
導(dǎo)入包中模塊的方法是:
import 包.子包.模塊從最頂層的包開始依次向下引用子包,直至目標(biāo)模塊。
如,從上面示例的包結(jié)構(gòu)中,
導(dǎo)入 模塊1.py,使用:
import 包.模塊1導(dǎo)入 模塊3.py,使用:
import 包.子包1.模塊3導(dǎo)入 模塊6.py,使用:
import 包.子包2.孫子包1.模塊6?
為什么需要模塊和包
模塊的存在是為了更好的組織代碼。將不同功能的代碼分散在不同模塊中,清晰地劃分出各個模塊的職責(zé),有利于使用和維護(hù)代碼,同時也可避免模塊中的內(nèi)容過長。
包的存在是為了更好的組織模塊。與模塊同理,包在更高的抽象層次上組織著代碼。
?
總結(jié)
模塊可以更好的組織代碼,它的存在形式是文件。包的可以更好的組織模塊,它的存在形式是目錄。
導(dǎo)入模塊使用 import 語句:
import 模塊名導(dǎo)入包下的模塊:
import 包名.模塊名模塊導(dǎo)入后,可以使用該模塊中所定義的名字(變量、函數(shù)類)。方式如下:
模塊名.變量 模塊名.函數(shù) 模塊名.類?
練習(xí)——密碼生成器
學(xué)習(xí)編程一定要多加練習(xí),只靠單純地閱讀是無法真正掌握編程方法的,只有反復(fù)練習(xí)才能真正領(lǐng)悟編程思想。我們已經(jīng)學(xué)習(xí)了一些 Python 知識了,說多不多說少也不少,是時候來運用一下了。
開始之前我們先來聊聊賬號的話題。當(dāng)今互聯(lián)網(wǎng)十分普及,大家一定注冊了很多 APP 和網(wǎng)站吧,大大小小的賬號少則十幾個多則可能數(shù)十個。大家的密碼是都怎么設(shè)置的呢,所有賬號用的是同一個密碼嗎?
所有賬號用同一個密碼是件很危險的事,一個平臺上的賬號泄漏了,有可能殃及其它平臺。安全的做法是每個平臺使用單獨的密碼,并且密碼間的關(guān)聯(lián)性盡可能的小,這樣就算一個密碼泄漏了也不會將影響擴(kuò)大。
每個平臺都使用一個單獨的密碼,并且密碼間的關(guān)聯(lián)性盡要可能的小,那十幾個甚至幾十個平臺的密碼要怎么來取呢?我們可以用密碼自動生成器呀,現(xiàn)在就來動手做一個!
?
密碼生成器要求
我們對密碼生成器的要求是:
?
實現(xiàn)思路
要求有了,怎么來實現(xiàn)呢?
實現(xiàn)方法非常多,不同的人有不同的思路。在這里我們一起來分析吧。
- 首先,隨機(jī)生成 N 位密碼——換一種角度這其實相當(dāng)于,準(zhǔn)備好大寫字母集合、小寫字母集合、數(shù)字集合和特殊字符集合,從中隨機(jī)挑出 N 個字符,然后將它們排成一排。你看,這樣我們不就將一個籠統(tǒng)的需求轉(zhuǎn)化成了可以用編程來解決的實際問題了嗎?
- 其次,密碼至少要包含一個大寫字母、一個小寫字母、一個數(shù)字、一個特殊字符,且可指定密碼長度——要滿足這個要求,有個簡單的辦法,我們從頭開始,密碼第一位放
 大寫字母,第二位放小寫字母,第三位放數(shù)字,第四位放特殊字符,剩余的 N - 4 個字符就依次放任意的字符。
- 再次,要解決從字符集合中隨機(jī)取字符的問題——我們之前學(xué)習(xí)過 random.randint() 函數(shù),它可以隨機(jī)生成一個數(shù)字,我們就將這個隨機(jī)數(shù)字當(dāng)作索引去字符集合中取值(字符集合可以是 str 或 list 形式),這樣就達(dá)到了隨機(jī)從字符集合中取字符的目的。
- 最后,通過命令行交互接收密碼長度,這個比較簡單,使用 input() 即可。
?
實現(xiàn)
經(jīng)過剛才的分析,我們可以將這個程序劃分為三個主要部分:
這三個部分各司其職,共同構(gòu)成我們的密碼生成器。
我們完全可以只用我們之前學(xué)習(xí)過的那些知識,來實現(xiàn)這三個部分,并完整地構(gòu)建整個程序。
命令行交互部分的實現(xiàn)
程序被執(zhí)行后,首先給出提示信息要求用戶指定密碼長度,然后接收用戶所輸入的值,并判斷值是否符合要求。
實現(xiàn)如下:
password_length = input('請輸入密碼長度(8~20):') password_length = int(password_length)if password_length < 8 or password_length > 20:raise ValueError('密碼長度不符')獲取到密碼長度以后,就該使用「密碼邏輯部分」來進(jìn)一步完成工作,在這里我們把「密碼邏輯部分」封裝成一個函數(shù),只需調(diào)用它就可以獲取到想要的密碼。也就是下面代碼中的 generate_password() 函數(shù)。
password = generate_password(password_length) print(password)對于「命令行交互部分」而言,它不需要知道「密碼邏輯部分」中的實現(xiàn)細(xì)節(jié),只要調(diào)用「密碼邏輯部分」能獲取到密碼就足夠了。
密碼邏輯部分的實現(xiàn)
「密碼邏輯部分」是一個函數(shù),它以密碼長度作為參數(shù),返回我們所要的隨機(jī)密碼。
它生成密碼的策略是,先隨機(jī)生成一個大寫字母,以此作為起始密碼;再生成一小寫字母,追加到密碼末尾;再生成一個數(shù)字,追加到密碼末尾;再生成一個特殊字符,追加到密碼末尾。這樣就擁有 4 位密碼了,且滿足包含大寫字母、小寫字母、數(shù)字、特殊字符的要求。密碼剩余的幾位,依次隨機(jī)取任意字符并追加到密碼末尾。
上述生成隨機(jī)字符的功能將由「隨機(jī)字符生成部分」提供,我們將「隨機(jī)字符生成部分」封裝成 RandomChar 類,并單獨放置在 randomchar 模塊中。使用 RandomChar 類對象的方法即可獲取隨機(jī)字符:
- 獲取大寫字母: random_char.uppercase()
- 獲取小寫字母: random_char.lowercase()
- 獲取數(shù)字: random_char.digit()
- 獲取特殊字符: random_char.special()
- 獲取上述任意一種字符:random_char.anyone()
無需在這個層面上關(guān)心 RandomChar 類對象是怎么做到獲取隨機(jī)字符的,對當(dāng)前這個部分來講這并不重要,重要的是如何運用其它部分的能力來達(dá)到當(dāng)前部分的目的。
我們來實現(xiàn)整個「密碼邏輯部分」:
def generate_password(length):if length < 4:raise ValueError('密碼至少為 4 位')random_char = randomchar.RandomChar()password = random_char.uppercase() # 用一個隨機(jī)的大寫字符作為起始密碼password += random_char.lowercase() # 將一個隨機(jī)的小寫字符拼接在密碼末尾password += random_char.digit() # 將一個隨機(jī)的數(shù)字拼接在密碼末尾password += random_char.special() # 將一個隨機(jī)的特殊字符拼接在密碼末尾count = 5 # 此時的密碼長度為 4,再向后拼接要從第 5 位開始,所以 count 為 5。while count <= length: # 如果 count 大于密碼長度則退出循環(huán)password += random_char.anyone() # 隨機(jī)取出一個字符拼接在密碼末尾count += 1return password上面代碼中以 # 號開頭的代碼,稱為注釋,如 # 用一個隨機(jī)的大寫字符作為起始密碼。注釋用于對代碼作注解,只是寫給代碼閱讀者看的,并不會被解釋器執(zhí)行。注釋的范圍是 # 及其之后的該行的所有字符。
隨機(jī)字符生成部分的實現(xiàn)
「隨機(jī)字符生成部分」被封裝成 RandomChar 類,并單獨放置在 randomchar 模塊中,使用它的對象方法即可獲取隨機(jī)字符。
我們需要先準(zhǔn)備好各類字符的完整集合,這里采用字符串的形式存放:
- 大寫字母:'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- 小寫字母:'abcdefghijklmnopqrstuvwxyz'
- 數(shù)字:'0123456789'
- 特殊字符:'~!@#$%^&*'
可以把這些字符串分別保存在對象屬性中:
class RandomChar:def __init__(self):self.all_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'self.all_lowercase = 'abcdefghijklmnopqrstuvwxyz'self.all_digits = '0123456789'self.all_specials = '~!@#$%^&*'再來準(zhǔn)備一個方法 pick_random_item(),這個方法接受一個字符串作為參數(shù),隨機(jī)返回這個字符串中的一個字符。其內(nèi)部可以使用 random.randint() 隨機(jī)生成一個數(shù)字,然后把這個隨機(jī)數(shù)字當(dāng)作索引去字符串中取值,以此生成隨機(jī)字符。
pick_random_item() 方法實現(xiàn)如下:
def pick_random_item(self, sequence):random_int = random.randint(0, len(sequence) - 1) # 調(diào)用 random.randint() 生成一個隨機(jī)數(shù)字作為索引去字符串中取值,因為隨機(jī)生成的數(shù)字不可超過字符串長度,所以取值范圍為 0, len(sequence) - 1。return sequence[random_int]有了上面這個從任意字符串中隨機(jī)取值的功能,我們就可以把它應(yīng)用到大寫字母、小寫字母、數(shù)字、特殊字符的集合(字符串形式)中去,這樣就可以隨機(jī)獲取這四種字符了。
分別對應(yīng)四個方法:
def uppercase(self):return self.pick_random_item(self.all_uppercase) # 調(diào)用 pick_random_item 隨機(jī)從 all_uppercase 字符串中取出一個大寫字母def lowercase(self):return self.pick_random_item(self.all_lowercase) # 調(diào)用 pick_random_item 隨機(jī)從 all_lowercase 字符串中取出一個小寫字母def digit(self):return self.pick_random_item(self.all_digits) # 調(diào)用 pick_random_item 隨機(jī)從 all_digits 字符串中取出一個數(shù)字def special(self):return self.pick_random_item(self.all_specials) # 調(diào)用 pick_random_item 隨機(jī)從 all_specials 字符串中取出一個特殊字符最后還有需要一個不區(qū)分上述字符種類,隨機(jī)取任意字符的對象方法。
我們可以把大寫字母、小寫字母、數(shù)字、特殊字符的集合拼接在一起,形成一個更大的集合,然后隨機(jī)從中取值。
可隨機(jī)取任意字符的 anyone() 方法如下:
def anyone(self):# 將四種字符拼接在一起,形成一個大字符串 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*',然后調(diào)用 pick_random_item 方法從中隨機(jī)取出一個字符。return self.pick_random_item(self.all_uppercase + self.all_lowercase + self.all_digits + self.all_specials)至此就全部實現(xiàn)完了,大功告成!
整個程序的調(diào)用鏈?zhǔn)?#xff1a;「命令行交互部分」->「密碼邏輯部分」->「隨機(jī)字符生成部分」。每一個部分各司其職,共同完成這個程序。
?
完整代碼
我們的代碼位于兩個模塊中。
「命令行交互部分」和「密碼邏輯部分」位于 password_generator.py 模塊,完整代碼如下:
password_generator.py
import randomchardef generate_password(length):if length < 4:raise ValueError('密碼至少為 4 位')random_char = randomchar.RandomChar()password = random_char.uppercase()password += random_char.lowercase()password += random_char.digit()password += random_char.special()count = 5while count <= length:password += random_char.anyone()count += 1return passwordpassword_length = input('請輸入密碼長度(8~20):') password_length = int(password_length)if password_length < 8 or password_length > 20:raise ValueError('密碼長度不符')password = generate_password(password_length) print(password)「隨機(jī)字符生成部分」位于 randomchar.py 模塊,完整代碼如下:
randomchar.py
import randomclass RandomChar:def __init__(self):self.all_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'self.all_lowercase = 'abcdefghijklmnopqrstuvwxyz'self.all_digits = '0123456789'self.all_specials = '~!@#$%^&*'def pick_random_item(self, sequence):random_int = random.randint(0, len(sequence) - 1)return sequence[random_int]def uppercase(self):return self.pick_random_item(self.all_uppercase)def lowercase(self):return self.pick_random_item(self.all_lowercase)def digit(self):return self.pick_random_item(self.all_digits)def special(self):return self.pick_random_item(self.all_specials)def anyone(self):return self.pick_random_item(self.all_uppercase + self.all_lowercase + self.all_digits + self.all_specials)?
運行示例
來執(zhí)行一下程序看看:
? ~ python3 password_generator.py
 請輸入密碼長度(8~20):16
 Aw6~8a3$AeAo4kSN
?
補(bǔ)充說明
為了可以僅利用之前學(xué)過的知識來實現(xiàn)這個程序,這里放棄了一些更簡潔或更恰當(dāng)?shù)?Python 用法。比如
- 循環(huán)若干次這里用了 while 循環(huán),可以使用 for _ in range(x) 的方式替代
- 把隨機(jī)數(shù)字當(dāng)作索引然后從字符串中取值,可以直接使用 random.choice() 函數(shù)替代
- RandomChar 中的對象屬性和對象方法,可直接定義成類屬性和類方法
- ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’ 這類字符集合不需要手工書寫,使用 string 模塊即可獲取,如 string.ascii_uppercase
大家若有興趣可以自己改進(jìn)這個程序。
高級的用法和概念將會在之后章節(jié)中介紹,不過值得一提的是,樸素的方法也是有價值的!
?
在學(xué)習(xí)中有疑問或者不懂的地方歡迎小伙伴評論留言!
之后持續(xù)為大家更新Python入門及進(jìn)階技術(shù)分享!
覺得有用的小伙伴記得點贊關(guān)注喲!
灰小猿陪你一起進(jìn)步!
同時給大家推薦一個CSDN官方的Python全棧知識圖譜學(xué)習(xí)路線,涵蓋Python六大模塊,100+知識點,內(nèi)容梳理全面,難點,痛點羅列齊全,可以說這本知識圖譜上的每一句話,都價值千金,這是CSDN聯(lián)合6位一線Python工程師,花費3個月反復(fù)打磨,旨在幫助大家Python知識體系,具備實戰(zhàn)經(jīng)驗,破解開發(fā)和面試難題!非常適合學(xué)習(xí)Python的小伙伴們!原價129,CSDN官方限時限量29元!
總結(jié)
以上是生活随笔為你收集整理的【全网力荐】堪称最易学的Python基础入门教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 2、安装VisualStudio、Uni
- 下一篇: 刚进公司就给我来个下马威!让我这个初学者
