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