該筆記源自尚學堂,非原創
Python 單詞是“大蟒蛇”的意思。但是龜叔不是喜歡蟒蛇才起這個名字,而是正在追劇:英國電視喜劇片《蒙提·派森的飛行馬戲團》(Monty Python and the Flying Circus)。
使用 www.python.org 提供的 interactive shell 入門 Python
· 特點
- 可讀性強
可讀性遠比聽上去重要的多得多。一個程式會被反覆的修改,可讀性強意味著讓你可以在更短時間內學習和記憶,直接提高生產率。
- 簡潔,簡潔,簡潔
研究證明,程式設計師每天可編寫的有效程式碼數是有限的。完成同樣功能只用一半的程式碼,其實就是提高了一倍的生產率。
Python 是由 C 語言開發,但是不再有 C 語言中指標等複雜資料型別,Python 的簡潔性讓開發難度和程式碼幅度大幅降低,開發任務大大簡化。程式設計師再也不需要關注複雜的語法,而是關注任務本身。
完成這樣的螺旋線,程式碼只有幾行:
import turtle t = turtle.Pen() for x in range(360): t.forward(x) t.left(59) |
- 物件導向
- 免費和開源
- 可移植性和跨平臺
Python 會被編譯成與作業系統相關的二進位制程式碼,然後再解釋執行。這種方式和 java 類似,大大提高了執行速度,也實現了跨平臺。
- 豐富的庫(豐富的標準庫, 多種多樣的擴充套件庫)
- 可擴充套件性。 可嵌入到 C 和 C++語言。 膠水式語言。
· 應用範圍
- 科學計算
- 人工智慧
- WEB 服務端和大型網站後端。
YouTube、gmail 等應用基於 python 開發。
- GUI 開發(圖形使用者介面開發)
- 遊戲開發
- 移動裝置
嵌入式裝置
- 系統運維
- 大資料
- 雲端計算
· 什麼時候不應該用 Python
1. Python 是解釋執行。效能較低。
因此,一些影響效能的功能可以使用 C/C++/JAVA/GO(GO 是一種新語言,寫起了像 Python,效能像 C)去開發。
不過,不用擔心 Python 直譯器會越來越快。
· 版本和相容問題解決方案
目前主要兩個版本:Python2 和 Python3
Python2:
2000 年 10 月釋出。最新版本是 2.7,已經停止更新,不會再有 2.8 以後了。預計
2020 年退出歷史舞臺。
Python3:
2008 年釋出。Python3 有了較大的提升,不相容 Python2。
相容問題解決:
1. Python3 的很多新特性也被移植到了 Python2.7,作為過渡。如果程式可以在 2.7 執行,可以通過一個名為 2to3(Python 自帶的一個指令碼)的轉換工具無縫遷移到 Python3.
2. 建議大家學習從 Python3 開始,畢竟這才是未來。
· Python 直譯器
Python 程式的執行依賴於 Python 直譯器。常用的 Python 直譯器有:
- CPython
使用 c 語言實現的直譯器,最常用的直譯器。通常說的直譯器指的就是它。
- Jython
使用 java 語言實現的直譯器。Jython 可以直接呼叫 java 類庫,適合在 java 平臺上開發
- IronPython
.NET 平臺上使用的直譯器。可直接呼叫.NET 平臺的類,適合.NET 平臺上開發
- PyPy
使用 Python 語言實現的直譯器
Python 開發入門
Python 下載安裝和配置
進入官網:www.python.org/downloads/
下載
安裝(和安裝一般軟體區別不大)
環境變數問題
勾選:“Add Python to environment variable”。 這樣就會將 Python 新增到環境變數 Path 中,我們可以在 windows 的命令列模式下執行 Python 直譯器。
問題:由於 dll 缺失造成安裝出錯:
下載 dll 修復軟體,執行修復即可,重啟計算機。
Python 開發環境
開發環境,英文是 IDE(Integrated Development Environment 整合開發環境)。
不要糾結於使用哪個開發環境。開發環境本質上就是對 Python 直譯器 python.exe 的封裝,核心都一樣。可以說:“開發環境 IDE,只是直譯器的一個外掛而已”,只是為了讓程式設計師更加方便程式設計,減少出錯率,尤其是拼寫錯誤。
常用的開發環境如下:
- IDLE
- Pycharm
- wingIDE
- Eclipse
- IPython
互動模式(指令碼 shell 模式)
- 進入命令列視窗,輸入:python
- >>>即為“提示符”
- 關閉互動視窗:
(1) Ctrl+Z 和回車
(2) 輸入 quit()命令
(3) 直接關閉命令列視窗
- 中斷程式執行:ctrl+C
互動模式工作原理和 Python 處理檔案的方式一樣。除了一點:當你輸入一些值時,互動模式會自動列印輸出。Py 檔案中則必須使用 print 語句。
IDLE 開發環境使用入門
IDLE 介紹
- IDLE 是 Python 的官方標準開發環境,Python 安裝完後同時就安裝了 IDLE。
- IDLE 已經具備了 Python 開發幾乎所有功能(語法智慧提示、不同顏色顯示不同型別等等),也不需要其他配置,非常適合初學者使用。
- IDLE 是 Python 標準發行版內建的一個簡單小巧的 IDE,包括了互動式命令列、編輯器、偵錯程式等基本元件,足以應付大多數簡單應用。
- IDLE 是用純 Python 基於 Tkinter 編寫,最初的作者正是 Python 之父 Guido van
IDLE實(Rossum。) 操
1. 互動模式
啟動 IDLE,預設就是進入互動模式。
1 編寫和執行 Python 原始檔
IDLE 常用快捷鍵
快捷鍵 |
| 說明 |
Alt+N | Alt+P | 檢視歷史命令上一條、下一條 |
Ctrl+F6 |
| 重啟 shell,以前定義的變數全部失效 |
F1 |
| 開啟幫助文件 |
Alt+/ |
| 自動補全前面曾經出現過的單詞 |
Ctrl + [ | Ctrl + ] | 縮排程式碼和取消縮排 |
Alt+M |
| 開啟模組程式碼,先選中模組,然後按下此快捷鍵,會幫你開啟改模組的 py 原始碼供瀏覽 |
Alt+C |
| 開啟類瀏覽器,方便在原始碼檔案中的各個方法體之間切換 |
F5 | 執行程式 |
第一個 Python 源程式
原始碼
print("a") print("b") print("c")
將原始碼儲存到:d:/python_exec/mypy01.py
在 IDLE 中單擊 F5 或者 run-->run module 執行這個源程式。
第一個 Python 程式中需要注意的小要點:
- 不要在程式中,行開頭處增加空格。空格在 Python 中有縮排的含義。
- 符號都是英文符號,不是中文。比如:(,”
程式基本格式
- 恰當的空格,縮排問題
(1) 邏輯行首的空白(空格和製表符)用來決定邏輯行的縮排層次,從而用來決定語句的分組。
(2) 語句從新行的第一列開始。
(3) 縮排風格統一:每個縮排層次使用 單個製表符 或四個空格(IDE 會自動將製表符設定成 4 個空格)
Python 用縮排而不是{}表示程式塊
- Python 區分大小寫
- 註釋
(1) 行註釋
每行註釋前加#號。當直譯器看到#,則忽略這一行#後面的內容
(2) 段註釋
使用三個連續單引號(''')。當解釋看到''',則會掃描到下一個''',然後忽略他們之間的內容。
開始學習圖形化程式設計
為了讓初學者更加容易接受程式設計,我們這裡先從海龜畫圖開始講解。這樣,大家在不接
觸其他程式設計概念時,就能開始做出一些簡單的效果。提高興趣,寓教於樂。
>>> import turtle | #匯入 turtle 模組 |
>>> turtle.showturtle() | #顯示箭頭 |
>>> turtle.write("高淇") | #寫字串 |
>>> turtle.forward(300) | #前進 300 畫素 |
>>> turtle.color("red") | #畫筆顏色改為 red |
>>> turtle.left(90) >>> turtle.forward(300) | #箭頭左轉 90 度 |
>>> turtle.goto(0,50) >>> turtle.goto(0,0) | #去座標(0,50) |
>>> turtle.penup() >>> turtle.goto(0,300) | #抬筆。這樣,路徑就不會畫出來 |
>>> turtle.pendown() | #下筆。這樣,路徑就會畫出來 |
>>> turtle.circle(100) | #畫圓 |
繪製奧運五環標記
原始碼:
import turtle turtle.width(10)
turtle.color("blue") turtle.circle(50)
turtle.color("black") turtle.penup() turtle.goto(120,0) turtle.pendown() turtle.circle(50)
turtle.color("red") turtle.penup() turtle.goto(240,0) turtle.pendown() turtle.circle(50)
turtle.color("yellow") turtle.penup() turtle.goto(60,-50) turtle.pendown() turtle.circle(50)
turtle.color("green") turtle.penup() turtle.goto(180,-50) turtle.pendown() turtle.circle(50)
執行結果:
本章實操作業
- 建立 Python 開發環境,並完成第一個 Python 程式。將整個過程使用圖文描述出來。
- 根據老師程式碼,完成奧運五環的繪圖程式
- 使用海龜繪圖,輸出四個矩形:
第2章(1) 程式設計基礎概念
Python 程式的構成
- Python 程式由模組組成。一個模組對應 python 原始檔,一般字尾名是:.py。
- 模組由語句組成。執行 Python 程式時,按照模組中語句的順序依次執行。
2 語句是 Python 程式的構造單元,用於建立物件、變數賦值、呼叫函式、控制語句等。
Python 檔案的建立和執行
前面使用的互動式環境,每次只能執行一條語句;為了編寫多條語句實現複雜的邏輯,本章開始我們通過建立 Python 檔案,並執行該檔案。
在 IDLE 環境中,我們可以通過 File-->new 建立 Python 檔案,並可以編輯該檔案內容。我們也可以通過 File-->save/save as 儲存檔案。一般儲存成副檔名為 py 的檔案。
需要執行編輯好的檔案,可以用快捷鍵 F5 或者點選 Run-->Run module。
程式碼的組織和縮排
很多程式語言通過字元(例如:花括號{})、關鍵字(例如:begain/end)來劃分程式碼塊。同時,在配合程式碼的縮排增加可讀性。“龜叔”設計 Python 語言時,直接通過縮排來組織程式碼塊。“縮排”成為了 Python 語法強制的規定。縮排時,幾個空格都是允許的,但是數目必須統一。我們通常採用“四個空格”表示一個縮排。
同時,也要避免將“tab 製表符”或者 tab 與空格混合的縮排風格。目前,常用的編輯器一般設定成:tab 製表符就是 4 個空格。
Python 官方推薦的 PEP-8 程式碼風格詳細說明,有興趣的同學可以參考: https://www.python.org/dev/peps/pep-0008/ 使用註釋#
註釋是程式中會被 Python 直譯器忽略的一段文字。程式設計師可以通過註釋記錄任意想寫的內容,通常是關於程式碼的說明。
Python 中的註釋只有單行註釋,使用#開始知道行結束的部分。
>>> # 註釋是個好習慣,方便自己方便他人
>>> a = [10,20,30] #生成一個列表物件,變數 a 引用了這個變數使用\行連線符
一行程式長度是沒有限制的,但是為了可讀性更強,通常將一行比較長的程式分為多行。這是,我們可以使用\行連線符,把它放在行結束的地方。Python 直譯器仍然將它們解釋為同一行。
>>> a = [10,20,30,40,\
50,60,70,\
80,90,100]
>>> a
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
>>> a = 'abcdefghijklmnopqrstuvwxyz'
>>> b = 'abcdefg\ hijklmn\ opqrst\ uvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> b
'abcdefghijklmnopqrstuvwxyz'
物件
Python 中,一切皆物件。每個物件由:標識(identity)、型別(type)、value(值)組成。
- 標識用於唯一標識物件,通常對應於物件在計算機記憶體中的地址。使用內建函式 id(obj) 可返回物件 obj 的標識。
- 型別用於表示物件儲存的“資料”的型別。型別可以限制物件的取值範圍以及可執行的操作。可以使用 type(obj)獲得物件的所屬型別。
- 值表示物件所儲存的資料的資訊。使用 print(obj)可以直接列印出值。物件的本質就是:一個記憶體塊,擁有特定的值,支援特定型別的相關操作。
原始碼:
>>> a = 3
>>> a
3
>>> id(3)
1531372336
>>> type(3)
<class 'int'>
>>> b = "我愛你"
>>> id(a)
1531372336
>>> type(a)
<class 'int'>
>>> print(a)
3
>>> id(b)
46806816
>>> type(b) <class 'str'>
示意圖:
引用
在 Python 中,變數也成為:物件的引用。因為,變數儲存的就是物件的地址。
變數通過地址引用了“物件”。
變數位於:棧記憶體(壓棧出棧等細節,後續再介紹)。
物件位於:堆記憶體。
·Python 是動態型別語言
變數不需要顯式宣告型別。根據變數引用的物件,Python 直譯器自動確定資料型別。
·Python 是強型別語言
每個物件都有資料型別,只支援該型別支援的操作。
識別符號
基本用法
識別符號:用於變數、函式、類、模組等的名稱。識別符號有如下特定的規則:
- 區分大小寫。如:sxt 和 SXT 是不同的
- 第一個字元必須是字母、下劃線。其後的字元是:字母、數字、下劃線
- 不能使用關鍵字。比如:if、or、while 等。
- 以雙下劃線開頭和結尾的名稱通常有特殊含義,儘量避免這種寫法。比如:__init__是類的建構函式。
【操作】使用 Python 幫助系統檢視關鍵字 |
| ||||||
>>> help() help> keywords |
|
| |||||
False | def | if | raise | ||||
None | del | import | return | ||||
True | elif | in | try | ||||
and | else | is | while | ||||
as | except | lambda | with | ||||
assert | finally | nonlocal | yield |
| |||
break | for | not |
|
| |||
class | from | or |
|
| |||
continue | global | pass |
|
| |||
注:無需刻意去背關鍵字,後面都會學習。
Python 識別符號命名規則開發中,我們通常約定俗稱遵守如下規則:
型別 | 規則 | 例子 |
模組和包名 | 全小寫字母,儘量簡單。若多個單詞之間用下劃線 | math, os, sys |
函式名 | 全小寫字母,多個單詞之間用下劃線隔開 | phone, my_name |
類名 | 首字母大寫,採用駝峰原則。多個單詞時,每個單詞第一個字母大寫,其餘部分小寫 | MyPhone、MyClass、 Phone |
常量名 | 全大寫字母,多個單詞使用下劃線隔開 | SPEED、MAX_SPEED |
變數和簡單賦值語句
變數的宣告和賦值
變數的宣告和賦值用於將一個變數繫結到一個物件上,格式如下:變數名 = 表示式
最簡單的表示式就是字面量。比如:a = 123 。 執行過程中,直譯器先執行右邊的表示式,生成一個代表表示式運算結果的物件;然後,將這個物件地址賦值給左邊的變數。
【操作】變數在使用前必須先被初始化(先被賦值)
>>> my_name Traceback (most recent call last): File "<pyshell#17>", line 1, in <module> my_name NameError: name 'my_name' is not defined |
變數 my_name 在被使用前未做賦值,因此報錯:’my_name’is not defined。刪除變數和垃圾回收機制
可以通過 del 語句刪除不在使用的變數。
【操作 55】刪除變數示例
>>> a=123
>>> del a
>>> x
Traceback (most recent call last):
File "<pyshell#20>", line 1, in <module> x
NameError: name 'x' is not defined
如果物件沒有變數引用,就會被垃圾回收器回收,清空記憶體空間。
鏈式賦值
鏈式賦值用於同一個物件賦值給多個變數。 x=y=123 相當於:x=123; y=123 系列解包賦值
系列資料賦值給對應相同個數的變數(個數必須保持一致)
>>> a,b,c=4,5,6 相當於:a=4;b=5;c=6
【操作】使用系列解包賦值實現變數交換
>>> a,b=1,2 >>> a,b=b,a
>>> print(a,b)
2 1
常量
Python 不支援常量,即沒有語法規則限制改變一個常量的值。我們只能約定常量的命名規則,以及在程式的邏輯上不對常量的值作出修改。
>>> MAX_SPEED = 120
>>> print(MAX_SPEED)
120
>>> MAX_SPEED = 140 #實際是可以改的。只能邏輯上不做修改。
>>> print(MAX_SPEED)
140
最基本內建資料型別和運算子
每個物件都有型別,python 中最基本的內建資料型別:
- 整型
整數,2345,10,50
- 浮點型
小數,3.14 或者科學計數法 314e-2
- 布林型
表示真假,僅包含:True、False
- 字串型
由字元組成的序列。 “abc”,”sxt”,“尚學堂”,”百戰程式設計師” 數字和基本運算子
Python 支援整數(如:50,520)和浮點數(如:3.14,10.0, 1.23e2),我們可以對數字做如下運算。
運算子 | 說明 | 示例 | 結果 |
+ | 加法 | 3+2 | 5 |
- | 減法 | 30-5 | 25 |
* | 乘法 | 3*6 | 18 |
/ | 浮點數除法 | 8/2 | 4.0 |
// | 整數除法 | 7//2 | 3 |
% | 模(取餘) | 7%4 | 3 |
** | 冪 | 2**3 | 8 |
【操作】基本運算子的使用
>>> a = 7/2
>>> a
3.5
>>> a = 7//2
>>> a
3
>>> a = 7%2
>>> a
1
>>> 7%4 3
>>> 2**3
8
>>> 3/0
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
3/0
ZeroDivisionError: division by zero
>>> divmod(10,5)
(2, 0)
>>> divmod(10,3)
(3, 1)
除數為 0,會產生異常:
>>> 3/0 Traceback (most recent call last): File "<pyshell#31>", line 1, in <module> 3/0 ZeroDivisionError: division by zero |
使用 divmod()函式同時得到商和餘數:
>>> divmod(13,3)
(4, 1)
divmod()是一個函式,我們以後會詳細介紹。他返回的是一個元組(後續將會學習)。
整數
Python 中,除 10 進位制,還有其他三種進位制:
·0b 或 0B,二進位制 0 1
·0o 或 0O,八進位制 0 1 2 3 4 5 6 7
·0x 或 0X,十六進位制 0 1 2 3 4 5 6 7 8 9 a b c d e f
這三種進位制可以非常方便的進行“位運算”操作。位運算知識後續將會介紹。
【操作】測試不同進位制
>>> 12
12
>>> 0b101
5
>>> 0o19
SyntaxError: invalid syntax
>>> 0o10
8
>>> 0xff
255
>>> 0xf
15
>>> 0x10 16
使用 int()實現型別轉換:
- 浮點數直接捨去小數部分。如:int(9.9)結果是:9
- 布林值 True 轉為 1,False 轉為 0。 如:int(True)結果是 1
- 字串符合整數格式(浮點數格式不行)則直接轉成對應整數,否則報錯。
>>> int("456")
456
>>> int("456abc")
Traceback (most recent call last):
File "<pyshell#41>", line 1, in <module> int("456abc")
ValueError: invalid literal for int() with base 10: '456abc'
>>> int("456.78")
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
int("456.78") ValueError: invalid literal for int() with base 10: '456.78' >>> |
自動轉型:
整數和浮點數混合運算時,表示式結果自動轉型成浮點數。比如:2+8.0 的結果是 10.0
整數可以有多大?
Python2 中,int 是 32 位,可以儲存從-2147483648 到 2147483647 的整數(約±
21 億)。Long 型別是 64 位,可以儲存:-2^63--2^63-1 之間的數值。
Python3 中,int 可以儲存任意大小的整數,long 被取消。我們甚至可以儲存下面的值:
>>> googol = 10**100
>>> googol
1000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000
000
Googol 也是 Google 最初的名字,這也是 Google 最初的含義。
Python3 中可以做超大數的計算,而不會造成“整數溢位”,這也是 Python 特別適合科學運算的特點。
浮點數
浮點數,稱為 float。
浮點數用 ab10 形式的科學計數法表示。比如:3.14,表示成:314E-2 或者 314e-2。
這些數字在記憶體中也是按照科學計數法儲存。
型別轉換和四捨五入
- 類似於 int(),我們也可以使用 float()將其他型別轉化成浮點數。
- 整數和浮點數混合運算時,表示式結果自動轉型成浮點數。比如:2+8.0 的結果是 10.0
- round(value)可以返回四捨五入的值
注:但不會改變原有值,而是產生新的值
增強型賦值運算子
運算子+、-、*,/、//、**和%和賦值符=結合可以構成“增強型賦值運算子”。
a = a + 1 等價於: a +=1
增強型賦值運算子
運算子 | 例子 | 等價 |
+= | a += 2 | a = a + 2 |
-= | a -= 2 | a = a-2 |
*= | a *= 2 | a = a * 2 |
/= | a /= 2 | a = a / 2 |
//= | a //= 2 | a = a//2 |
**= | a **= 2 | a = a**2 |
%= | a %= 2 | a = a % 2 |
注意:“+=”中間不能加空格!時間的表示
計算機中時間的表示是從“1970 年 1 月 1 日 00:00:00”開始,以毫秒(1/1000 秒)進行計算。我們也把 1970 年這個時刻成為“unix 時間點”。
這樣,我們就把時間全部用數字來表示了。
python 中可以通過 time.time() 獲得當前時刻,返回的值是以秒為單位,帶微秒
(1/1000 毫秒)精度的浮點值。例如:1530167364.8566。
>>> import time
>>> b = int(time.time())
>>> b
1530168754
>>> totalMinutes = b/60
>>> totalMinutes
25502812.566666666
>>> totalMinutes = b//60
>>> totalMinutes
25502812
>>> totalHours = totalMinutes//60
>>> totalHours
425046
>>> totalDays = totalHours//24
>>> totalDays
17710
>>> totalYears = totalDays//365
>>> totalYears
48
【操作】定義多點座標_繪出折線_並計算起始點和終點距離原始碼
import turtle import math
#定義多個點的座標
x1,y1 = 100,100 x2,y2 = 100,-100 x3,y3 = -100,-100 x4,y4 = -100,100
#繪製折線
turtle.penup() turtle.goto(x1,y1) turtle.pendown() turtle.goto(x2,y2)
turtle.goto(x3,y3) turtle.goto(x4,y4) #計算起始點和終點的距離 distance = math.sqrt((x1-x4)**2 + (y1-y4)**2) turtle.write(distance) |
執行結果:
布林值
Python2 中沒有布林值,直接用數字 0 表示 False,用數字 1 表示 True。
Python3 中,把 True 和 False 定義成了關鍵字,但他們的本質還是 1 和 0,甚至可以和數字相加。
>>> a = True
>>> b = 3
>>> a+b
4
比較運算子
所有比較運算子返回 1 表示真,返回 0 表示假。這分別與特殊的變數 True 和 False 等價。
以下假設變數 a 為 15,變數 b 為 30:
運算子 | 描述 | 例項 |
== | 等於 - 比較物件的值是否相等 | (a == b) 返回 False。 |
!= | 不等於 - 比較兩個物件的值是否不相等 | (a != b) 返回 true. |
> | 大於 - 返回 x 是否大於 y | (a > b) 返回 False。 |
< | 小於 - 返回 x 是否小於 y。 | (a < b) 返回 true。 |
>= | 大於等於 - 返回 x 是否大於等於 y。 | (a >= b) 返回 False。 |
<= | 小於等於 - 返回 x 是否小於等於 y。 | (a <= b) 返回 true。 |
邏輯運算子
運算子 | 格式 | 說明 |
or 邏輯或 | x or y | x 為 true,則不計算 y,直接返回 true x 為 false,則返回 y |
and 邏輯與 | x and y | x 為 true,則返回 y 的值 x 為 false,則不計算 y,直接返回 false |
not 邏輯非 | not x | x 為 true,返回 false x 為 false,返回 true |
同一運算子同一運算子用於比較兩個物件的儲存單元,實際比較的是物件的地址。
運算子 | 描述 |
is | is 是判斷兩個識別符號是不是引用同一個物件 |
is not | is not 是判斷兩個識別符號是不是引用不同物件 |
is 與 == 區別:
is 用於判斷兩個變數引用物件是否為同一個,既比較物件的地址。
== 用於判斷引用變數引用物件的值是否相等,預設呼叫物件的 __eq__()方法。
整數快取問題
Python 僅僅對比較小的整數物件進行快取(範圍為[-5, 256])快取起來,而並非是所有整數物件。需要注意的是,這僅僅是在命令列中執行,而在 Pycharm 或者儲存為檔案執行,結果是不一樣的,這是因為直譯器做了一部分優化(範圍是[-5,任意正整數])。
·總結
1、 is 比較兩個物件的 id 值是否相等,是否指向同一個記憶體地址;
2、 == 比較的是兩個物件的內容是否相等,值是否相等;
3、 小整數物件[-5,256]在全域性直譯器範圍內被放入快取供重複使用;
4、 is 運算子比 == 效率高,在變數和 None 進行比較時,應該使用 is。
【操作】同一運算子測試
>>> a = 1000
>>> b = 1000
>>> a == b
True
>>> a is b
False
>>> id(a)
46764560
>>> id(b)
46765216
>>> c = 10
>>> d = 10
>>> c is d
True
>>> id(c)
1388831648
>>> id(d)
1388831648
基本運算子
我們在前面講解了“+”、“-”、“*”、“/”、“//”、“%”等運算子,這裡我們繼續講解一些其他運算子,並進行學習和測試。
運算子 | 說明 |
and , or , not | 布林與、布林或、布林非 |
is , is not | 同一性判斷,判斷是否為同一個物件 |
<,<=,>,>=,!=,== | 比較值是否相當,可以連用 |
| ^ & | 按位或,按位異或、按位與 |
<<, >> | 移位 |
~ | 按位翻轉 |
+,-,*,/,//,% | 加,減,乘,浮點除、整數除、取餘 |
** | 冪運算 |
1. 比較運算子可以連用,並且含義和我們日常使用完全一致。
>>> a = 4
>>> 3<a<10 #關係運算子可以連用
True
2. 位操作
>>> a = 0b11001
>>> b = 0b01000
>>> c = a|b
>>> bin(c) #bin()可以將數字轉成二進位制表示
'0b11001'
>>> bin(c&b)
'0b1000'
>>> bin(c^b)
'0b10001'
>>> a = 3
>>> a<<2 #左移 1 位相當於乘以 2.左移 2 位,相當於乘以 4
12
>>> a = 8
>>> a>>1 #右移 1 位相當於除以 2.
3. 加法操作
(1) 數字相加 3+2 ==> 5
(2) 字串拼接 | “3”+“2”==> “32” |
(3) 列表、元組等合併 4. 乘法操作 | [10,20,30]+[5,10,100] ==>[10,20,30,5,10,100] |
(1) 數字相乘 | 3*2 ==> 6 |
(2) 字串複製 | “sxt”*3 ==> ”sxtsxtsxt” |
(3) 列表、元組等複製 | [10,20,30]*3 ==> [10,20,30,10,20,30,10,20,30] |
複合賦值運算子複合賦值可以讓程式更加精煉,提高效率。
運算子 | 描述 | 示例 | 等價於 |
+= | 加法賦值字串拼接 | sum += n a += “sxt” | sum = sum + n a = a + “sxt” |
-= | 減法賦值 | num1 -= n | num = num - n |
*= | 乘法賦值 | a *= b | a = a * b |
/= | 浮點除賦值 | a/=b | a = a / b |
//= | 整數除賦值 | a//=b | a = a//b |
%= | 取餘賦值 | a%=b | a = a % b |
**= | 冪運算賦值 | a**=2 | a = a**2 |
<<= | 左移賦值 | a<<=2 | a = a<<2 |
>>= | 右移賦值 | a>>=2 | a = a>>2 |
&= | 按位與賦值 | a&=b | a = a&b |
|= | 按位或賦值 | a|=b | a=a|b |
^= | 按位異或賦值 | a^=b | a = a^b |
注:與 C 和 JAVA 不一樣,Python 不支援自增(++)和自減(--) 運算子優先順序問題如下優先順序,從高到低。
運算子 | 描述 |
** | 指數 (最高優先順序) |
~ | 按位翻轉 |
* / % // | 乘,除,取模和取整除 |
+ - | 加法減法 |
>> << | 右移,左移運算子 |
& | 位 'AND' |
^ | | 位運算子 |
<= < > >= | 比較運算子 |
<> == != | 等於運算子 |
= %= /= //= -= += *= **= | 賦值運算子 |
is is not | 身份運算子 |
in not in | 成員運算子 |
not or and | 邏輯運算子 |
實際使用中,記住如下簡單的規則即可,複雜的表示式一定要使用小括號組織。
- 乘除優先加減
- 位運算和算術運算>比較運算子>賦值運算子>邏輯運算子
【操作】使用 python 表示數學式: 510x13(y1)(ab) 9(5 12x)
5 x x y
(5+10*x)/5-13*(y-1)*(a+b)/x+9*(5/x+(12+x)/y)
第2章(2) 字串
字串基本特點
很多人初學程式設計時,總是擔心自己數學不行,潛意識裡認為數學好才能程式設計。實際上,大多數程式設計師打交道最多的是“字串”而不是“數字”。因為,程式設計是用來解決現實問題的,因此邏輯思維的重要性遠遠超過數學能力。
字串的本質是:字元序列。Python 的字串是不可變的,我們無法對原字串做任
何修改。但,可以將字串的一部分複製到新建立的字串,達到“看起來修改”的效果。
Python 不支援單字元型別,單字元也是作為一個字串使用的。
字串的編碼
Python3 直接支援 Unicode,可以表示世界上任何書面語言的字元。Python3 的字元預設就是 16 位 Unicode 編碼,ASCII 碼是 Unicode 編碼的子集。
使用內建函式 ord()可以把字元轉換成對應的 Unicode 碼;使用內建函式 chr()可以把十進位制數字轉換成對應的字元。
>>> ord('A')
65
>>> ord('高')
39640
>>> chr(66)
'B'
>>> ord('淇')
28103
引號建立字串
我們可以通過單引號或雙引號建立字串。例如:a=’abc’; b=”sxt”
使用兩種引號的好處是可以建立本身就包含引號的字串,而不用使用轉義字元。例如:
>>> a = "I'm a teacher!"
>>> print(a) I'm a teacher!
>>> b = 'my_name is "TOM"'
>>> print(b) my_name is "TOM"
連續三個單引號或三個雙引號,可以幫助我們建立多行字串。例如:
>>> resume = ''' name="gaoqi"
company="sxt" age=18
lover="Tom"'''
>>> print(resume) name="gaoqi"
company="sxt" age=18 lover="Tom"
空字串和 len()函式
Python 允許空字串的存在,不包含任何字元且長度為 0。例如:
>>> c = ''
>>> len(c)
0
len()用於計算字串含有多少字元。例如:
>>> d = 'abc 尚學堂'
>>> len(d)
6
轉義字元
我們可以使用“\+特殊字元”,實現某些難以用字元表示的效果。比如:換行等。常見的轉義字元有這些:
轉義字元 | 描述 | ||
|
| ||
\\ | 反斜槓符號 | ||
\' | 單引號 | ||
\" | 雙引號 | ||
\b | 退格(Backspace) | ||
\n | 換行 | ||
\t | 橫向製表符 | ||
\r | 回車 |
【操作】測試轉義字元的使用
>>> a = 'I\nlove\nU'
>>> a
'I\nlove\nU'
>>> print(a)
I
love
U
>>> print('aaabb\ cccddd') aaabbcccddd
字串拼接
- 可以使用+將多個字串拼接起來。例如:’aa’+ ’bb’ ==>’aabb’。
(1) 如果+兩邊都是字串,則拼接。
(2) 如果+兩邊都是數字,則加法運算。
(3) 如果+兩邊型別不同,則丟擲異常。
- 可以將多個字面字串直接放到一起實現拼接。例如:’aa’’bb’==>’aabb’
【操作】字串拼接操作
>>> a = 'sxt'+'gaoqi'
>>> a
'sxtgaoqi'
>>> b = 'sxt''gaoqi'
>>> b
'sxtgaoqi'
字串複製
使用*可以實現字串複製。
【操作】字串複製操作
>>> a = 'Sxt'*3
>>> a
'SxtSxtSxt'
不換行列印
我們前面呼叫 print 時,會自動列印一個換行符。有時,我們不想換行,不想自動新增換行
符。我們可以自己通過引數 end = “任意字串”。實現末尾新增任何內容:
建立原始檔 mypy_06.py:
print("sxt",end=' ') print("sxt",end='##') print("sxt")
執行結果:
sxt sxt##sxt
從控制檯讀取字串
我們可以使用 input()從控制檯讀取鍵盤輸入的內容。
>>> myname = input("請輸入名字:")
請輸入名字:高淇
>>> myname '高淇'
str()實現數字轉型字串
str()可以幫助我們將其他資料型別轉換為字串。例如:
str(5.20) ==> ‘5.20’ str(3.14e2)==>’314.0’ str(True) ==> ‘True’
當我們呼叫 print()函式時,直譯器自動呼叫了 str()將非字串的物件轉成了字串。我們在物件導向章節中詳細講解這部分內容。
使用[]提取字元
字串的本質就是字元序列,我們可以通過在字串後面新增[],在[]裡面指定偏移量,
可以提取該位置的單個字元。
正向搜尋:最左側第一個字元,偏移量是 0,第二個偏移量是 1,以此類推。直到 len(str)-1
為止。
反向搜尋:
最右側第一個字元,偏移量是-1,倒數第二個偏移量是-2,以此類推,直到-len(str)
為止。
【操作】使用[]提取字串中的字元
>>> a = 'abcdefghijklmnopqrstuvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> a[0]
'a'
>>> a[3]
'd'
>>> a[26-1]
'z'
>>> a[-1]
'z'
>>> a[-26]
'a'
>>> a[-30]
Traceback (most recent call last):
File "<pyshell#91>", line 1, in <module> a[-30]
IndexError: string index out of range
replace()實現字串替換
字串是“不可改變”的,我們通過[]可以獲取字串指定位置的字元,但是我們不能改變字串。我們嘗試改變字串中某個字元,發現報錯了:
>>> a = 'abcdefghijklmnopqrstuvwxyz' >>> a 'abcdefghijklmnopqrstuvwxyz' >>> a[3]='高' Traceback (most recent call last): File "<pyshell#94>", line 1, in <module> a[3]='高' TypeError: 'str' object does not support item assignment |
字串不可改變。但是,我們確實有時候需要替換某些字元。這時,只能通過建立新的字串來實現。
>>> a = 'abcdefghijklmnopqrstuvwxyz'
>>> a
'abcdefghijklmnopqrstuvwxyz'
>>> a = a.replace('c','高')
'ab 高 defghijklmnopqrstuvwxyz'
整個過程中,實際上我們是建立了新的字串物件,並指向了變數 a,而不是修改了以前的字串。 記憶體圖如下:
字串切片 slice 操作
切片 slice 操作可以讓我們快速的提取子字串。標準格式為:
[起始偏移量 start:終止偏移量 end:步長 step]
典型操作(三個量為正數的情況)如下:
操作和說明 | 示例 | 結果 |
[:] 提取整個字串 | “abcdef”[:] | “abcdef” |
[start:]從 start 索引開始到結尾 | “abcdef”[2:] | “cdef” |
[:end]從頭開始知道 end-1 | “abcdef”[:2] | “ab” |
[start:end]從 start 到 end-1 | “abcdef”[2:4] | “cd” |
[start:end:step]從 start 提取到 end-1,步長是 step | “abcdef”[1:5:2] | “bd” |
其他操作(三個量為負數)的情況:
示例 | 說明 | 結果 |
"abcdefghijklmnopqrstuv wxyz"[-3:] | 倒數三個 | “xyz” |
"abcdefghijklmnopqrstuv wxyz"[-8:-3] | 倒數第八個到倒數第三個(包頭不包尾) | 'stuvw' |
"abcdefghijklmnopqrstuv wxyz"[::-1] | 步長為負,從右到左反向提取 | 'zyxwvutsrqpon mlkjihgfedcba' |
切片操作時,起始偏移量和終止偏移量不在[0,字串長度-1]這個範圍,也不會報錯。起始
偏移量小於 0 則會當做 0,終止偏移量大於“長度-1”會被當成-1。例如:
>>> "abcdefg"[3:50]
'defg'
我們發現正常輸出了結果,沒有報錯。
【操作】
1. 將”to be or not to be”字串倒序輸出 2. 將”sxtsxtsxtsxtsxt”字串中所有的 s 輸出
split()分割和 join()合併
split()可以基於指定分隔符將字串分隔成多個子字串(儲存到列表中)。如果不指定分隔符,則預設使用空白字元(換行符/空格/製表符)。示例程式碼如下:
>>> a = "to be or not to be"
>>> a.split()
['to', 'be', 'or', 'not', 'to', 'be']
>>> a.split('be')
['to ', ' or not to ', '']
join()的作用和 split()作用剛好相反,用於將一系列子字串連線起來。示例程式碼如下:
>>> a = ['sxt','sxt100','sxt200']
>>> '*'.join(a)
'sxt*sxt100*sxt200'
拼接字串要點:
使用字串拼接符+,會生成新的字串物件,因此不推薦使用+來拼接字串。推薦
使用 join 函式,因為 join 函式在拼接字串之前會計算所有字串的長度,然後逐一拷貝,僅新建一次物件。
【操作】測試+拼接符和 join(),不同的效率 (mypy_07.py)
import time
time01 = time.time() #起始時刻
a = "" for i in range(1000000):
a += "sxt" time02 = time.time() #終止時刻 print("運算時間:"+str(time02-time01)) time03 = time.time() #起始時刻 li = [] for i in range(1000000): li.append("sxt") a = "".join(li) time04 = time.time() #終止時刻 print("運算時間:"+str(time04-time03)) |
字串駐留機制和字串比較
字串駐留:僅儲存一份相同且不可變字串的方法,不同的值被存放在字串駐留池中。 Python 支援字串駐留機制,對於符合識別符號規則的字串(僅包含下劃線(_)、字母和數字)會啟用字串駐留機制駐留機制。
>>> a = "abd_33"
>>> b = "abd_33"
>>> a is b
True
>>> c = "dd#"
>>> d = "dd#"
>>> c is d
False
>>> str1 = "aa"
>>> str2 = "bb"
>>> str1+str2 is "aabb"
False
>>> str1+str2 == "aabb"
True
字串比較和同一性
我們可以直接使用==,!=對字串進行比較,是否含有相同的字元。
我們使用 is / not is,判斷兩個物件是否同一個物件。比較的是物件的地址,即 id(obj1)是否和 id(obj2)相等。
成員操作符
in /not in 關鍵字,判斷某個字元(子字串)是否存在於字串中。
字串常用方法彙總
字串有很多常用的方法,我們需要熟悉。我們通過表格將這些方法彙總起來,方便大家查閱。希望大家針對每個方法都做一次測試。
常用查詢方法
我們以一段文字作為測試: a='''我是高淇,今年 18 歲了,我在北京尚學堂科技上班。我的兒子叫高洛希,他 6 歲了。我是一個程式設計教育的普及者,希望影響 6000 萬學習程式設計的中國人。我兒子現在也開始學習程式設計,希望他 18 歲的時候可以超過我'''
方法和使用示例 | 說明 | 結果 |
len(a) | 字串長度 | 96 |
a.startswith('我是高淇') | 以指定字串開頭 | True |
a.endswith('過我') | 以指定字串結尾 | True |
a.find('高') | 第一次出現指定字串的位置 | 2 |
a.rfind('高') | 最後一次出現指定字串的位置 | 29 |
a.count("程式設計") | 指定字串出現了幾次 | 3 |
a.isalnum() | 所有字元全是字母或數字 | False |
去除首尾資訊
我們可以通過 strip()去除字串首尾指定資訊。通過 lstrip()去除字串左邊指定資訊, rstrip()去除字串右邊指定資訊。【操作】去除字串首尾資訊
>>> "*s*x*t*".strip("*")
's*x*t'
>>> "*s*x*t*".lstrip("*")
's*x*t*'
>>> "*s*x*t*".rstrip("*")
'*s*x*t'
>>> " sxt ".strip()
'sxt'
大小寫轉換
程式設計中關於字串大小寫轉換的情況,經常遇到。我們將相關方法彙總到這裡。為了方便學習,先設定一個測試變數:
a = "gaoqi love programming, love SXT"
示例 | 說明 | 結果 | ||
a.capitalize() | 產生新的字串,首字母大寫 | 'Gaoqi love programming, love sxt' | ||
a.title() | 產生新的字串,每個單詞都首字母大寫 | 'Gaoqi Love Programming, Love Sxt' | ||
a.upper() | 產生新的字串,所有字元全轉成大寫 | 'GAOQI LOVE PROGRAMMING, LOVE SXT' | ||
a.lower() | 產生新的字串,所有字元全轉成小寫 | 'gaoqi love programming, love sxt' | ||
a.swapcase() | 產生新的,所有字母大小寫轉換 | 'GAOQI sxt' | LOVE | PROGRAMMING, LOVE |
格式排版
center()、ljust()、rjust()這三個函式用於對字串實現排版。示例如下:
>>> a="SXT"
>>> a.center(10,"*")
'***SXT****'
>>> a.center(10)
' SXT '
>>> a.ljust(10,"*") 'SXT*******' 其他方法
1. isalnum() 是否為字母或數字 2. isalpha() 檢測字串是否只由字母組成(含漢字)。
- isdigit() 檢測字串是否只由數字組成。
- isspace() 檢測是否為空白符
- isupper() 是否為大寫字母
- islower() 是否為小寫字母
>>> "sxt100".isalnum()
True
>>> "sxt 尚學堂".isalpha()
True
>>> "234.3".isdigit()
False
>>> "23423".isdigit()
True
>>> "aB".isupper()
False
>>> "A".isupper()
True
>>> "\t\n".isspace()
True
字串的格式化
format()基本用法
Python2.6 開始,新增了一種格式化字串的函式 str.format(),它增強了字串格式化的功能。
基本語法是通過 {} 和 : 來代替以前的 % 。
format 函式可以接受不限個引數,位置可以不按順序。
我們通過示例進行格式化的學習。
>>> a = "名字是:{0},年齡是:{1}"
>>> a.format("高淇",18)
'名字是:高淇,年齡是:18'
>>> a.format("高希希",6)
'名字是:高希希,年齡是:6'
>>> b = "名字是:{0},年齡是{1}。{0}是個好小夥"
>>> b.format("高淇",18)
'名字是:高淇,年齡是 18。高淇是個好小夥'
>>> c = "名字是{name},年齡是{age}"
>>> c.format(age=19,name='高淇')
'名字是高淇,年齡是 19'
我們可以通過{索引}/{引數名},直接對映引數值,實現對字串的格式化,非常方便。
填充與對齊
填充常跟對齊一起使用
^、<、>分別是居中、左對齊、右對齊,後面頻寬度
:號後面帶填充的字元,只能是一個字元,不指定的話預設是用空格填充
>>> "{:*>8}".format("245")
'*****245'
>>> "我是{0},我喜歡數字{1:*^8}".format("高淇","666")
'我是高淇,我喜歡數字**666***'
數字格式化
浮點數通過 f,整數通過 d 進行需要的格式化。案例如下:
>>> a = "我是{0},我的存款有{1:.2f}"
>>> a.format("高淇",3888.234342)
'我是高淇,我的存款有 3888.23'
其他格式,供大家參考:
數字 | 格式 | 輸出 | 描述 |
3.1415926 | {:.2f} | 3.14 | 保留小數點後兩位 |
3.1415926 | {:+.2f} | 3.14 | 帶符號保留小數點後兩位 |
2.71828 | {:.0f} | 3 | 不帶小數 |
5 | {:0>2d} | 05 | 數字補零 (填充左邊, 寬度為 2) |
5 | {:x<4d} | 5xxx | 數字補 x (填充右邊, 寬度為 4) |
10 | {:x<4d} | 10xx | 數字補 x (填充右邊, 寬度為 4) |
1000000 | {:,} | 1,000,000 | 以逗號分隔的數字格式 |
0.25 | {:.2%} | 25.00% | 百分比格式 |
1000000000 | {:.2e} | 1.00E+09 | 指數記法 |
13 | {:10d} | 13 | 右對齊 (預設, 寬度為 10) |
13 | {:<10d} | 13 | 左對齊 (寬度為 10) |
13 | {:^10d} | 13 | 中間對齊 (寬度為 10) |
可變字串
在 Python 中,字串屬於不可變物件,不支援原地修改,如果需要修改其中的值,智慧建立新的字串物件。但是,經常我們確實需要原地修改字串,可以使用 io.StringIO 物件或 array 模組。
>>> import io
>>> s = "hello, sxt"
>>> sio = io.StringIO(s)
>>> sio
<_io.StringIO object at 0x02F462B0>
>>> sio.getvalue()
'hello, sxt'
>>> sio.seek(7)
7
>>> sio.write("g")
1
>>> sio.getvalue() 'hello, gxt'
本章實操作業
- 使用 python 表示數學式: 510x13(y1)(ab) 9(5 12x)
5 x x y
- 從控制檯輸入使用者的月薪,進行運算計算出年薪。列印輸出使用者的年薪
- 使用字串複製,用計算機列印出“愛你一百遍”,列印 100 次
- 將”to be or not to be”字串倒序輸出
- 將”sxtsxtsxtsxtsxt”字串中所有的 s 輸出
- 判斷如下輸出結果,並文字解釋原因:
>>> a = "abd_33"
>>> b = "abd_33"
>>> c = "dd#"
>>> d = "dd#"
>>> a is b #輸出 true or false? >>> c is d #輸出 true or false?
- 寫出如下程式碼列印的結果:
>>> c = "名字是{name},年齡是{age}"
>>> c.format(age=19,name='高淇')
第3章 序列
序列是一種資料儲存方式,用來儲存一系列的資料。在記憶體中,序列就是一塊用來存放多個值的連續的記憶體空間。比如一個整數序列[10,20,30,40],可以這樣示意表示:
由於 Python3 中一切皆物件,在記憶體中實際是按照如下方式儲存的: a = [10,20,30,40]
從圖示中,我們可以看出序列中儲存的是整數物件的地址,而不是整數物件的值。python 中常用的序列結構有:
字串、列表、元組、字典、集合
我們上一章學習的字串就是一種序列。關於字串裡面很多操作,在這一章中仍然會用到,大家一定會感覺非常熟悉。
本章內容,我們必須非常熟悉。無論是在學習還是工作中,序列都是每天都會用到的技術,可以非常方便的幫助我們進行資料儲存的操作。
列表簡介
列表:用於儲存任意數目、任意型別的資料集合。
列表是內建可變序列,是包含多個元素的有序連續的記憶體空間。列表定義的標準語法格式: a = [10,20,30,40] 其中,10,20,30,40 這些稱為:列表 a 的元素。
列表中的元素可以各不相同,可以是任意型別。比如:
a = [10,20,'abc',True] 列表物件的常用方法彙總如下,方便大家學習和查閱。
方法 | 要點 | 描述 |
list.append(x) | 增加元素 | 將元素 x 增加到列表 list 尾部 |
list.extend(aList) | 增加元素 | 將列表 alist 所有元素加到列表 list 尾部 |
list.insert(index,x) | 增加元素 | 在列表 list 指定位置 index 處插入元素 x |
list.remove(x) | 刪除元素 | 在列表 list 中刪除首次出現的指定元素 x |
list.pop([index]) | 刪除元素 | 刪除並返回列表 list 指定為止 index 處的元素,預設是最後一個元素 |
list.clear() | 刪除所有元素 | 刪除列表所有元素,並不是刪除列表物件 |
list.index(x) | 訪問元素 | 返回第一個 x 的索引位置,若不存在 x 元素丟擲異常 |
list.count(x) | 計數 | 返回指定元素 x 在列表 list 中出現的次數 |
len(list) | 列表長度 | 返回列表中包含元素的個數 |
list.reverse() | 翻轉列表 | 所有元素原地翻轉 |
list.sort() | 排序 | 所有元素原地排序 |
list.copy() | 淺拷貝 | 返回列表物件的淺拷貝 |
Python 的列表大小可變,根據需要隨時增加或縮小。
字串和列表都是序列型別,一個字串是一個字元序列,一個列表是任何元素的序列。我們前面學習的很多字串的方法,在列表中也有類似的用法,幾乎一模一樣。
列表的建立基本語法[]建立
>>> a = [10,20,'gaoqi','sxt']
>>> a = [] #建立一個空的列表物件
list()建立
使用 list()可以將任何可迭代的資料轉化成列表。
>>> a = list() #建立一個空的列表物件
>>> a = list(range(10))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a = list("gaoqi,sxt")
>>> a
['g', 'a', 'o', 'q', 'i', ',', 's', 'x', 't']
range()建立整數列表
range()可以幫助我們非常方便的建立整數列表,這在開發中及其有用。語法格式為:
range([start,] end [,step])
start 引數:可選,表示起始數字。預設是 0 end 引數:必選,表示結尾數字。 step 引數:可選,表示步長,預設為 1
python3 中 range()返回的是一個 range 物件,而不是列表。我們需要通過 list()方法將其轉換成列表物件。典型示例如下:
>>> list(range(3,15,2))
[3, 5, 7, 9, 11, 13]
>>> list(range(15,3,-1))
[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4]
>>> list(range(3,-10,-1))
[3, 2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
推導式生成列表(簡介一下,重點在 for 迴圈後講)
使用列表推導式可以非常方便的建立列表,在開發中經常使用。但是,由於涉及到 for 迴圈和 if 語句。在此,僅做基本介紹。在我們控制語句後面,會詳細講解更多列表推導式的細節。
>>> a = [x*2 for x in range(5)] #迴圈建立多個元素
>>> a
[0, 2, 4, 6, 8]
>>> a = [x*2 for x in range(100) if x%9==0] #通過 if 過濾元素
>>> a
[0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198] 列表元素的增加和刪除
當列表增加和刪除元素時,列表會自動進行記憶體管理,大大減少了程式設計師的負擔。但這個特點涉及列表元素的大量移動,效率較低。除非必要,我們一般只在列表的尾部新增元素或刪除元素,這會大大提高列表的操作效率。
append()方法
原地修改列表物件,是真正的列表尾部新增新的元素,速度最快,推薦使用。
>>> a = [20,40]
>>> a.append(80)
>>> a
[20, 40, 80]
+運算子操作
並不是真正的尾部新增元素,而是建立新的列表物件;將原列表的元素和新列表的元素依次複製到新的列表物件中。這樣,會涉及大量的複製操作,對於操作大量元素不建議使用。 >>> a = [20,40]
>>> id(a)
46016072
>>> a = a+[50]
>>> id(a) 46015432
通過如上測試,我們發現變數 a 的地址發生了變化。也就是建立了新的列表物件。
extend()方法
將目標列表的所有元素新增到本列表的尾部,屬於原地操作,不建立新的列表物件。
>>> a = [20,40]
>>> id(a)
46016072
>>> a.extend([50,60])
>>> id(a) 46016072
insert()插入元素
使用 insert()方法可以將指定的元素插入到列表物件的任意制定位置。這樣會讓插入位置後面所有的元素進行移動,會影響處理速度。涉及大量元素時,儘量避免使用。類似發生這種移動的函式還有:remove()、pop()、del(),它們在刪除非尾部元素時也會發生操作位置後面元素的移動。
>>> a = [10,20,30]
>>> a.insert(2,100)
>>> a
[10, 20, 100, 30] 乘法擴充套件
使用乘法擴充套件列表,生成一個新列表,新列表元素時原列表元素的多次重複。
>>> a = ['sxt',100]
>>> b = a*3
>>> a
['sxt', 100]
>>> b
['sxt', 100, 'sxt', 100, 'sxt', 100]
適用於乘法操作的,還有:字串、元組。例如:
>>> c = 'sxt'
>>> d = c*3
>>> c
'sxt'
>>> d
'sxtsxtsxt'
列表元素的刪除
del 刪除
刪除列表指定位置的元素。
>>> a = [100,200,888,300,400]
>>> del a[1]
>>> a
[100,200,300,400]
pop()方法
pop()刪除並返回指定位置元素,如果未指定位置則預設操作列表最後一個元素。
>>> a = [10,20,30,40,50]
>>> a.pop()
50
>>> a
[10, 20, 30, 40]
>>> a.pop(1)
20
>>> a
[10, 30, 40]
remove()方法
刪除首次出現的指定元素,若不存在該元素丟擲異常。
>>> a = [10,20,30,40,50,20,30,20,30]
>>> a.remove(20)
>>> a
[10, 30, 40, 50, 20, 30, 20, 30]
>>> a.remove(100)
Traceback (most recent call last):
File "<pyshell#208>", line 1, in <module>
a.remove(100)
ValueError: list.remove(x): x not in list
列表元素訪問和計數通過索引直接訪問元素
我們可以通過索引直接訪問元素。索引的區間在[0, 列表長度-1]這個範圍。超過這個範圍則會丟擲異常。
>>> a = [10,20,30,40,50,20,30,20,30]
>>> a[2]
30
>>> a[10]
Traceback (most recent call last):
File "<pyshell#211>", line 1, in <module> a[10]
IndexError: list index out of range
index()獲得指定元素在列表中首次出現的索引
index()可以獲取指定元素首次出現的索引位置。語法是:index(value,[start,[end]])。其中, start 和 end 指定了搜尋的範圍。
>>> a = [10,20,30,40,50,20,30,20,30]
>>> a.index(20)
1
>>> a.index(20,3)
5
>>> a.index(20,3) #從索引位置 3 開始往後搜尋的第一個 20
5
>>> a.index(30,5,7) #從索引位置 5 到 7 這個區間,第一次出現 30 元素的位置
6
count()獲得指定元素在列表中出現的次數
count()可以返回指定元素在列表中出現的次數。
>>> a = [10,20,30,40,50,20,30,20,30]
>>> a.count(20)
3
len()返回列表長度
len()返回列表長度,即列表中包含元素的個數。
>>> a = [10,20,30]
>>> len(a)
3
成員資格判斷
判斷列表中是否存在指定的元素,我們可以使用 count()方法,返回 0 則表示不存在,返回大於 0 則表示存在。但是,一般我們會使用更加簡潔的 in 關鍵字來判斷,直接返回 True 或 False。
>>> a = [10,20,30,40,50,20,30,20,30]
>>> 20 in a
True
>>> 100 not in a
True
>>> 30 not in a
False
切片操作
我們在前面學習字串時,學習過字串的切片操作,對於列表的切片操作和字串類似。切片是 Python 序列及其重要的操作,適用於列表、元組、字串等等。切片的格式如下:
切片 slice 操作可以讓我們快速提取子列表或修改。標準格式為:
[起始偏移量 start:終止偏移量 end[:步長 step]]
注:當步長省略時順便可以省略第二個冒號
典型操作(三個量為正數的情況)如下:
操作和說明 | 示例 | 結果 |
[:] 提取整個列表 | [10,20,30][:] | [10,20,30] |
[start:]從 start 索引開始到 結尾 | [10,20,30][1:] | [20,30] |
[:end]從頭開始知道 end-1 | [10,20,30][:2] | [10,20] |
[start:end]從 start 到 end-1 | [10,20,30,40][1:3] | [20,30] |
[start:end:step] 從 start 提 取到 end-1,步長是 step | [10,20,30,40,50,60,70][1:6: 2] | [20, 40, 60] |
其他操作(三個量為負數)的情況:
示例 | 說明 | 結果 |
[10,20,30,40,50,60,70][-3:] | 倒數三個 | [50,60,70] |
10,20,30,40,50,60,70][-5:-3] | 倒數第五個到倒數第三個(包頭不包尾) | [30,40] |
[10,20,30,40,50,60,70][::-1] | 步長為負,從右到左反向提取 | [70, 60, 50, 40, 30, 20, 10] |
切片操作時,起始偏移量和終止偏移量不在[0,字串長度-1]這個範圍,也不會報錯。起始
偏移量小於 0 則會當做 0,終止偏移量大於“長度-1”會被當成”長度-1”。例如:
>>> [10,20,30,40][1:30]
[20, 30, 40]
我們發現正常輸出了結果,沒有報錯。
列表的遍歷
for obj in listObj:
print(obj)
複製列表所有的元素到新列表物件
如下程式碼實現列表元素的複製了嗎? list1 = [30,40,50] list2 = list1
只是將 list2 也指向了列表物件,也就是說 list2 和 list2 持有地址值是相同的,列表物件本身的元素並沒有複製。
我們可以通過如下簡單方式,實現列表元素內容的複製: list1 = [30,40,50] list2 = [] + list1
注:我們後面也會學習 copy 模組,使用淺複製或深複製實現我們的複製操作。
列表排序修改原列表,不建新列表的排序
>>> a = [20,10,30,40]
>>> id(a)
46017416
>>> a.sort() #預設是升序排列
>>> a
[10, 20, 30, 40]
>>> a = [10,20,30,40]
>>> a.sort(reverse=True) #降序排列
>>> a
[40, 30, 20, 10]
>>> import random
>>> random.shuffle(a) #打亂順序
>>> a
[20, 40, 30, 10]
建新列表的排序
我們也可以通過內建函式 sorted()進行排序,這個方法返回新列表,不對原列表做修改。
>>> a = [20,10,30,40]
>>> id(a)
46016008
>>> a = sorted(a) #預設升序
>>> a
[10, 20, 30, 40]
>>> id(a)
45907848
>>> a = [20,10,30,40]
>>> id(a)
45840584
>>> b = sorted(a)
>>> b
[10, 20, 30, 40]
>>> id(a)
45840584 >>> id(b) 46016072
>>> c = sorted(a,reverse=True) #降序
>>> c
[40, 30, 20, 10]
通過上面操作,我們可以看出,生成的列表物件 b 和 c 都是完全新的列表物件。 reversed()返回迭代器
內建函式 reversed()也支援進行逆序排列,與列表物件 reverse()方法不同的是,內建函式 reversed()不對原列表做任何修改,只是返回一個逆序排列的迭代器物件。
>>> a = [20,10,30,40]
>>> c = reversed(a)
>>> c
<list_reverseiterator object at 0x0000000002BCCEB8>
>>> list(c)
[40, 30, 10, 20]
>>> list(c)
[]
我們列印輸出 c 發現提示是:list_reverseiterator。也就是一個迭代物件。同時,我們使用 list(c)進行輸出,發現只能使用一次。第一次輸出了元素,第二次為空。那是因為迭代物件在第一次時已經遍歷結束了,第二次不能再使用。注:關於迭代物件的使用,後續章節會進行詳細講解。
列表相關的其他內建函式彙總
max 和 min
用於返回列表中最大和最小值。
[40, 30, 20, 10]
>>> a = [3,10,20,15,9]
>>> max(a)
20
>>> min(a)
3
sum
對數值型列表的所有元素進行求和操作,對非數值型列表運算則會報錯。
>>> a = [3,10,20,15,9]
>>> sum(a)
57
多維列表
二維列表
一維列表可以幫助我們儲存一維、線性的資料。
二維列表可以幫助我們儲存二維、表格的資料。例如下表的資料:
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
原始碼:
a = [
["高小一",18,30000,"北京"], ["高小二",19,20000,"上海"], ["高小一",20,10000,"深圳"], ] |
記憶體結構圖:
>>> print(a[1][0],a[1][1],a[1][2])
高小二 19 20000
巢狀迴圈列印二維列表所有的資料(mypy_08.py)(由於沒有學迴圈,照著敲一遍即可):
a = [ ["高小一",18,30000,"北京"], ["高小二",19,20000,"上海"], ["高小一",20,10000,"深圳"], ] for m in range(3): for n in range(4): print(a[m][n],end="\t") print() #列印完一行,換行 |
執行結果:高小一 18 30000 北京高小二 19 20000 上海高小一 20 10000 深圳
元組 tuple
列表屬於可變序列,可以任意修改列表中的元素。元組屬於不可變序列,不能修改元組中的元素。因此,元組沒有增加元素、修改元素、刪除元素相關的方法。
因此,我們只需要學習元組的建立和刪除,元組中元素的訪問和計數即可。元組支援如下操作:
- 索引訪問
- 切片操作
- 連線操作
- 成員關係操作
- 比較運算操作
- 計數:元組長度 len()、最大值 max()、最小值 min()、求和 sum()等。
元組的建立
- 通過()建立元組。小括號可以省略。
a = (10,20,30) 或者 a = 10,20,30
如果元組只有一個元素,則必須後面加逗號。這是因為直譯器會把(1)解釋為整數 1,(1,) 解釋為元組。
>>> a = (1)
>>> type(a)
<class 'int'>
>>> a = (1,) #或者 a = 1,
>>> type(a)
<class 'tuple'>
- 通過 tuple()建立元組 tuple(可迭代的物件) 例如:
b = tuple() #建立一個空元組物件 b = tuple("abc") b = tuple(range(3)) b = tuple([2,3,4])
總結: tuple()可以接收列表、字串、其他序列型別、迭代器等生成元組。
list()可以接收元組、字串、其他序列型別、迭代器等生成列表。
元組的元素訪問和計數
- 元組的元素不能修改
>>> a = (20,10,30,9,8) >>> a[3]=33 Traceback (most recent call last): File "<pyshell#313>", line 1, in <module> a[3]=33 TypeError: 'tuple' object does not support item assignment |
- 元組的元素訪問和列表一樣,只不過返回的仍然是元組物件。
>>> a = (20,10,30,9,8)
>>> a[1]
10
>>> a[1:3]
(10, 30)
>>> a[:4]
(20, 10, 30, 9)
- 列表關於排序的方法 list.sorted()是修改原列表物件,元組沒有該方法。如果要對元組排序,只能使用內建函式 sorted(tupleObj),並生成新的列表物件。
>>> a = (20,10,30,9,8)
>>> sorted(a)
[8, 9, 10, 20, 30]
zip
zip(列表 1,列表 2,...)將多個列表對應位置的元素組合成為元組,並返回這個 zip 物件。
>>> a = [10,20,30]
>>> b = [40,50,60]
>>> c = [70,80,90]
>>> d = zip(a,b,c)
>>> list(d)
[(10, 40, 70), (20, 50, 80), (30, 60, 90)]
生成器推導式建立元組
從形式上看,生成器推導式與列表推導式類似,只是生成器推導式使用小括號。列表推
導式直接生成列表物件,生成器推導式生成的不是列表也不是元組,而是一個生成器物件。
我們可以通過生成器物件,轉化成列表或者元組。也可以使用生成器物件的__next__() 方法進行遍歷,或者直接作為迭代器物件來使用。不管什麼方式使用,元素訪問結束後,如果需要重新訪問其中的元素,必須重新建立該生成器物件。
【操作】生成器的使用測試
>>> s = (x*2 for x in range(5))
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
(0, 2, 4, 6, 8)
>>> list(s) #只能訪問一次元素。第二次就為空了。需要再生成一次
[]
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
()
>>> s = (x*2 for x in range(5))
>>> s.__next__()
0
>>> s.__next__()
2
>>> s.__next__() 4
元組總結
- 元組的核心特點是:不可變序列。
- 元組的訪問和處理速度比列表快。
- 與整數和字串一樣,元組可以作為字典的鍵,列表則永遠不能作為字典的鍵使用。
字典介紹
字典是“鍵值對”的無序可變序列,字典中的每個元素都是一個“鍵值對”,包含:“鍵
物件”和“值物件”。可以通過“鍵物件”實現快速獲取、刪除、更新對應的“值物件”。
列表中我們通過“下標數字”找到對應的物件。字典中通過“鍵物件”找到對應的“值
物件”。“鍵”是任意的不可變資料,比如:整數、浮點數、字串、元組。但是:列表、字典、集合這些可變物件,不能作為“鍵”。並且“鍵”不可重複。 “值”可以是任意的資料,並且可重複。
一個典型的字典的定義方式: a = {'name':'gaoqi','age':18,'job':'programmer'}
字典的建立
- 我們可以通過{}、dict()來建立字典物件。
>>> a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> b = dict(name='gaoqi',age=18,job='programmer')
>>> a = dict([("name","gaoqi"),("age",18)])
>>> c = {} #空的字典物件
>>> d = dict() #空的字典物件
- 通過 zip()建立字典物件
>>> k = ['name','age','job']
>>> v = ['gaoqi',18,'techer']
>>> d = dict(zip(k,v))
>>> d
{'name': 'gaoqi', 'age': 18, 'job': 'techer'}
- 通過 fromkeys 建立值為空的字典
>>> a = dict.fromkeys(['name','age','job'])
>>> a
{'name': None, 'age': None, 'job': None}
字典元素的訪問
為了測試各種訪問方法,我們這裡設定一個字典物件:
a = {'name':'gaoqi','age':18,'job':'programmer'}
- 通過 [鍵] 獲得“值”。若鍵不存在,則丟擲異常。
>>> a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> a['name']
'gaoqi'
>>> a['age']
18
>>> a['sex']
Traceback (most recent call last):
File "<pyshell#374>", line 1, in <module> a['sex']
KeyError: 'sex'
- 通過 get()方法獲得“值”。推薦使用。優點是:指定鍵不存在,返回 None;也可以設定指定鍵不存在時預設返回的物件。推薦使用 get()獲取“值物件”。
>>> a.get('name')
'gaoqi'
>>> a.get('sex')
>>> a.get('sex','一個男人')
'一個男人'
- 列出所有的鍵值對
>>> a.items()
dict_items([('name', 'gaoqi'), ('age', 18), ('job', 'programmer')])
- 列出所有的鍵,列出所有的值
>>> a.keys()
dict_keys(['name', 'age', 'job'])
>>> a.values()
dict_values(['gaoqi', 18, 'programmer'])
- len() 鍵值對的個數
- 檢測一個“鍵”是否在字典中
>>> a = {"name":"gaoqi","age":18}
>>> "name" in a
True
字典元素新增、修改、刪除
- 給字典新增“鍵值對”。如果“鍵”已經存在,則覆蓋舊的鍵值對;如果“鍵”不存在,則新增“鍵值對”。
>>>a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> a['address']='西三旗 1 號院'
>>> a['age']=16
>>> a
{'name': 'gaoqi', 'age': 16, 'job': 'programmer', 'address': '西三旗 1 號院'}
- 使用 update()將新字典中所有鍵值對全部新增到舊字典物件上。如果 key 有重複,則直接覆蓋。
>>> a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> b = {'name':'gaoxixi','money':1000,'sex':'男的'}
>>> a.update(b)
>>> a
{'name': 'gaoxixi', 'age': 18, 'job': 'programmer', 'money': 1000, 'sex': '男的'}
- 字典中元素的刪除,可以使用 del()方法;或者 clear()刪除所有鍵值對;pop()刪除指定
鍵值對,並返回對應的“值物件”;
>>> a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> del(a['name'])
>>> a
{'age': 18, 'job': 'programmer'}
>>> b = a.pop('age')
>>> b
18
- popitem() :隨機刪除和返回該鍵值對。字典是“無序可變序列”,因此沒有第一個元素、最後一個元素的概念;popitem 彈出隨機的項,因為字典並沒有"最後的元素"或者其他有關順序的概念。若想一個接一個地移除並處理項,這個方法就非常有效(因為不用首先獲取鍵的列表)。
>>> a = {'name':'gaoqi','age':18,'job':'programmer'}
>>> a.popitem()
('job', 'programmer')
>>> a
{'name': 'gaoqi', 'age': 18}
>>> a.popitem()
('age', 18)
>>> a
{'name': 'gaoqi'}
序列解包
序列解包可以用於元組、列表、字典。序列解包可以讓我們方便的對多個變數賦值。
>>> x,y,z=(20,30,10)
>>> x
20
>>> y
30
>>> z
10
>>> (a,b,c)=(9,8,10)
>>> a
9
>>> [a,b,c]=[10,20,30]
>>> a
10
>>> b
20
序列解包用於字典時,預設是對“鍵”進行操作; 如果需要對鍵值對操作,則需要使用 items();如果需要對“值”進行操作,則需要使用 values();
>>> s = {'name':'gaoqi','age':18,'job':'teacher'}
>>> name,age,job=s #預設對鍵進行操作
>>> name
'name'
>>> name,age,job=s.items() #對鍵值對進行操作
>>> name
('name', 'gaoqi')
>>> name,age,job=s.values() #對值進行操作
>>> name
'gaoqi'
表格資料使用字典和列表儲存,並實現訪問
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
原始碼(mypy_09.py):
r1 = {"name":"高小一","age":18,"salary":30000,"city":"北京"} r2 = {"name":"高小二","age":19,"salary":20000,"city":"上海"} r3 = {"name":"高小五","age":20,"salary":10000,"city":"深圳"} tb = [r1,r2,r3] #獲得第二行的人的薪資 print(tb[1].get("salary")) #列印表中所有的的薪資 for i in range(len(tb)): # i -->0,1,2 print(tb[i].get("salary")) #列印表的所有資料 for i in range(len(tb)): print(tb[i].get("name"),tb[i].get("age"),tb[i].get("salary"),tb[i].get("city")) |
字典核心底層原理(重要)
字典物件的核心是雜湊表。雜湊表是一個稀疏陣列(總是有空白元素的陣列),陣列的每個單元叫做 bucket。每個 bucket 有兩部分:一個是鍵物件的引用,一個是值物件的引用。
由於,所有 bucket 結構和大小一致,我們可以通過偏移量來讀取指定 bucket。
將一個鍵值對放進字典的底層過程
>>> a = {}
>>>
a["name"]="gaoqi"
假設字典 a 物件建立完後,陣列長度為 8:
我們要把”name”=”gaoqi”這個鍵值對放到字典物件 a 中,首先第一步需要計算鍵”name”的雜湊值。Python 中可以通過 hash()來計算。
>>> bin(hash("name"))
'-0b1010111101001110110101100100101'
由於陣列長度為 8,我們可以拿計算出的雜湊值的最右邊 3 位數字作為偏移量,即 “101”,十進位制是數字 5。我們檢視偏移量 5,對應的 bucket 是否為空。如果為空,則將鍵值對放進去。如果不為空,則依次取右邊 3 位作為偏移量,即“100”,十進位制是數字 4。再檢視偏移量為 4 的 bucket 是否為空。直到找到為空的 bucket 將鍵值對放進去。流程圖如下:
擴容
python 會根據雜湊表的擁擠程度擴容。“擴容”指的是:創造更大的陣列,將原有內容拷貝到新陣列中。
接近 2/3 時,陣列就會擴容。
根據鍵查詢“鍵值對”的底層過程
我們明白了,一個鍵值對是如何儲存到陣列中的,根據鍵物件取到值物件,理解起來就簡單了。
>>> a.get("name")
'gaoqi'
當我們呼叫 a.get(“name”),就是根據鍵“name”查詢到“鍵值對”,從而找到值物件“gaoqi”。
第一步,我們仍然要計算“name”物件的雜湊值:
>>> bin(hash("name"))
'-0b1010111101001110110101100100101'
和儲存的底層流程演算法一致,也是依次取雜湊值的不同位置的數字。 假設陣列長度為
8,我們可以拿計算出的雜湊值的最右邊 3 位數字作為偏移量,即“101”,十進位制是數字
5。我們檢視偏移量 5,對應的 bucket 是否為空。如果為空,則返回 None。如果不為空,則將這個 bucket 的鍵物件計算對應雜湊值,和我們的雜湊值進行比較,如果相等。則將對應“值物件”返回。如果不相等,則再依次取其他幾位數字,重新計算偏移量。依次取完後,仍然沒有找到。則返回 None。流程圖如下:
用法總結:
- 鍵必須可雜湊
(1) 數字、字串、元組,都是可雜湊的。
(2) 自定義物件需要支援下面三點:
1支援 hash()函式
2支援通過__eq__()方法檢測相等性。
3若 a==b 為真,則 hash(a)==hash(b)也為真。
- 字典在記憶體中開銷巨大,典型的空間換時間。
- 鍵查詢速度很快
- 往字典裡面新增新建可能導致擴容,導致雜湊表中鍵的次序變化。因此,不要在遍歷字典的同時進行字典的修改。
集合
集合是無序可變,元素不能重複。實際上,集合底層是字典實現,集合的所有元素都是字典中的“鍵物件”,因此是不能重複的且唯一的。
集合建立和刪除
- 使用{}建立集合物件,並使用 add()方法新增元素
>>> a = {3,5,7}
>>> a
{3, 5, 7}
>>> a.add(9)
>>> a
{9, 3, 5, 7}
- 使用 set(),將列表、元組等可迭代物件轉成集合。如果原來資料存在重複資料,則只保留一個。
>>> a = ['a','b','c','b']
>>> b = set(a)
>>> b
{'b', 'a', 'c'}
- remove()刪除指定元素;clear()清空整個集合
>>> a = {10,20,30,40,50}
>>> a.remove(20)
>>> a
{10, 50, 30}
集合相關操作
像數學中概念一樣,Python 對集合也提供了並集、交集、差集等運算。我們給出示例:
>>> a = {1,3,'sxt'}
>>> b = {'he','it','sxt'}
>>> a|b #並集
{1, 3, 'sxt', 'he', 'it'} |
|
>>> a&b {'sxt'} | #交集 |
>>> a-b {1, 3} | #差集 |
>>> a.union(b) {1, 3, 'sxt', 'he', 'it'} | #並集 |
>>> a.intersection(b) {'sxt'} | #交集 |
>>> a.difference(b) {1, 3} | #差集 |
章節實操作業
- 畫出程式碼 a = [100,200,300]的記憶體儲存示意圖。
- 使用 range 生成序列:30,40,50,60,70,80
- 推導式生成列表: a = [x*2 for x in range(100) if x%9==0],手寫出結果。
- 使用二維列表儲存表格資訊,並畫出簡單的記憶體儲存示意圖:
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
- 元組和列表有哪些共同點?有哪些不同點?
- 建立一個字典物件,包含如下資訊:
支出金額:300.15,支出日期:2018.10.18,支出人:高小七
- 使用字典儲存行資料,最後將整個表使用列表儲存起來。
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
- 用文字和自己畫的示意圖描述:字典儲存一個鍵值對的底層過程。
- 集合和字典有什麼關係?
第4章 控制語句
我們在前面學習的過程中,都是很短的示例程式碼,沒有進行復雜的操作。現在,我們將開始學習流程控制語句。
前面學習的變數、資料型別(整數、浮點數、布林)、序列(字串、列表、元組、字典、集合),可以看做是資料的組織方式。資料可以看做是“磚塊”!流程控制語句是程式碼的組織方式,可以看做是“混凝土”。
一個完整的程式,離不開“磚塊”,也離不開“混凝土”。他們的組合,才能讓我們建立從小到“一個方法”,大到“作業系統”,這樣各種各樣的“軟體”。
PyCharm 開發環境的使用
開始學習控制語句,就會有大量的練習,我們開始學習更加強大的 IDE。目前,比較流行的
IDE 是 PyCharm。當然,還有其他 IDE 可供我們使用:
- IDLE
- PyCharm
- wingIDE
- Eclipse
- IPython
PyCharm 下載和安裝
下載地址:https://www.jetbrains.com/pycharm/download/#section=windows
下載對應的版本:
和安裝普通軟體一致,點選下一步即可。只有幾個畫面需要單獨關注。根據 win 系統是 64 還是 32 位,選擇不同的型別。
不匯入配置:
將右側滾動條拖到最下面,然後選擇“accept”
不傳送訊息,選擇“Don’t send”:
啟用和選擇不同 UI 風格
啟用的方式:
(1) 購買正版
(2) 試用 30 天
(3) 網上尋找啟用碼
沒有啟用碼,選擇試用:
若有啟用碼,則輸入啟用碼:
選擇不同的 UI 風格:
根據自己喜好,選擇 Dracula 風格或者 IntelliJ 風格。然後點選“Skip Remaining and Set
Defaults”
建立專案和初始配置
1. 選擇:“Create New Project”
選擇路徑(儘量不要包含中文),檔名:mypro01 就是我們的專案名稱,可以修改。
其他:
- Project Interpreter 部分是選擇新建專案所依賴的 python 庫,第一個選項會在專案中建立一個 venv(virtualenv)目錄,這裡存放一個虛擬的 python 環境。這裡所有的類庫依賴都可以直接脫離系統安裝的 python 獨立執行。
3.Existing Interpreter 關聯已經存在的 python 直譯器,如果不想在專案中出現 venv 這個虛擬直譯器就可以選擇本地安裝的 python 環境。
那麼到底這兩個該怎麼去選擇呢,這裡建議選擇 New Environment 可以在 Base Interpreter 選擇系統中安裝的 Python 直譯器,這樣做的好處如下:
- python 專案可以獨立部署
- 防止一臺伺服器部署多個專案之間存在類庫的版本依賴問題發生
- 也可以充分發揮專案的靈活性
- 開啟專案後,右鍵單擊專案,建立 Python 檔案“mypy01”
- 執行 py 檔案,使用右鍵單擊編輯區,選擇“Run ‘mypy01’”即可。
·字型大小設定
File→Setting→Editor→Font 把字型調大一些
選擇結構
選擇結構通過判斷條件是否成立,來決定執行哪個分支。選擇結構有多種形式,分為:單分支、雙分支、多分支。流程圖如下:
單分支結構 雙分支結構
多分支結構
單分支選擇結構
if 語句單分支結構的語法形式如下: if 條件表示式:
語句/語句塊
其中:
1.條件表示式:可以是邏輯表示式、關係表示式、算術表示式等等。
2.語句/語句塊:可以是一條語句,也可以是多條語句。多條語句,縮排必須對齊一致。
【操作】輸入一個數字,小於 10,則列印這個數字(if_test01.py)
num = input("輸入一個數字:") if int(num)<10:
print(num)
條件表示式詳解
在選擇和迴圈結構中,條件表示式的值為 False 的情況如下:
False、0、0.0、空值 None、空序列物件(空列表、空元祖、空集合、空字典、空字串)、空 range 物件、空迭代物件。
其他情況,均為 True。這麼看來,Python 所有的合法表示式都可以看做條件表示式,甚至包括函式呼叫的表示式。
【操作】測試各種條件表示式
if 3: #整數作為條件表示式 print("ok") a = [] #列表作為條件表示式,由於為空列表,是 False if a: print("空列表,False") s = "False" #非空字串,是 True if s: |
print("非空字串,是 True") c = 9 if 3<c<20: print("3<c<20") if 3<c and c<20: print("3<c and c<20") if True: #布林值 print("True") |
執行結果如下:
ok
非空字串,是 True
3<c<20
3<c and c<20
True >>>
·條件表示式中,不能有賦值操作符“=”
在 Python 中,條件表示式不能出現賦值操作符“=”,避免了其他語言中經常誤將關係運
算符“==”寫作賦值運算子“=”帶來的困擾。如下程式碼將會報語法錯誤: if 3<c and (c=20):
print("賦值符不能出現在條件表示式中") 雙分支選擇結構
雙分支結構的語法格式如下: if 條件表示式 :
語句 1/語句塊 1 else:
語句 2/語句塊 2
【操作】輸入一個數字,小於 10,則列印該數字;大於 10,則列印“數字太大”
num = input("輸入一個數字:") if int(num)<10: print(num) else: print("數字太大") |
三元條件運算子
Python 提供了三元運算子,用來在某些簡單雙分支賦值情況。三元條件運算子語法格式如下:
if | (條件表示式) | else |
條件為真時的值條件為假時的值
上一個案例程式碼,可以用三元條件運算子實現:
num = input("請輸入一個數字")
print( num if int(num)<10 else "數字太大")
可以看到,這種寫法更加簡潔,易讀。
多分支選擇結構
多分支選擇結構的語法格式如下: if 條件表示式 1 :
語句 1/語句塊 1 elif 條件表示式 2:
語句 2/語句塊 2
.
. .
elif 條件表示式 n :
語句 n/語句塊 n
[else:
語句 n+1/語句塊 n+1
]
【注】計算機行業,描述語法格式時,使用中括號[]通常表示可選,非必選。
多分支結構,幾個分支之間是有邏輯關係的,不能隨意顛倒順序。
【操作】輸入一個學生的成績,將其轉化成簡單描述:不及格(小於 60)、及格(60-79)、良好(80-89)、優秀(90-100)。
方法 1(使用完整的條件表達)
score = int(input("請輸入分數")) grade = '' if(score<60): grade = "不及格" if(60<=score<80): grade = "及格" if(80<=score<90): grade = "良好" if(90<=score<=100): grade = "優秀" print("分數是{0},等級是{1}".format(score,grade)) |
每個分支都使用了獨立的、完整的判斷,順序可以隨意挪動,而不影響程式執行。
方法 2(利用多分支結構)
score = int(input("請輸入分數")) grade = '' if score<60 : grade = "不及格" elif score<80 : grade = "及格" elif score<90 : grade = "良好" elif score<=100: grade = "優秀" print("分數是{0},等級是{1}".format(score,grade)) |
多分支結構,幾個分支之間是有邏輯關係的,不能隨意顛倒順序。
【操作】已知點的座標(x,y),判斷其所在的象限
x = int(input("請輸入 x 座標")) y = int(input("請輸入 y 座標")) if(x==0 and y==0):print("原點") elif(x==0):print("y 軸") elif(y==0):print("x 軸") elif(x>0 and y>0):print("第一象限") elif(x<0 and y>0):print("第二象限") elif(x<0 and y<0):print("第三象限") else: print("第四象限") |
選擇結構巢狀
選擇結構可以巢狀,使用時一定要注意控制好不同級別程式碼塊的縮排量,因為縮排量決定了程式碼的從屬關係。語法格式如下:
if 表示式 1:
語句塊 1
if 表示式 2:語句塊 2 else:
語句塊 3 else:
if 表示式 4:語句塊 4
【操作】輸入一個分數。分數在 0-100 之間。90 以上是 A,80 以上是 B,70 以上是 C,60 以上是 D。60 以下是 E。
score = int(input("請輸入一個在0-100之間的數字:")) grade = "" if score>100 or score<0: score = int(input("輸入錯誤!請重新輸入一個在0-100之間的數字:")) else: if score>=90: grade = "A" elif score>=80: grade = 'B' elif score>=70: grade = 'C' elif score>=60: grade = 'D' else: grade = 'E' print("分數為{0},等級為{1}".format(score,grade)) |
#或者也可以用下面程式碼更少的方法。不過,需要大家思考為什麼這麼寫了 score = int(input("請輸入一個在0-100之間的數字:")) degree = "ABCDE" num = 0 if score>100 or score<0: score = int(input("輸入錯誤!請重新輸入一個在0-100之間的數字:")) else: num = score//10 if num<6:num=5 print("分數是{0},等級是{1}".format(score,degree[9-num])) |
迴圈結構
迴圈結構用來重複執行一條或多條語句。表達這樣的邏輯:如果符合條件,則反覆執行迴圈體裡的語句。在每次執行完後都會判斷一次條件是否為 True,如果為 True 則重複執行迴圈體裡的語句。圖示如下:
迴圈體裡面的語句至少應該包含改變條件表示式的語句,以使迴圈趨於結束;否則,就會變成一個死迴圈。
while 迴圈
while 迴圈的語法格式如下:
while 條件表示式:迴圈體語句
我們通過一些簡單的練習,來慢慢熟悉 while 迴圈。
【操作】利用 while 迴圈列印從 0-10 的數字。
num = 0 while num<=10: print(num) num += 1 |
【操作】利用 while 迴圈,計算 1-100 之間數字的累加和;計算 1-100 之間偶數的累加和,計算 1-100 之間奇數的累加和。
num = 0 sum_all = 0 | #1-100 所有數的累加和 |
sum_even = 0 #1-100 偶數的累加和 sum_odd = 0 #1-100 奇數的累加和 while num<=100: sum_all += num if num%2==0:sum_even += num else:sum_odd += num num += 1 #迭代,改變條件表示式,使迴圈趨於結束 print("1-100 所有數的累加和",sum_all) print("1-100 偶數的累加和",sum_even) print("1-100 奇數的累加和",sum_odd) |
for 迴圈和可迭代物件遍歷
for 迴圈通常用於可迭代物件的遍歷。for 迴圈的語法格式如下: for 變數 in 可迭代物件:迴圈體語句
【操作】遍歷一個元組或列表
for x in (20,30,40): print(x*3) |
可迭代物件
Python 包含以下幾種可迭代物件:
- 序列。包含:字串、列表、元組
- 字典
- 迭代器物件(iterator)
- 生成器函式(generator)
- 檔案物件
我們已經在前面學習了序列、字典等知識,迭代器物件和生成器函式將在後面進行詳解。接下來,我們通過迴圈來遍歷這幾種型別的資料:
【操作】遍歷字串中的字元
for x in "sxt001": print(x) |
【操作】遍歷字典
d = {'name':'gaoqi','age':18,'address':'西三旗 001 號樓'} for x in d: #遍歷字典所有的 key print(x) for x in d.keys():#遍歷字典所有的 key print(x) for x in d.values():#遍歷字典所有的 value print(x) for x in d.items():#遍歷字典所有的"鍵值對" print(x) |
range 物件
range 物件是一個迭代器物件,用來產生指定範圍的數字序列。格式為:
range(start, end [,step])
生成的數值序列從 start 開始到 end 結束(不包含 end)。若沒有填寫 start,則預設從 0
開始。step 是可選的步長,預設為 1。如下是幾種典型示例: for i in range(10) 產生序列:0 1 2 3 4 5 6 7 8 9 for i in range(3,10) 產生序列:3 4 5 6 7 8 9 for i in range(3,10,2) 產生序列:3 5 7 9
【操作】利用 for 迴圈,計算 1-100 之間數字的累加和;計算 1-100 之間偶數的累加和,計算 1-100 之間奇數的累加和。
sum_all = 0 #1-100 所有數的累加和 sum_even = 0 #1-100 偶數的累加和 sum_odd = 0 #1-100 奇數的累加和 for num in range(101): sum_all += num
if num%2==0:sum_even += num else:sum_odd += num print("1-100 累加總和{0},奇數和{1},偶數和{2}".format(sum_all,sum_odd,sum_even))
巢狀迴圈和綜合練習
一個迴圈體內可以嵌入另一個迴圈,一般稱為“巢狀迴圈”,或者“多重迴圈”。
【操作】列印如下圖案
0 0 0 0 0 1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 4 4 4 4
for x in range(5): for y in range(5): print(x,end="\t") print() #僅用於換行 |
【操作】利用巢狀迴圈列印九九乘法表
for m in range(1,10): for n in range(1,m+1): print("{0}*{1}={2}".format(m,n,(m*n)),end="\t") print() |
執行結果:
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81 【操作】用列表和字典儲存下表資訊,並列印出表中工資高於 15000 的資料
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
r1= dict(name="高小一",age=18,salary=30000,city="北京") r2= dict(name="高小二",age=19,salary=20000,city="上海") r3= dict(name="高小三",age=20,salary=10000,city="深圳") tb = [r1,r2,r3] for x in tb: if x.get("salary")>15000: print(x) |
break 語句
break 語句可用於 while 和 for 迴圈,用來結束整個迴圈。當有巢狀迴圈時,break 語句只能跳出最近一層的迴圈。
【操作】使用 break 語句結束迴圈
while True: a = input("請輸入一個字元(輸入 Q 或 q 結束)") if a.upper()=='Q': print("迴圈結束,退出") break else: print(a) |
continue 語句
continue 語句用於結束本次迴圈,繼續下一次。多個迴圈巢狀時,continue 也是應用於最近的一層迴圈。
【操作】要求輸入員工的薪資,若薪資小於 0 則重新輸入。最後列印出錄入員工的數量和薪資明細,以及平均薪資
empNum = 0 salarySum= 0 salarys = [] while True: s = input("請輸入員工的薪資(按 Q 或 q 結束)") if s.upper()=='Q': print("錄入完成,退出") break if float(s)<0: continue empNum +=1 salarys.append(float(s)) salarySum += float(s) print("員工數{0}".format(empNum)) print("錄入薪資:",salarys) print("平均薪資{0}".format(salarySum/empNum)) |
執行結果:請輸入員工的薪資(按 Q 或 q 結束)2000 請輸入員工的薪資(按 Q 或 q 結束)3000 請輸入員工的薪資(按 Q 或 q 結束)4000 請輸入員工的薪資(按 Q 或 q 結束)5000 請輸入員工的薪資(按 Q 或 q 結束)Q 錄入完成,退出
員工數 4
錄入薪資: [2000.0, 3000.0, 4000.0, 5000.0] 平均薪資 3500.0 else 語句
while、for 迴圈可以附帶一個 else 語句(可選)。如果 for、while 語句沒有被 break 語句結束,則會執行 else 子句,否則不執行。語法格式如下:
while 條件表示式:迴圈體 else:
語句塊
或者:
for 變數 in 可迭代物件:迴圈體 else:
語句塊
【操作】員工一共 4 人。錄入這 4 位員工的薪資。全部錄入後,列印提示“您已經全部錄入 4 名員工的薪資”。最後,列印輸出錄入的薪資和平均薪資
salarySum= 0 salarys = [] for i in range(4): s = input("請輸入一共 4 名員工的薪資(按 Q 或 q 中途結束)") if s.upper()=='Q': print("錄入完成,退出") break if float(s)<0: continue salarys.append(float(s)) salarySum += float(s) else: print("您已經全部錄入 4 名員工的薪資") print("錄入薪資:",salarys) print("平均薪資{0}".format(salarySum/4)) |
迴圈程式碼優化雖然計算機越來越快,空間也越來越大,我們仍然要在效能問題上“斤斤計較”。編寫迴圈時,遵守下面三個原則可以大大提高執行效率,避免不必要的低效計算:
- 儘量減少迴圈內部不必要的計算
- 巢狀迴圈中,儘量減少內層迴圈的計算,儘可能向外提。
- 區域性變數查詢較快,儘量使用區域性變數
#迴圈程式碼優化測試 import time start = time.time() for i in range(1000): result = [] for m in range(10000): result.append(i*1000+m*100) end = time.time() print("耗時:{0}".format((end-start))) start2 = time.time() for i in range(1000): result = [] c = i*1000 for m in range(10000): result.append(c+m*100) end2 = time.time() print("耗時:{0}".format((end2-start2))) |
其他優化手段
- 連線多個字串,使用 join()而不使用+
- 列表進行元素插入和刪除,儘量在列表尾部操作使用 zip()並行迭代我們可以通過 zip()函式對多個序列進行並行迭代,zip()函式在最短序列“用完”時就會停止。
【操作】測試 zip()並行迭代
names = ("高淇","高老二","高老三","高老四") ages = (18,16,20,25) jobs = ("老師","程式設計師","公務員") for name,age,job in zip(names,ages,jobs): print("{0}--{1}--{2}".format(name,age,job)) |
執行結果:
高淇--18--老師高老二--16--程式設計師高老三--20--公務員
推導式建立序列
推導式是從一個或者多個迭代器快速建立序列的一種方法。它可以將迴圈和條件判斷結合,從而避免冗長的程式碼。推導式是典型的 Python 風格,會使用它代表你已經超過 Python 初學者的水平。
列表推導式
列表推導式生成列表物件,語法如下:
[表示式 for item in 可迭代物件 ] 或者:{表示式 for item in 可迭代物件 if 條件判斷}
>>> [x for x in range(1,5)]
[1, 2, 3, 4]
>>> [x*2 for x in range(1,5)]
[2, 4, 6, 8]
>>> [x*2 for x in range(1,20) if x%5==0 ]
[10, 20, 30]
>>> [a for a in "abcdefg"]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> cells = [(row,col) for row in range(1,10) for col in range(1,10)] #可以使用兩
個迴圈
>>> for cell in cells: print(cell)
字典推導式
字典的推導式生成字典物件,格式如下:
{key_expression : value_expression for 表示式 in 可迭代物件}
類似於列表推導式,字典推導也可以增加 if 條件判斷、多個 for 迴圈。
統計文字中字元出現的次數:
>>> my_text = ' i love you, i love sxt, i love gaoqi'
>>> char_count = {c:my_text.count(c) for c in my_text}
>>> char_count
{' ': 9, 'i': 4, 'l': 3, 'o': 5, 'v': 3, 'e': 3, 'y': 1, 'u': 1, ',': 2, 's': 1, 'x': 1, 't': 1, 'g': 1, 'a': 1, 'q': 1}
集合推導式
集合推導式生成集合,和列表推導式的語法格式類似:
{表示式 for item in 可迭代物件 } 或者:{表示式 for item in 可迭代物件 if 條件判斷}
>>> {x for x in range(1,100) if x%9==0} {99, 36, 72, 9, 45, 81, 18, 54, 90, 27, 63} 生成器推導式(生成元組)
很多同學可能會問:“都有推導式,元組有沒有?”,能不能用小括號呢?
>>> (x for x in range(1,100) if x%9==0)
<generator object <genexpr> at 0x0000000002BD3048>
我們發現提示的是“一個生成器物件”。顯然,元組是沒有推導式的。一個生成器只能執行一次。第一次迭代可以得到資料,第二次迭代發現資料已經沒有了。
>>> gnt = (x for x in range(1,100) if x%9==0) >>> for x in gnt:
print(x,end=' ')
9 18 27 36 45 54 63 72 81 90 99 >>> for x in gnt:
print(x,end=' ')
>>>
綜合練習
- 【操作】繪製多個同心圓
import turtle t = turtle.Pen() my_colors = ("red","green","yellow","black") t.width(4) t.speed(1) for i in range(10): #0 1 2 3 4 t.penup() t.goto(0,-i*10) #0,-100,-200,-300,-400 t.pendown() t.color(my_colors[i%len(my_colors)]) t.circle(15+i*10) #100,200,300,400,,500 turtle.done() #程式執行完,視窗仍然在 |
執行效果:
- 【操作】繪製 18*18 棋盤
#畫棋盤 import turtle width = 30 num = 18 x1 = [(-400,400),(-400+width*num,400)] y1 = [(-400,400),(-400,400-width*num)] t = turtle.Pen() t.speed(10) #t.goto(x1[0][0],x1[0][1]) #t.goto(x1[1][0],x1[1][1]) for i in range(0,19): t.penup() t.goto(x1[0][0],x1[0][1]-30*i) t.pendown() t.goto(x1[1][0],x1[1][1]-30*i) for i in range(0,19): t.penup() t.goto(y1[0][0]+30*i,y1[0][1]) t.pendown() t.goto(y1[1][0]+30*i,y1[1][1]) t.hideturtle() #隱藏畫筆 turtle.done() #保證執行視窗不被自動關閉 |
執行結果:
實操作業
- 安裝 Pycharm 開發環境,並使用圖文描述整個過程。
- 輸入一個學生的成績,將其轉化成簡單描述:不及格(小於 60)、及格(60-79)、良好
(80-89)、優秀(90-100)
- 已知點的座標(x,y),判斷其所在的象限
- 輸入一個分數。分數在 0-100 之間。90 以上是 A,80 以上是 B,70 以上是 C,60 以上是 D。60 以下是 E
- 利用 while 迴圈,計算 1-100 之間數字的累加和;計算 1-100 之間偶數的累加和,計算
1-100 之間奇數的累加和
- 利用 for 迴圈,計算 1-100 之間數字的累加和;計算 1-100 之間偶數的累加和,計算
1-100 之間奇數的累加和
- 列印如下圖案
0 0 0 0 0
1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 4 4 4 4
- 利用巢狀迴圈列印九九乘法表
- 用列表和字典儲存下表資訊,並列印出表中工資高於 15000 的資料
姓名 | 年齡 | 薪資 | 城市 |
高小一 | 18 | 30000 | 北京 |
高小二 | 19 | 20000 | 上海 |
高小五 | 20 | 10000 | 深圳 |
- 要求輸入員工的薪資,若薪資小於 0 則重新輸入。最後列印出錄入員工的數量和薪資明細,以及平均薪資
- 員工一共 4 人。錄入這 4 位員工的薪資。全部錄入後,列印提示“您已經全部錄入 4 名員工的薪資”。最後,列印輸出錄入的薪資和平均薪資
- 使用海龜繪圖,繪製同心圓:
- 使用海龜繪圖,繪製 18*18 的棋盤:
第5章 函式用法和底層分析
函式是可重用的程式程式碼塊。函式的作用,不僅可以實現程式碼的複用,更能實現程式碼的一致性。一致性指的是,只要修改函式的程式碼,則所有呼叫該函式的地方都能得到體現。
在編寫函式時,函式體中的程式碼寫法和我們前面講述的基本一致,只是對程式碼實現了封裝,並增加了函式呼叫、傳遞引數、返回計算結果等內容。
為了讓大家更容易理解,掌握的更深刻。我們也要深入記憶體底層進行分析。絕大多數語言記憶體底層都是高度相似的,這樣大家掌握了這些內容也便於以後學習其他語言。
函式簡介
函式的基本概念
- 一個程式由一個個任務組成;函式就是代表一個任務或者一個功能。
- 函式是程式碼複用的通用機制。
Python 函式的分類
Python 中函式分為如下幾類:
- 內建函式
我們前面使用的 str()、list()、len()等這些都是內建函式,我們可以拿來直接使用。
- 標準庫函式
我們可以通過 import 語句匯入庫,然後使用其中定義的函式
- 第三方庫函式
Python 社群也提供了很多高質量的庫。下載安裝這些庫後,也是通過 import 語句匯入,然後可以使用這些第三方庫的函式
- 使用者自定義函式
使用者自己定義的函式,顯然也是開發中適應使用者自身需求定義的函式。今天我們學習的就是如何自定義函式。
函式的定義和呼叫
核心要點
Python 中,定義函式的語法如下: def 函式名 ([引數列表]) :
'''文件字串''' 函式體/若干語句
要點:
- 我們使用 def 來定義函式,然後就是一個空格和函式名稱;
(1) Python 執行 def 時,會建立一個函式物件,並繫結到函式名變數上。
- 引數列表
(1) 圓括號內是形式引數列表,有多個引數則使用逗號隔開
(2) 形式引數不需要宣告型別,也不需要指定函式返回值型別
(3) 無引數,也必須保留空的圓括號
(4) 實參列表必須與形參列表一一對應
- return 返回值
(1) 如果函式體中包含 return 語句,則結束函式執行並返回值;
(2) 如果函式體中不包含 return 語句,則返回 None 值。
- 呼叫函式之前,必須要先定義函式,即先呼叫 def 建立函式物件
(1) 內建函式物件會自動建立
(2) 標準庫和第三方庫函式,通過 import 匯入模組時,會執行模組中的 def 語句
我們通過實際定義函式來學習函式的定義方式。
形參和實參
形參和實參的要點,請參考上一節中的總結。在此不再贅述。
【操作】定義一個函式,實現兩個數的比較,並返回較大的值。
def printMax(a,b): '''實現兩個數的比較,並返回較大的值''' if a>b: print(a,'較大值') else: print(b,'較大值') printMax(10,20) printMax(30,5) |
執行結果:
20 較大值
30 較大值
上面的 printMax 函式中,在定義時寫的 printMax(a,b)。a 和 b 稱為“形式引數”,簡稱“形參”。也就是說,形式引數是在定義函式時使用的。 形式引數的命名只要符合“識別符號”命名規則即可。
在呼叫函式時,傳遞的引數稱為“實際引數”,簡稱“實參”。上面程式碼中, printMax(10,20),10 和 20 就是實際引數。
文件字串(函式的註釋)
程式的可讀性最重要,一般建議在函式體開始的部分附上函式定義說明,這就是“文件字串”,也有人成為“函式的註釋”。我們通過三個單引號或者三個雙引號來實現,中間可以加入多行文字進行說明。
【操作】測試文件字串的使用
def print_star(n): '''根據傳入的 n,列印多個星號''' print("*"*n) help(print_star) |
我們呼叫 help(函式名.__doc__)可以列印輸出函式的文件字串。執行結果如下:
Help on function print_star in module __main__: print_star(n) 根據傳入的 n,列印多個星號 |
返回值
return 返回值要點:
- 如果函式體中包含 return 語句,則結束函式執行並返回值;
- 如果函式體中不包含 return 語句,則返回 None 值。
- 要返回多個返回值,使用列表、元組、字典、集合將多個值“存起來”即可。
【操作】定義一個列印 n 個星號的無返回值的函式
def print_star(n):
print("*"*n)
print_star(5)
【操作】定義一個返回兩個數平均值的函式
def my_avg(a,b):
return (a+b)/2
#如下是函式的呼叫 c = my_avg(20,30) print(c)
函式也是物件,記憶體底層分析
Python 中,“一切都是物件”。實際上,執行 def 定義函式後,系統就建立了相應的函式物件。我們執行如下程式,然後進行解釋:
def print_star(n): print("*"*n)
print(print_star) print(id(print_star)) c = print_star c(3)
執行結果:
<function print_star at 0x0000000002BB8620>
45844000
***
上面程式碼執行 def 時,系統中會建立函式物件,並通過 print_star 這個變數進行引用:
我們執行“c=print_star”後,顯然將 print_star 變數的值賦給了變數 c,記憶體圖變成了:
顯然,我們可以看出變數 c 和 print_star 都是指向了同一個函式物件。因此,執行 c(3)和執行 print_star(3)的效果是完全一致的。 Python 中,圓括號意味著呼叫函式。在沒有圓括號的情況下,Python 會把函式當做普通物件。
與此核心原理類似,我們也可以做如下操作: zhengshu = int
zhengshu("234")
顯然,我們將內建函式物件 int()賦值給了變數 zhengshu,這樣 zhengshu 和 int 都是指向了同一個內建函式物件。當然,此處僅限於原理性講解,實際開發中沒必要這麼做。
變數的作用域(全域性變數和區域性變數)
變數起作用的範圍稱為變數的作用域,不同作用域內同名變數之間互不影響。變數分為:全域性變數、區域性變數。
全域性變數:
1. 在函式和類定義之外宣告的變數。作用域為定義的模組,從定義位置開始直到模組結束。
2. 全域性變數降低了函式的通用性和可讀性。應儘量避免全域性變數的使用。
3. 全域性變數一般做常量使用。
4. 函式內要改變全域性變數的值,使用 global 宣告一下區域性變數:
- 在函式體中(包含形式引數)宣告的變數。
- 區域性變數的引用比全域性變數快,優先考慮使用。
- 如果區域性變數和全域性變數同名,則在函式內隱藏全域性變數,只使用同名的區域性變數【操作】全域性變數的作用域測試
a = 100 | #全域性變數 |
def f1(): global a | #如果要在函式內改變全域性變數的值,增加 global 關鍵字宣告 |
print(a) a = 300 f1() print(a) | #列印全域性變數 a 的值 |
執行結果:
100
300
【操作】全域性變數和區域性變數同名測試
a=100 def f1(): a = 3 | #同名的區域性變數 |
print(a) f1() print(a) | #a 仍然是 100,沒有變化 |
執行結果:
3
100
【操作】 輸出區域性變數和全域性變數
a = 100 def f1(a,b,c): print(a,b,c) print(locals()) | #列印輸出的區域性變數 |
print("#"*20) print(globals()) f1(2,3,4) | #列印輸出的全域性變數 |
執行結果:
2 3 4
{'c': 4, 'b': 3, 'a': 2}
####################
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\PythonExec\\if_test01.py',
'a': 100, 'f1': <function f1 at 0x0000000002BB8620>}
區域性變數和全域性變數效率測試
區域性變數的查詢和訪問速度比全域性變數快,優先考慮使用,尤其是在迴圈的時候。在特別強調效率的地方或者迴圈次數較多的地方,可以通過將全域性變數轉為區域性變數提高執行速度。
【操作】測試區域性變數和全域性變數效率
#測試區域性變數、全域性變數的效率 import math import time def test01(): start = time.time() for i in range(10000000): math.sqrt(30) end = time.time() print("耗時{0}".format((end-start))) def test02(): b = math.sqrt start = time.time() for i in range(10000000): b(30) end = time.time() print("耗時{0}".format((end-start))) test01() test02() |
執行結果:
耗時 2.3589999675750732 耗時 1.6410000324249268 引數的傳遞
函式的引數傳遞本質上就是:從實參到形參的賦值操作。 Python 中“一切皆物件”,所有的賦值操作都是“引用的賦值”。所以,Python 中引數的傳遞都是“引用傳遞”,不是“值傳遞”。具體操作時分為兩類:
1. 對“可變物件”進行“寫操作”,直接作用於原物件本身。
2. 對“不可變物件”進行“寫操作”,會產生一個新的“物件空間”,並用新的值填充這塊空間。(起到其他語言的“值傳遞”效果,但不是“值傳遞”)
可變物件有:
字典、列表、集合、自定義的物件等不可變物件有:
數字、字串、元組、function 等傳遞可變物件的引用
傳遞引數是可變物件(例如:列表、字典、自定義的其他可變物件等),實際傳遞的還是物件的引用。在函式體中不建立新的物件拷貝,而是可以直接修改所傳遞的物件。
【操作】引數傳遞:傳遞可變物件的引用
b = [10,20] def f2(m): print("m:",id(m)) #b 和 m 是同一個物件 m.append(30) #由於 m 是可變物件,不建立物件拷貝,直接修改這個物件 f2(b) print("b:",id(b)) print(b) |
執行結果:
m: 45765960
b: 45765960
[10, 20, 30]
傳遞不可變物件的引用
傳遞引數是不可變物件(例如:int、float、字串、元組、布林值),實際傳遞的還是物件的引用。在”賦值操作”時,由於不可變物件無法修改,系統會新建立一個物件。
【操作】引數傳遞:傳遞不可變物件的引用
a = 100 def f1(n): print("n:",id(n)) | #傳遞進來的是 a 物件的地址 |
n = n+200 | #由於 a 是不可變物件,因此建立新的物件 n |
print("n:",id(n)) print(n) f1(a) print("a:",id(a)) | #n 已經變成了新的物件 |
執行結果:
n: 1663816464 n: 46608592
300
a: 1663816464
顯然,通過 id 值我們可以看到 n 和 a 一開始是同一個物件。給 n 賦值後,n 是新的物件。
淺拷貝和深拷貝
為了更深入的瞭解引數傳遞的底層原理,我們需要講解一下“淺拷貝和深拷貝”。我們可以使用內建函式:copy(淺拷貝)、deepcopy(深拷貝)。
淺拷貝:不拷貝子物件的內容,只是拷貝子物件的引用。
深拷貝:會連子物件的記憶體也全部拷貝一份,對子物件的修改不會影響源物件
原始碼:
#測試淺拷貝和深拷貝 import copy def testCopy(): '''測試淺拷貝''' a = [10, 20, [5, 6]] b = copy.copy(a) print("a", a) print("b", b) b.append(30) b[2].append(7) print("淺拷貝......") print("a", a) print("b", b) def testDeepCopy(): '''測試深拷貝''' a = [10, 20, [5, 6]] b = copy.deepcopy(a) print("a", a) print("b", b) b.append(30) b[2].append(7) print("深拷貝......") print("a", a) print("b", b) testCopy() print("*************") testDeepCopy() |
執行結果:
a [10, 20, [5, 6]] b [10, 20, [5, 6]] 淺拷貝...... a [10, 20, [5, 6, 7]] b [10, 20, [5, 6, 7], 30]
*************
a [10, 20, [5, 6]] b [10, 20, [5, 6]] 深拷貝...... a [10, 20, [5, 6]] b [10, 20, [5, 6, 7], 30]
傳遞不可變物件包含的子物件是可變的情況
#傳遞不可變物件時。不可變物件裡面包含的子物件是可變的。則方法內修改了這個可變物件,源物件也發生了變化。 a = (10,20,[5,6]) print("a:",id(a)) def test01(m): print("m:",id(m)) m[2][0] = 888 print(m) print("m:",id(m)) test01(a) print(a) |
執行結果:
a: 41611632 m: 41611632 (10, 20, [888, 6]) m: 41611632 (10, 20, [888, 6])
引數的幾種型別位置引數
函式呼叫時,實參預設按位置順序傳遞,需要個數和形參匹配。按位置傳遞的引數,稱為: “位置引數”。【操作】測試位置引數
def f1(a,b,c): print(a,b,c) f1(2,3,4) f1(2,3) #報錯,位置引數不匹配 |
執行結果:
2 3 4
Traceback (most recent call last):
File "E:\PythonExec\if_test01.py", line 5, in <module> f1(2,3)
TypeError: f1() missing 1 required positional argument: 'c'
預設值引數
我們可以為某些引數設定預設值,這樣這些引數在傳遞時就是可選的。稱為“預設值引數”。預設值引數放到位置引數後面。【操作】測試預設值引數
def f1(a,b,c=10,d=20): print(a,b,c,d) f1(8,9) f1(8,9,19) f1(8,9,19,29) | #預設值引數必須位於普通位置引數後面 |
執行結果:
8 9 10 20
8 9 19 20
8 9 19 29 命名引數
我們也可以按照形參的名稱傳遞引數,稱為“命名引數”,也稱“關鍵字引數”。
【操作】測試命名引數
def f1(a,b,c): print(a,b,c) f1(8,9,19) #位置引數 f1(c=10,a=20,b=30) #命名引數 |
執行結果:
8 9 19
20 30 10 可變引數
可變引數指的是“可變數量的引數”。分兩種情況:
- *param(一個星號),將多個引數收集到一個“元組”物件中。
- **param(兩個星號),將多個引數收集到一個“字典”物件中。
【操作】測試可變引數處理(元組、字典兩種方式)
def f1(a,b,*c):
print(a,b,c)
f1(8,9,19,20)
def f2(a,b,**c):
print(a,b,c)
f2(8,9,name='gaoqi',age=18)
def f3(a,b,*c,**d):
print(a,b,c,d) f3(8,9,20,30,name='gaoqi',age=18)
執行結果:
8 9 (19, 20)
8 9 {'name': 'gaoqi', 'age': 18}
8 9 (20, 30) {'name': 'gaoqi', 'age': 18} 強制命名引數
在帶星號的“可變引數”後面增加新的引數,必須在呼叫的時候“強制命名引數”。
【操作】強制命名引數的使用
def f1(*a,b,c): print(a,b,c) #f1(2,3,4) #會報錯。由於 a 是可變引數,將 2,3,4 全部收集。造成 b 和 c 沒有賦值。 f1(2,b=3,c=4) |
執行結果:
(2,) 3 4
lambda 表示式和匿名函式
lambda 表示式可以用來宣告匿名函式。lambda 函式是一種簡單的、在同一行中定義函式的方法。lambda 函式實際生成了一個函式物件。
lambda 表示式只允許包含一個表示式,不能包含複雜語句,該表示式的計算結果就是函式的返回值。
lambda 表示式的基本語法如下:
lambda arg1,arg2,arg3... : <表示式>
arg1/arg2/arg3 為函式的引數。<表示式>相當於函式體。運算結果是:表示式的運算結果。
【操作】lambda 表示式使用
f = lambda a,b,c:a+b+c print(f) print(f(2,3,4)) g = [lambda a:a*2,lambda b:b*3,lambda c:c*4] print(g[0](6),g[1](7),g[2](8)) |
執行結果:
<function <lambda> at 0x0000000002BB8620>
9
12 21 32
eval()函式
功能:將字串 str 當成有效的表示式來求值並返回計算結果。
語法: eval(source[, globals[, locals]]) -> value
引數:
source:一個 Python 表示式或函式 compile()返回的程式碼物件
globals:可選。必須是 dictionary locals:可選。任意對映物件
#測試eval()函式 s = "print('abcde')" eval(s) a = 10 b = 20 c = eval("a+b") |
print(c)
dict1 = dict(a=100,b=200)
d = eval("a+b",dict1) print(d)
eval 函式會將字串當做語句來執行,因此會被注入安全隱患。比如:字串中含有刪除檔案的語句。那就麻煩大了。因此,使用時候,要慎重!!!遞迴函式
遞迴函式指的是:自己呼叫自己的函式,在函式體內部直接或間接的自己呼叫自己。遞迴類似於大家中學數學學習過的“數學歸納法”。 每個遞迴函式必須包含兩個部分:
- 終止條件
表示遞迴什麼時候結束。一般用於返回值,不再呼叫自己。
- 遞迴步驟
把第 n 步的值和第 n-1 步相關聯。
遞迴函式由於會建立大量的函式物件、過量的消耗記憶體和運算能力。在處理大量資料時,謹慎使用。
【操作】 使用遞迴函式計算階乘(factorial)
def factorial(n): if n==1:return 1 return n*factorial(n-1) for i in range(1,6): print(i,'!=',factorial(i)) |
執行結果:
1 != 1
2 != 2
3 != 6
4 != 24
5!=120
巢狀函式(內部函式)
巢狀函式:
在函式內部定義的函式!
【操作】巢狀函式定義
def f1(): print('f1 running...') def f2(): print('f2 running...') f2() f1() |
執行結果:
f1 running... f2 running...
上面程式中,f2()就是定義在 f1 函式內部的函式。f2()的定義和呼叫都在 f1()函式內部。一般在什麼情況下使用巢狀函式?
- 封裝 - 資料隱藏
外部無法訪問“巢狀函式”。
- 貫徹 DRY(Don’t Repeat Yourself) 原則
巢狀函式,可以讓我們在函式內部避免重複程式碼。
- 閉包
後面會詳細講解。
【操作】使用巢狀函式避免重複程式碼
def printChineseName(name,familyName): print("{0} {1}".format(familyName,name)) def printEnglishName(name,familyName): print("{0} {1}".format(name, familyName)) |
#使用1個函式代替上面的兩個函式 def printName(isChinese,name,familyName): def inner_print(a,b): print("{0} {1}".format(a,b)) if isChinese: inner_print(familyName,name) else: inner_print(name,familyName) printName(True,"小七","高") printName(False,"George","Bush") |
nonlocal 關鍵字
nonlocal 用來宣告外層的區域性變數。
global 用來宣告全域性變數。
【操作】使用 nonlocal 宣告外層區域性變數
#測試nonlocal、global關鍵字的用法 a = 100 def outer(): b = 10 |
def inner(): nonlocal b #宣告外部函式的區域性變數 print("inner b:",b) b = 20 global a #宣告全域性變數 a = 1000 inner() print("outer b:",b) outer() print("a:",a) |
LEGB 規則
Python 在查詢“名稱”時,是按照 LEGB 規則查詢的:
Local-->Enclosed-->Global-->Built in
Local 指的就是函式或者類的方法內部
Enclosed 指的是巢狀函式(一個函式包裹另一個函式,閉包)
Global 指的是模組中的全域性變數
Built in 指的是 Python 為自己保留的特殊名稱。
如果某個 name 對映在區域性(local)名稱空間中沒有找到,接下來就會在閉包作用域 (enclosed)進行搜尋,如果閉包作用域也沒有找到,Python 就會到全域性(global)名稱空間中進行查詢,最後會在內建(built-in)名稱空間搜尋 (如果一個名稱在所有名稱空間中都沒有找到,就會產生一個 NameError)。
#測試 LEGB str = "global" def outer(): str = "outer" def inner(): str = "inner" print(str) |
inner()
outer()
我們依次將幾個 str 註釋掉,觀察控制檯列印的內容,體會 LEBG 的搜尋順序。
實操作業
- 定義一個函式實現反響輸出一個整數。比如:輸入 3245,輸出 5432.
- 編寫一個函式,計算下面的數列:
m(n) 1 2 ... n
2 3 n1
- 輸入三角形三個頂點的座標,若有效則計算三角形的面積;如座標無效,則給出提示。
- 輸入一個毫秒數,將該數字換算成小時數,分鐘數、秒數。
- 使用海龜繪圖。輸入多個點,將這些點都兩兩相連。
第6章(1) 物件導向初步
物件導向(Object oriented Programming,OOP)程式設計的思想主要是針對大型軟體設計而來的。物件導向程式設計使程式的擴充套件性更強、可讀性更好,使的程式設計可以像搭積木一樣簡單。
物件導向程式設計將資料和運算元據相關的方法封裝到物件中,組織程式碼和資料的方式更加接近人的思維,從而大大提高了程式設計的效率。
Python 完全採用了物件導向的思想,是真正物件導向的程式語言,完全支援物件導向的基本功能,例如:繼承、多型、封裝等。
Python 中,一切皆物件。我們在前面學習的資料型別、函式等,都是物件。
注:Python 支援程式導向、物件導向、函數語言程式設計等多種程式設計正規化。
物件導向和麵向過程區別
·程式導向(Procedure Oriented)思維
程式導向程式設計更加關注的是“程式的邏輯流程”,是一種“執行者”思維,適合編寫小規模的程式。
程式導向思想思考問題時,我們首先思考“怎麼按步驟實現?”並將步驟對應成方法,一步一步,最終完成。 這個適合簡單任務,不需要過多協作的情況下。比如,如何開車?我們很容易就列出實現步驟:
1. 發動車 2. 掛擋 3.踩油門 4. 走你
程式導向適合簡單、不需要協作的事務。 但是當我們思考比較複雜的問題,比如“如何造車?”,就會發現列出 1234 這樣的步驟,是不可能的。那是因為,造車太複雜,需要很多協作才能完成。此時物件導向思想就應運而生了。
·物件導向(Object Oriented)思維
物件導向更加關注的是“軟體中物件之間的關係”,是一種“設計者”思維,適合編寫大規模的程式。
物件導向(Object)思想更契合人的思維模式。我們首先思考的是“怎麼設計這個事物?” 比如思考造車,我們就會先思考“車怎麼設計?”,而不是“怎麼按步驟造車的問題”。這就是思維方式的轉變。
q 物件導向方式思考造車,發現車由如下物件組成:
- 1. 輪胎
- 2. 發動機
- 3. 車殼
- 4. 座椅
- 5. 擋風玻璃
為了便於協作,我們找輪胎廠完成製造輪胎的步驟,發動機廠完成製造發動機的步驟;這樣,發現大家可以同時進行車的製造,最終進行組裝,大大提高了效率。但是,具體到輪胎廠的一個流水線操作,仍然是有步驟的,還是離不開程式導向思想!
因此,物件導向可以幫助我們從巨集觀上把握、從整體上分析整個系統。 但是,具體到實現部分的微觀操作(就是一個個方法),仍然需要程式導向的思路去處理。
我們千萬不要把程式導向和麵向物件對立起來。他們是相輔相成的。物件導向離不開程式導向!
· 物件導向思考方式
遇到複雜問題,先從問題中找名詞(程式導向更多的是找動詞),然後確立這些名詞哪些可以作為類,再根據問題需求確定的類的屬性和方法,確定類之間的關係。
· 物件導向和麵向過程的總結
q 都是解決問題的思維方式,都是程式碼組織的方式。
q 解決簡單問題可以使用程式導向
q 解決複雜問題:巨集觀上使用物件導向把握,微觀處理上仍然是程式導向。
一個好的“設計者”肯定也是“好的執行者”,不然無法落地,白忙一場。
一個好的“執行者”不一定是“好的設計者”,眼界層次不夠,越忙越亂。
物件的進化
隨著程式設計面臨的問題越來越複雜,程式語言本身也在進化,從主要處理簡單資料開始,隨著資料變多進化“陣列”; 資料型別變複雜,進化出了“結構體”; 處理資料的方式和邏輯變複雜,進化出了“物件”。
- 簡單資料
像 30,40,50.4 等這些數字,可以看做是簡單資料。最初的計算機程式設計,都是像這樣的數字。
- 陣列
將同型別的資料放到一起。比如:整數陣列[20,30,40],浮點數陣列[10.2, 11.3, 12.4],字串陣列:[“aa”,”bb”,”cc”]
- 結構體
將不同型別的資料放到一起,是 C 語言中的資料結構。比如:
struct resume{ int age;
char name[10];
double salary;
};
- 物件
將不同型別的資料、方法(即函式)放到一起,就是物件。比如:
class Student: company = "SXT" #類屬性 count = 0 #類屬性 def __init__(self,name,score): self.name = name #例項屬性 self.score = score Student.count = Student.count+1 def say_score(self): #例項方法 print("我的公司是:",Student.company) print(self.name,'的分數是:',self.score) |
我們前面學習的數字也是物件。比如:整數 9,就是一個包含了加法、乘法等方法的物件。類的定義我們把物件比作一個“餅乾”,類就是製造這個餅乾的“模具”。
我們通過類定義資料型別的屬性(資料)和方法(行為),也就是說,“類將行為和狀態打包在一起”。
物件是類的具體實體,一般稱為“類的例項”。類看做“餅乾模具”,物件就是根據這個“模具”製造出的“餅乾”。
從一個類建立物件時,每個物件會共享這個類的行為(類中定義的方法),但會有自己的屬性值(不共享狀態)。更具體一點:“方法程式碼是共享的,屬性資料不共享”。
Python 中,“一切皆物件”。類也稱為“類物件”,類的例項也稱為“例項物件”。
定義類的語法格式如下: class 類名:類體
要點如下:
- 類名必須符合“識別符號”的規則;一般規定,首字母大寫,多個單詞使用“駝峰原則”。
- 類體中我們可以定義屬性和方法。
- 屬性用來描述資料,方法(即函式)用來描述這些資料相關的操作。
【操作】一個典型的類的定義
class Student: def __init__(self,name,score): #構造方法第一個引數必須為 self self.name = name #例項屬性 self.score = score def say_score(self): #例項方法 print(self.name,'的分數是:',self.score) | |
s1 = Student('張三',80) s1.say_score() | #s1 是例項物件,自動呼叫__init__()方法 |
__init__構造方法和__new__方法
類是抽象的,也稱之為“物件的模板”。我們需要通過類這個模板,建立類的例項物件,然後才能使用類定義的功能。
我們前面說過一個 Python 物件包含三個部分:id(identity 識別碼)、type(物件型別)、 value(物件的值)。
現在,我們可以更進一步的說,一個 Python 物件包含如下部分:
- id(identity 識別碼)
- type(物件型別)
- value(物件的值)
(1) 屬性(attribute) (2) 方法(method)
建立物件,我們需要定義建構函式__init__()方法。構造方法用於執行“例項物件的初始化工作”,即物件建立後,初始化當前物件的相關屬性,無返回值。
__init__()的要點如下:
- 名稱固定,必須為:__init__()
- 第一個引數固定,必須為:self。 self 指的就是剛剛建立好的例項物件。
- 建構函式通常用來初始化例項物件的例項屬性,如下程式碼就是初始化例項屬性:name 和 score。
def __init__(self,name,score): self.name = name self.score = score | #例項屬性 |
- 通過“類名(引數列表)”來呼叫建構函式。呼叫後,將建立好的物件返回給相應的變數。
比如:s1 = Student('張三', 80)
- __init__()方法:初始化建立好的物件,初始化指的是:“給例項屬性賦值”
- __new__()方法: 用於建立物件,但我們一般無需重定義該方法。
- 如果我們不定義__init__方法,系統會提供一個預設的__init__方法。如果我們定義了帶參的__init__方法,系統不建立預設的__init__方法。
注:
1. Python 中的 self 相當於 C++中的 self 指標,JAVA 和 C#中的 this 關鍵字。Python 中, self 必須為建構函式的第一個引數,名字可以任意修改。但一般遵守慣例,都叫做 self。
例項屬性和例項方法
例項屬性
例項屬性是從屬於例項物件的屬性,也稱為“例項變數”。他的使用有如下幾個要點:
- 例項屬性一般在__init__()方法中通過如下程式碼定義: self.例項屬性名 = 初始值
- 在本類的其他例項方法中,也是通過 self 進行訪問: self.例項屬性名
- 建立例項物件後,通過例項物件訪問:
obj01 = 類名() #建立物件,呼叫__init__()初始化屬性
obj01.例項屬性名 = 值 #可以給已有屬性賦值,也可以新加屬性例項方法
例項方法是從屬於例項物件的方法。例項方法的定義格式如下: def 方法名(self [, 形參列表]):函式體
方法的呼叫格式如下:
物件.方法名([實參列表])
要點:
- 定義例項方法時,第一個引數必須為 self。和前面一樣,self 指當前的例項物件。
- 呼叫例項方法時,不需要也不能給 self 傳參。self 由直譯器自動傳參。
· 函式和方法的區別
- 都是用來完成一個功能的語句塊,本質一樣。
- 方法呼叫時,通過物件來呼叫。方法從屬於特定例項物件,普通函式沒有這個特點。
- 直觀上看,方法定義時需要傳遞 self,函式不需要。
· 例項物件的方法呼叫本質:
- dir(obj)可以獲得物件的所有屬性、方法
- obj.__dict__ 物件的屬性字典
- pass 空語句
- isinstance(物件,型別) 判斷“物件”是不是“指定型別” 類物件、類屬性、類方法、靜態方法
類物件
我們在前面講的類定義格式中,“class 類名:”。實際上,當直譯器執行 class 語句時,就會建立一個類物件。
【操作】測試類物件的生成
class Student: pass #空語句 print(type(Student)) print(id(Student)) |
Stu2 = Student s1 = Stu2() print(s1)
執行結果如下:
<class 'type'>
51686328
<__main__.Student object at 0x0000000002B5FDD8>
我們可以看到實際上生成了一個變數名就是類名“Student”的物件。我們通過賦值給新變數 Stu2,也能實現相關的呼叫。說明,確實建立了“類物件”。
【注】pass 為空語句。就是表示什麼都不做,只是作為一個佔位符存在。當你寫程式碼時,遇到暫時不知道往方法或者類中加入什麼時,可以先用 pass 佔位,後期再補上。
類屬性
類屬性是從屬於“類物件”的屬性,也稱為“類變數”。由於,類屬性從屬於類物件,可以被所有例項物件共享。
類屬性的定義方式:
class 類名:
類變數名= 初始值
在類中或者類的外面,我們可以通過:“類名.類變數名”來讀寫。
【操作】 類屬性的使用測試
class Student: company = "SXT" #類屬性 count = 0 #類屬性 def __init__(self,name,score): self.name = name #例項屬性 self.score = score |
Student.count = Student.count+1 def say_score(self): #例項方法 print("我的公司是:",Student.company) print(self.name,'的分數是:',self.score) s1 = Student('張三',80) #s1 是例項物件,自動呼叫__init__()方法 s1.say_score() print('一共建立{0}個 Student 物件'.format(Student.count)) |
執行結果:我的公司是: SXT
張三 的分數是: 80
一共建立 1 個 Student 物件
類方法
類方法是從屬於“類物件”的方法。類方法通過裝飾器@classmethod 來定義,格式如下:
@classmethod
def 類方法名(cls [,形參列表]) :函式體要點如下:
- @classmethod 必須位於方法上面一行
- 第一個 cls 必須有;cls 指的就是“類物件”本身;
- 呼叫類方法格式:“類名.類方法名(引數列表)”。 引數列表中,不需要也不能給 cls 傳值。
- 類方法中訪問例項屬性和例項方法會導致錯誤
- 子類繼承父類方法時,傳入 cls 是子類物件,而非父類物件
【操作】類方法使用測試
class Student: company = "SXT" @classmethod | #類屬性 |
def printCompany(cls): print(cls.company) Student.printCompany() |
靜態方法
Python 中允許定義與“類物件”無關的方法,稱為“靜態方法”。
“靜態方法”和在模組中定義普通函式沒有區別,只不過“靜態方法”放到了“類的名字空間裡面”,需要通過“類呼叫”。
靜態方法通過裝飾器@staticmethod 來定義,格式如下:
@staticmethod
def 靜態方法名([形參列表]) :函式體要點如下:
- @staticmethod 必須位於方法上面一行
- 呼叫靜態方法格式:“類名.靜態方法名(引數列表)”。
- 靜態方法中訪問例項屬性和例項方法會導致錯誤
【操作】靜態方法使用測試
class Student: company = "SXT" #類屬性 @staticmethod def add(a, b): #靜態方法 print("{0}+{1}={2}".format(a,b,(a+b))) return a+b Student.add(20,30) |
記憶體分析例項物件和類物件建立過程(重要)我們以下面程式碼為例,分析整個建立過程,讓大家對物件導向概念掌握更加深刻:
class Student: company = "尚學堂" #類屬性 count = 0 #類屬性 def __init__(self,name,score): self.name = name #例項屬性 self.score = score Student.count = Student.count+1 def say_score(self): #例項方法 print("我的公司是:",Student.company) print(self.name,'的分數是:',self.score) s1 = Student('高淇',80) #s1 是例項物件,自動呼叫__init__()方法 s1.say_score() print('一共建立{0}個 Student 物件'.format(Student.count)) |
__del__方法(解構函式)和垃圾回收機制
__del__方法稱為“析構方法”,用於實現物件被銷燬時所需的操作。比如:釋放物件佔用的資源,例如:開啟的檔案資源、網路連線等。
Python 實現自動的垃圾回收,當物件沒有被引用時(引用計數為 0),由垃圾回收器呼叫__del__方法。
我們也可以通過 del 語句刪除物件,從而保證呼叫__del__方法。
系統會自動提供__del__方法,一般不需要自定義析構方法。
#解構函式 class Person: def __del__(self): print("銷燬物件:{0}".format(self)) p1 = Person() p2 = Person() del p2 print("程式結束") |
運算結果:
銷燬物件:<__main__.Person object at 0x02175610> 程式結束
銷燬物件:<__main__.Person object at 0x021755D0>
__call__方法和可呼叫物件
定義了__call__方法的物件,稱為“可呼叫物件”,即該物件可以像函式一樣被呼叫。
#測試__call__,可呼叫物件
class SalaryAccount: '''工資計算類''' def __call__(self, salary): yearSalary = salary*12 daySalary = salary//30 hourSalary = daySalary//8 return dict(monthSalary=salary,yearSalary=yearSalary,daySalary=daySalary ,hourSalary=hourSalary) s = SalaryAccount() print(s(5000)) #可以像呼叫函式一樣呼叫物件的__call__方法 |
執行結果:
{'monthSalary': 5000, 'yearSalary': 60000, 'daySalary': 166, 'hourSalary': 20}
方法沒有過載
在其他語言中,可以定義多個重名的方法,只要保證方法簽名唯一即可。方法簽名包含 3 個部分:方法名、引數數量、引數型別。
Python 中,方法的的引數沒有宣告型別(呼叫時確定引數的型別),引數的數量也可以由可變引數控制。因此,Python 中是沒有方法的過載的。定義一個方法即可有多種呼叫方式,相當於實現了其他語言中的方法的過載。
如果我們在類體中定義了多個重名的方法,只有最後一個方法有效。
建議:不要使用重名的方法!Python 中方法沒有過載。
#Python中沒有方法的過載。定義多個同名方法,只有最後一個有效
class Person: def say_hi(self): print("hello") def say_hi(self,name): print("{0},hello".format(name)) p1 = Person() #p1.say_hi() #不帶參,報錯:TypeError:say_hi()missing1 requiredpositionalargument:'name' p1.say_hi("高淇") |
方法的動態性
Python 是動態語言,我們可以動態的為類新增新的方法,或者動態的修改類的已有的方法。
#測試方法的動態性 class Person: def work(self): print("努力上班!") def play_game(self): print("{0}玩遊戲".format(self)) def work2(s): print("好好工作,努力上班!") |
Person.play = play_game Person.work = work2 p = Person() p.play() p.work()
我們可以看到,Person 動態的新增了 play_game 方法,以及用 work2 替換了 work 方法。
私有屬性和私有方法(實現封裝)
Python 對於類的成員沒有嚴格的訪問控制限制,這與其他面嚮物件語言有區別。關於私有屬性和私有方法,有如下要點:
- 通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public)。
- 類內部可以訪問私有屬性(方法)
- 類外部不能直接訪問私有屬性(方法)
- 類外部可以通過“_類名__私有屬性(方法)名”訪問私有屬性(方法)
【注】方法本質上也是屬性!只不過是可以通過()執行而已。所以,此處講的私有屬性和公有屬性,也同時講解了私有方法和公有方法的用法。如下測試中,同時也包含了私有方法和公有方法的例子。
【測試】私有屬性和公有屬性使用測試
#測試私有屬性、私有方法 class Employee: __company = "百戰程式設計師" | #私有類屬性. 通過dir可以查到 | |
_Employee__company def __init__(self,name,age): self.name = name self.__age = age | #私有例項屬性 |
|
def say_company(self): print("我的公司是:",Employee.__company) #類內部 可以直接訪問私有屬性 print(self.name,"的年齡是:",self.__age) self.__work() def __work(self): #私有例項方法 通過dir可以查到 _Employee__work print("工作!好好工作,好好賺錢,娶個媳婦!") p1 = Employee("高淇",32) print(p1.name) print(dir(p1)) # p1.say_company() print(p1._Employee__age) #通過這種方式可以直接訪問到私有屬 性 。通過dir可以查到屬性:_Employee__age #print(p1.__age) #直接訪問私有屬性,報錯 #p1.__sleep() #直接訪問私有方法,報錯 |
執行結果:
高淇
['_Person__age', '_Person__leg_num', '_Person__sleep', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'name', 'say_age']
腿的數目: 2 高淇 的年齡是: 18
睡覺
18
從列印的 Person 物件所有屬性我們可以看出。私有屬性“__age”在實際儲存時是按照 “_Person__age”這個屬性來儲存的。這也就是為什麼我們不能直接使用“__age”而可以使用“_Person__age”的根本原因。
@property 裝飾器
@property 可以將一個方法的呼叫方式變成“屬性呼叫”。下面是一個簡單的示例,讓大家體會一下這種轉變:
#簡單測試@property class Employee: @property def salary(self): return 30000; emp1 = Employee() print(emp1.salary) #列印30000 print(type(emp1.salary)) #列印<class'int'> #emp1.salary() #報錯:TypeError:'int'objectisnot callable #emp1.salary=1000 #@property修飾的屬性,如果沒有 加setter方法,則為只讀屬性。此處修改報錯:AttributeError:can'tset attribute |
@property 主要用於幫助我們處理屬性的讀操作、寫操作。對於某一個屬性,我們可以直接通過:
emp1.salary = 30000
如上的操作讀操作、寫操作。但是,這種做法不安全。比如,我需要限制薪水必須為 1-10000 的數字。這時候,我們就需要通過 getter、setter 方法來處理。
#測試@property class Employee:
def __init__(self,name,salary): self.name = name self.__salary = salary @property #相當於salary屬性的getter方法 def salary(self): print("月薪為{0},年薪為 {1}".format(self.__salary,(12*self.__salary))) return self.__salary; @salary.setter def salary(self,salary): #相當於salary屬性的setter方法 if(0<salary<1000000): self.__salary = salary else: print("薪水錄入錯誤!只能在 0-1000000 之間") emp1 = Employee("高淇",100) print(emp1.salary) emp1.salary = -200 |
執行結果:
月薪為 100,年薪為 1200
100
月薪為 100,年薪為 1200
100
薪水錄入錯誤!只能在 0-1000000 之間
屬性和方法命名總結
· _xxx:保護成員,不能用“from module import * ”匯入,只有類物件和子類物件能訪問這些成員。
· __xxx__:系統定義的特殊成員
· __xxx: 類中的私有成員,只有類物件自己能訪問,子類物件也不能訪問。(但,在類外部可以通過“物件名. _類名__xxx”這種特殊方式訪問。Python 不存在嚴格意義的私有成員)
注:再次強調,方法和屬性都遵循上面的規則。
類編碼風格
- 類名首字母大寫,多個單詞之間採用駝峰原則。
- 例項名、模組名採用小寫,多個單詞之間採用下劃線隔開。
- 每個類,應緊跟“文件字串”,說明這個類的作用。
- 可以用空行組織程式碼,但不能濫用。在類中,使用一個空行隔開方法;模組中,使用兩個空行隔開多個類。
實操作業
- 如下程式碼,使用圖文分析整個記憶體過程:
class Student: company = "尚學堂" #類屬性 count = 0 #類屬性 def __init__(self,name,score): self.name = name #例項屬性 self.score = score Student.count = Student.count+1 def say_score(self): #例項方法 print("我的公司是:",Student.company) print(self.name,'的分數是:',self.score) |
s1 = Student('高淇',80) #s1 是例項物件,自動呼叫__init__()方法 s1.say_score()
print('一共建立{0}個 Student 物件'.format(Student.count))
- 設計一個名為 MyRectangle 的矩形類來表示矩形。這個類包含:
(1) 左上角頂點的座標:x,y
(2) 寬度和高度:width、height
(3) 構造方法:傳入 x,y,width,height。如果(x,y)不傳則預設是 0,如果 width 和 height 不傳,則預設是 100.
(4) 定義一個 getArea() 計算面積的方法
(5) 定義一個 getPerimeter(),計算周長的方法
(6) 定義一個 draw()方法,使用海龜繪圖繪製出這個矩形
第6章(2) 物件導向進階
物件導向三大特徵介紹
Python 是物件導向的語言,也支援物件導向程式設計的三大特性:繼承、封裝(隱藏)、多型。
·封裝(隱藏)
隱藏物件的屬性和實現細節,只對外提供必要的方法。相當於將“細節封裝起來”,只對外暴露“相關呼叫方法”。
通過前面學習的“私有屬性、私有方法”的方式,實現“封裝”。Python 追求簡潔的語法,沒有嚴格的語法級別的“訪問控制符”,更多的是依靠程式設計師自覺實現。
·繼承
繼承可以讓子類具有父類的特性,提高了程式碼的重用性。
從設計上是一種增量進化,原有父類設計不變的情況下,可以增加新的功能,或者改進已有的演算法。
·多型
多型是指同一個方法呼叫由於物件不同會產生不同的行為。生活中這樣的例子比比皆是:同樣是休息方法,人不同休息方法不同。張三休息是睡覺,李四休息是玩遊戲,程式設計師休息是“敲幾行程式碼”。
繼承
繼承是物件導向程式設計的重要特徵,也是實現“程式碼複用”的重要手段。
如果一個新類繼承自一個設計好的類,就直接具備了已有類的特徵,就大大降低了工作難度。已有的類,我們稱為“父類或者基類”,新的類,我們稱為“子類或者派生類”。
語法格式
Python 支援多重繼承,一個子類可以繼承多個父類。繼承的語法格式如下: class 子類類名(父類 1[,父類 2,...]):類體
如果在類定義中沒有指定父類,則預設父類是 object 類。也就是說,object 是所有類的父類,裡面定義了一些所有類共有的預設實現,比如:__new__()。
定義子類時,必須在其建構函式中呼叫父類的建構函式。呼叫格式如下:父類名.__init__(self, 引數列表)
class Person: def __init__(self,name,age): self.name = name |
self.__age = age def say_age(self): print(self.name,"的年齡是:",self.__age) class Student(Person): def __init__(self,name,age,score): self.score = score Person.__init__(self,name,age) #建構函式中包含呼叫父類構 造函式。根據需要,不是必須。子類並不會自動呼叫父類的__init__(),我們必須顯式的呼叫它。 s1 = Student("張三",15,85) s1.say_age() print(dir(s1)) |
執行結果:
張三 的年齡是: 15
['_Person__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'name', 'say_age', 'score']
類成員的繼承和重寫
- 成員繼承:子類繼承了父類除構造方法之外的所有成員。
- 方法重寫:子類可以重新定義父類中的方法,這樣就會覆蓋父類的方法,也稱為“重寫”
【操作】繼承和重寫的案例
class Person: def __init__(self,name,age): self.name = name self.age = age def say_age(self): print(self.name,"的年齡是:",self.age) def say_name(self): print("我是",self.name) class Student(Person): def __init__(self,name,age,score): self.score = score Person.__init__(self,name,age) #建構函式中包含呼叫父類建構函式 def say_score(self): print(self.name,"的分數是:",self.score) def say_name(self): #重寫父類的方法 print("報告老師,我是",self.name) s1 = Student("張三",15,85) s1.say_score() s1.say_name() s1.say_age() |
執行結果:張三 的分數是: 85 報告老師,我是 張三張三 的年齡是: 15
檢視類的繼承層次結構通過類的方法 mro()或者類的屬性__mro__可以輸出這個類的繼承層次結構。【操作】 檢視類的繼承層次結構
class A:pass class B(A):pass class C(B):pass print(C.mro())
執行結果:
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
object 根類
object 類是所有類的父類,因此所有的類都有 object 類的屬性和方法。我們顯然有必要深入研究一下 object 類的結構。對於我們繼續深入學習 Python 很有好處。
dir()檢視物件屬性
為了深入學習物件,我們先學習內建函式 dir(),他可以讓我們方便的看到指定物件所有的屬性。
【測試】檢視物件所有屬性以及和 object 進行比對
class Person:
def __init__(self,name,age): self.name = name self.age = age def say_age(self): print(self.name,"的年齡是:",self.age) obj = object() print(dir(obj)) s2 = Person("高淇",18) print(dir(s2)) |
執行結果:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', | |
'__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', | |
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', | |
'__str__', '__subclasshook__'] |
|
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_age']
從上面我們可以發現這樣幾個要點:
- Person 物件增加了六個屬性:
__dict__ __module__ __weakref__ age name say_age
- object 的所有屬性,Person 類作為 object 的子類,顯然包含了所有的屬性。
- 我們列印 age、name、say_age,發現 say_age 雖然是方法,實際上也是屬性。只不過,這個屬性的型別是“method”而已。 age <class 'int'> name <class 'str'> say_age <class 'method'>
【注】關於 object 這些屬性的詳細學習,會在後面學習中逐個涉及。在此,無法一一展開。
重寫__str__()方法
object 有一個__str__()方法,用於返回一個對於“物件的描述”,對應於內建函式 str() 經常用於 print()方法,幫助我們檢視物件的資訊。__str__()可以重寫。
class Person: def __init__(self,name,age): self.name = name self.__age = age def __str__(self): '''將物件轉化成一個字串,一般用於print方法''' return "名字是:{0},年齡是{1}".format(self.name,self.__age) p = Person("高淇",18) print(p) |
執行結果:名字是:高淇,年齡是 18
多重繼承
Python 支援多重繼承,一個子類可以有多個“直接父類”。這樣,就具備了“多個父類”的特點。但是由於,這樣會被“類的整體層次”搞的異常複雜,儘量避免使用。
#多重繼承 class A: def aa(self): print("aa") class B: def bb(self): print("bb") class C(B,A): def cc(self): print("cc") c = C() c.cc() c.bb() c.aa() |
運算結果: cc bb aa
MRO()
Python 支援多繼承,如果父類中有相同名字的方法,在子類沒有指定父類名時,直譯器將
“從左向右”按順序搜尋。
MRO(Method Resolution Order):方法解析順序。 我們可以通過 mro()方法獲得
“類的層次結構”,方法解析順序也是按照這個“類的層次結構”尋找的。
#多重繼承 class A: def aa(self): print("aa") def say(self): print("say AAA!") class B: def bb(self): print("bb") def say(self): print("say BBB!") class C(B,A): def cc(self): print("cc") c = C() print(C.mro()) #列印類的層次結構 c.say() #直譯器尋找方法是“從左到右”的方式尋找,此時會執行B 類中的say() |
super()獲得父類定義在子類中,如果想要獲得父類的方法時,我們可以通過 super()來做。 super()代表父類的定義,不是父類物件。
#super() class A: def say(self): print("A: ",self) print("say AAA") class B(A): def say(self): #A.say(self) 呼叫父類的say方法 super().say() #通過super()呼叫父類的方法 print("say BBB") b = B() b.say() |
執行結果:
A: <__main__.B object at 0x007A5690> say AAA say BBB 多型
多型(polymorphism)是指同一個方法呼叫由於物件不同可能會產生不同的行為。在現實生活中,我們有很多例子。比如:同樣是呼叫人的休息方法,張三的休息是睡覺,李四的休息是玩遊戲,高淇老師是敲程式碼。同樣是吃飯的方法,中國人用筷子吃飯,英國人用刀叉吃飯,印度人用手吃飯。
關於多型要注意以下 2 點:
- 多型是方法的多型,屬性沒有多型。
- 多型的存在有 2 個必要條件:繼承、方法重寫。
#多型
class Animal: def shout(self): print("動物叫了一聲") class Dog(Animal): def shout(self): print("小狗,汪汪汪") class Cat(Animal): def shout(self): print("小貓,喵喵喵") def animalShout(a): if isinstance(a,Animal): a.shout() #傳入的物件不同,shout方法對應的實際行為也不同。 animalShout(Dog()) animalShout(Cat()) |
執行結果:小狗,汪汪汪小貓,喵喵喵
特殊方法和運算子過載
Python 的運算子實際上是通過呼叫物件的特殊方法實現的。比如:
a = 20 b = 30 c = a+b d = a.__add__(b) print("c=",c) print("d=",d) |
運算結果: c= 50 d= 50
常見的特殊方法統計如下:
方法 | 說明 | 例子 |
__init__ | 構造方法 | 物件建立:p = Person() |
__del__ | 析構方法 | 物件回收 |
__repr__,__str__ | 列印,轉換 | print(a) |
__call__ | 函式呼叫 | a() |
__getattr__ | 點號運算 | a.xxx |
__setattr__ | 屬性賦值 | a.xxx = value |
__getitem__ | 索引運算 | a[key] |
__setitem__ | 索引賦值 | a[key]=value |
__len__ | 長度 | len(a) |
每個運算子實際上都對應了相應的方法,統計如下:
運算子 | 特殊方法 | 說明 |
運算子+ | __add__ | 加法 |
運算子- | __sub__ | 減法 |
<,<=,== | __lt__,__le__,__eq__ | 比較運算子 |
>,>=,!= | __gt__,__ge__,__ne__ | |
|,^,& | __or__,__xor__,__and__ | 或、異或、與 |
<<,>> | __lshift__,__rshift__ | 左移、右移 |
*,/,%,// | __mul__,__truediv__,__mod__,_ _floordiv__ | 乘、浮點除、模運算 (取餘)、整數除 |
** | __pow__ | 指數運算 |
我們可以重寫上面的特殊方法,即實現了“運算子的過載”。
#測試運算子的過載 class Person: def __init__(self,name): self.name = name def __add__(self, other): if isinstance(other,Person): return "{0}--{1}".format(self.name,other.name) else: return "不是同類物件,不能相加" def __mul__(self, other): if isinstance(other,int): return self.name*other else: return "不是同類物件,不能相乘" p1 = Person("高淇") p2 = Person("高希希") x = p1 + p2 print(x) print(p1*3) |
運算結果:
高淇--高希希高淇高淇高淇
特殊屬性
Python 物件中包含了很多雙下劃線開始和結束的屬性,這些是特殊屬性,有特殊用法。這裡我們列出常見的特殊屬性:
| 特殊方法 | 含義 |
|
| obj.__dict__ | 物件的屬性字典 |
|
| obj.__class__ | 物件所屬的類 |
|
| class.__bases__ | 類的基類元組(多繼承) |
|
| class.__base__ | 類的基類 |
|
| class.__mro__ | 類層次結構 |
|
| class.__subclasses__() | 子類列表 |
|
#測試特殊屬性 class A: pass class B: pass class C(B,A): def __init__(self,nn): self.nn = nn def cc(self): print("cc") c = C(3) print(dir(c)) print(c.__dict__) print(c.__class__) print(C.__bases__) print(C.mro()) print(A.__subclasses__()) | |||
執行結果:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cc', 'nn']
{'nn': 3}
<class '__main__.C'>
(<class '__main__.B'>, <class '__main__.A'>)
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.C'>]
物件的淺拷貝和深拷貝
·變數的賦值操作只是形成兩個變數,實際還是指向同一個物件。 ·淺拷貝
Python 拷貝一般都是淺拷貝。拷貝時,物件包含的子物件內容不拷貝。因此,源物件和拷貝物件會引用同一個子物件。
·深拷貝使用 copy 模組的 deepcopy 函式,遞迴拷貝物件中包含的子物件。源物件和拷貝物件所有的子物件也不同。
#測試物件的引用賦值、淺拷貝、深拷貝 import copy class MobilePhone: def __init__(self,cpu,screen): self.cpu = cpu self.screen = screen class CPU: |
def calculate(self): print("計算,算個 12345") print("CPU 物件:",self) class Screen: def show(self): print("顯示一個好看的畫面,亮瞎你的鈦合金大眼") print("螢幕物件:",self) c = CPU() s = Screen() m = MobilePhone(c,s) m.cpu.calculate() n = m #兩個變數,但是指向了同一個物件 print(m,n) m2 = copy.copy(m) #m2是新拷貝的另一個手機物件 print(m,m2) m.cpu.calculate() m2.cpu.calculate() #m2和m擁有了一樣的cpu物件和screen物件 m3 = copy.deepcopy(m) m3.cpu.calculate() #m3和m擁有不一樣的cpu物件和screen 物件 |
運算結果:
計算,算個 12345
CPU 物件: <__main__.CPU object at 0x00685690>
<__main__.MobilePhone object at 0x00685B50> <__main__.MobilePhone object at 0x00685B50>
<__main__.MobilePhone object at 0x00685B50> <__main__.MobilePhone object at
0x0069B490>
計算,算個 12345
CPU 物件: <__main__.CPU object at 0x00685690> 計算,算個 12345
CPU 物件: <__main__.CPU object at 0x00685690> 計算,算個 12345
CPU 物件: <__main__.CPU object at 0x006A5DB0>
組合
“is-a”關係,我們可以使用“繼承”。從而實現子類擁有的父類的方法和屬性。“is-a” 關係指的是類似這樣的關係:狗是動物,dog is animal。狗類就應該繼承動物類。
“has-a”關係,我們可以使用“組合”,也能實現一個類擁有另一個類的方法和屬性。” has-a”關係指的是這樣的關係:手機擁有 CPU。 MobilePhone has a CPU。
#組合測試 class MobilePhone: def __init__(self,cpu,screen): self.cpu = cpu self.screen = screen class CPU: def calculate(self): print("計算,算個 12345") class Screen: def show(self): print("顯示一個好看的畫面,亮瞎你的鈦合金大眼") |
c = CPU() s = Screen() m = MobilePhone(c,s) m.cpu.calculate() #通過組合,我們也能呼叫cpu物件的方法。相 當於手機物件間接擁有了“cpu的方法” m.screen.show() |
運算結果:
計算,算個 12345
顯示一個好看的畫面,亮瞎你的鈦合金大眼設計模式_工廠模式實現
設計模式是面嚮物件語言特有的內容,是我們在面臨某一類問題時候固定的做法,設計模式有很多種,比較流行的是:GOF(Goup Of Four)23 種設計模式。當然,我們沒有必要全部學習,學習幾個常用的即可。
對於初學者,我們學習兩個最常用的模式:工廠模式和單例模式。
工廠模式實現了建立者和呼叫者的分離,使用專門的工廠類將選擇實現類、建立物件進行統一的管理和控制。
#工廠模式 class CarFactory: def createCar(self,brand): if brand == "賓士": return Benz() elif brand == "寶馬": return BMW() elif brand == '比亞迪': return BYD() else: return "未知品牌,無法建立" class Benz: |
pass class BMW: pass class BYD: pass factory = CarFactory() c1 = factory.createCar("賓士") c2 = factory.createCar("寶馬") print(c1) print(c2) |
執行結果:
<__main__.Benz object at 0x021C5770>
<__main__.BMW object at 0x021C5790> 設計模式_單例模式實現
單例模式(Singleton Pattern)的核心作用是確保一個類只有一個例項,並且提供一個訪問該例項的全域性訪問點。
單例模式只生成一個例項物件,減少了對系統資源的開銷。當一個物件的產生需要比較多的資源,如讀取配置檔案、產生其他依賴物件時,可以產生一個“單例物件”,然後永久駐留記憶體中,從而極大的降低開銷。
單例模式有多種實現的方式,我們這裡推薦重寫__new__()的方法。
#單例模式
class MySingleton:
__obj = None __init_flag = True def __new__(cls, *args, **kwargs): if cls.__obj == None: cls.__obj = object.__new__(cls) return cls.__obj def __init__(self,name): if MySingleton.__init_flag: print("init....") self.name = name MySingleton.__init_flag = False a = MySingleton("aa") print(a) b = MySingleton("bb") print(b) |
運算結果:
init....
<__main__.MySingleton object at 0x01E15610>
<__main__.MySingleton object at 0x01E15610>
設計模式稱之為“模式”,就是一些固定的套路。我們很容易用到其他場景上,比如前面講的工廠模式,我們需要將工廠類定義成“單例”,只需要簡單的套用即可實現:
#測試工廠模式和單例模式的整合使用 class CarFactory: __obj = None #類屬性 __init_flag = True def create_car(self,brand): if brand =="賓士": return Benz() elif brand =="寶馬": return BMW() elif brand == "比亞迪": return BYD() |
else: return "未知品牌,無法建立" def __new__(cls, *args, **kwargs): if cls.__obj ==None: cls.__obj = object.__new__(cls) return cls.__obj def __init__(self): if CarFactory.__init_flag: print("init CarFactory....") CarFactory.__init_flag = False class Benz: pass class BMW: pass class BYD: pass factory = CarFactory() c1 = factory.create_car("賓士") c2 = factory.create_car("比亞迪") print(c1) print(c2) factory2 = CarFactory() print(factory) print(factory2) |
運算結果: init CarFactory....
<__main__.Benz object at 0x01E36E90> <__main__.BYD object at 0x01E36C30>
<__main__.CarFactory object at 0x01E36730>
<__main__.CarFactory object at 0x01E36730>
實操作業
- 如下程式碼測試物件的淺拷貝、深拷貝,請繪製出記憶體示意圖。
#測試物件的引用賦值、淺拷貝、深拷貝 import copy class MobilePhone: def __init__(self,cpu,screen): self.cpu = cpu self.screen = screen class CPU: def calculate(self): print("計算,算個 12345") print("CPU 物件:",self) class Screen: def show(self): print("顯示一個好看的畫面,亮瞎你的鈦合金大眼") print("螢幕物件:",self) c = CPU() s = Screen() m = MobilePhone(c,s) m.cpu.calculate() |
n = m #兩個變數,但是指向了同一個物件
print(m,n)
m2 = copy.copy(m) #m2是新拷貝的另一個手機物件 print(m,m2)
m.cpu.calculate()
m2.cpu.calculate() #m2和m擁有了一樣的cpu物件和screen物件
m3 = copy.deepcopy(m)
m3.cpu.calculate() #m3和m擁有不一樣的cpu物件和screen 物件
- 定義發動機類 Motor、底盤類 Chassis、座椅類 Seat,車輛外殼類 Shell,並使用組合
關係定義汽車類。其他要求如下:定義汽車的 run()方法,裡面需要呼叫 Motor 類的 work()方法,也需要呼叫座椅
類 Seat 的 work()方法,也需要呼叫底盤類 Chassis 的 work()方法。
- 使用工廠模式、單例模式實現如下需求:
(1) 電腦工廠類 ComputerFactory 用於生產電腦 Computer。工廠類使用單例模式,也就是說只能有一個工廠物件。
(2) 工廠類中可以生產各種品牌的電腦:聯想、華碩、神舟
(3) 各種品牌的電腦使用繼承實現:
(4) 父類是 Computer 類,定義了 calculate 方法
(5) 各品牌電腦類需要重寫父類的 calculate 方法
- 定義一個 Employee 僱員類,要求如下:
(1) 屬性有:id、name、salary
(2) 運算子過載+:實現兩個物件相加時,預設返回他們的薪水和
(3) 構造方法要求:輸入 name、salary,不輸入 id。id 採用自增的方式,從 1000 開始自增,第一個新增物件是 1001,第二個新增物件是 1002。
(4) 根據 salary 屬性,使用@property 設定屬性的 get 和 set 方法。set 方法要求輸入:1000-50000 範圍的數字。
高淇Python400集視訊教程目錄
第一章 Python 入門
001. Python 介紹_特性_版本問題_應用範圍
002. Python 下載_安裝_配置_第一行 Python 程式
003. 開發環境介紹_互動模式的使用_IDLE 介紹和使用
004. IDLE 開發環境的使用_建立 Python 原始檔
005. Python 程式格式_縮排_行註釋_段註釋
006. 簡單錯誤如何處理_守破離學習法_程式設計師修煉手冊
007. 海龜繪圖_座標系問題_畫筆各種方法
008. 海龜繪圖_畫出奧運五環圖第二章 程式設計基本概念
- 程式的構成
- 物件的基本組成和記憶體示意圖
- 引用的本質_棧記憶體和堆記憶體_記憶體示意圖
- 識別符號_幫助系統的簡單使用_命名規則
- 變數的宣告_初始化_刪除變數_垃圾回收機制
- 鏈式賦值_系列解包賦值_常量
- 內建資料型別_基本算術運算子
- 整數_不同進位制_其他型別轉換成整數
- 浮點數_自動轉換_強制轉換_增強賦值運算子
- 時間表示_unix 時間點_毫秒和微秒_time 模組
- 多點座標_繪出折線圖_計算兩點距離
- 布林值_比較運算子_邏輯運算子_短路問題
- 同一運算子_整數快取問題
- 字串_unicode 字符集_三種建立字串方式_len()
- 字串_轉義字元_字串拼接_字串複製_input()獲得鍵盤輸入
- 字串_str()_使用[]提取字元_replace()替換生成新字串_記憶體分析
- 字串_切片 slice 操作_逆序
- 字串_split()分割_join()合併_join()效率測試
- 字串_駐留機制_記憶體分析_字串同一判斷_值相等判斷
- 字串_常用查詢方法_去除首位資訊_大小寫轉換_排版
- 字串_format 格式化_數字格式化操作
- 可變字串_io.StringIO
- 運算子總結_位操作符_優先順序問題第三章 序列
- 列表_特點_記憶體分析
- 建立列表的 4 種方式_推導式建立列表
- 列表_元素的 5 種新增方式_效率問題
- 列表_元素刪除的三種方式_刪除本質是陣列元素拷貝
- 列表_元素的訪問_元素出現次數統計_成員資格判斷
- 列表_切片 slice 操作
- 列表_排序_revered 逆序_max_min_sum
- 列表_二維列表_表格資料的儲存和讀取
- 元組_特點_建立的兩種方式_tuple()要點
- 元組_元素訪問_計數方法_切片操作_成員資格判斷_zip()
- 元組_生成器推導式建立元組_總結
- 字典_特點_4 種建立方式_普通_dict_zip_formkeys
- 字典_元素的訪問_鍵的訪問_值的訪問_鍵值對的訪問
- 字典_元素的新增_修改_刪除
- 字典_序列解包用於列表元組字典
- 字典_複雜表格資料儲存_列表和字典綜合巢狀
- 字典_核心底層原理_記憶體分析_儲存鍵值對過程
- 字典_核心底層原理_記憶體分析_查詢值物件過程
- 集合_特點_建立和刪除_交集並集差集運算
第四章 控制語句
- Pycharm 開發環境的下載安裝配置_專案建立和執行
- 單分支選擇結構_條件表示式詳解
- 雙分支選擇結構_三元運算子的使用詳解
- 多分支選擇結構
- 選擇結構的巢狀
- while 迴圈結構_死迴圈處理
- for 迴圈結構_遍歷各種可迭代物件_range 物件
- 巢狀迴圈
- 巢狀迴圈練習_九九乘法表_列印表格資料
- break 語句
- continue 語句
- else 語句
- 迴圈程式碼優化技巧(及其重要)
- zip()並行迭代
- 推導式建立序列_列表推導式_字典推導式_集合推導式_生成器推導式
- 綜合練習_繪製不同顏色的多個同心圓_繪製棋盤
第五章 函式
- 函式的基本概念_記憶體分析_函式的分類_定義和呼叫
- 形參和實參_文件字串_函式註釋
- 返回值詳解
- 函式也是物件_記憶體分析
- 變數的作用域_全域性變數_區域性變數_棧幀記憶體分析講解
- 區域性變數和全域性變數_效率測試
- 引數的傳遞_傳遞可變物件_記憶體分析
- 引數的傳遞_傳遞不可變物件_記憶體分析
- 淺拷貝和深拷貝_記憶體分析
10.引數的傳遞_不可變物件含可變子物件_記憶體分析
11.引數的型別_位置引數_預設值引數_命名引數
12.引數的型別_可變引數_強制命名引數
13.lambda 表示式和匿名函式
14.eval()函式用法
15.遞迴函式_函式呼叫記憶體分析_棧幀的建立
16.遞迴函式_階乘計算案例
17.巢狀函式_內部函式_資料隱藏
18.nonlocal_global
19. LEGB 規則
第六章 物件導向程式設計
- 物件導向和麵向過程的區別_執行者思維_設計者思維
- 物件的進化故事
- 類的定義_類和物件的關係
- 建構函式__init__
- 例項屬性_記憶體分析
- 例項方法_記憶體分析方法呼叫過程_dir()_isinstance
- 類物件
- 類屬性_記憶體分析建立類和物件的底層
- 類方法_靜態方法_記憶體分析圖示
- __del__()析構方法和垃圾回收機制
- __call__()方法和可呼叫物件
- 方法沒有過載_方法的動態性
- 私有屬性
- 私有方法
- @property 裝飾器_get 和 set 方法
- 物件導向的三大特徵說明(封裝、繼承、多型)
- 繼承
- 方法的重寫
- object 根類_dir()
- 重寫__str__()方法
- 多重繼承
- mro()
- super()獲得父類的定義
- 多型
- 特殊方法和運算子過載
- 特殊屬性
- 物件的淺拷貝和深拷貝_記憶體分析
- 組合
- 設計模式_工廠模式實現
- 設計模式_單例模式實現
第七章 模組
- 模組化程式設計理念_模組和包
- 標準庫有哪些
- 模組的設計和實現
- API 的設計
- 模組的匯入和使用
- 包的使用_匯入和建立
- 重新載入模組_動態代入模組
- 模組的匯入順序
- 名稱空間和名稱查詢順序
- 第三方擴充套件庫的介紹和安裝
- PyPy
- PiP 安裝第三方擴充套件庫
第八章 檔案操作(IO)
- 文字檔案和二進位制檔案
- 檔案操作相關模組介紹
- 建立檔案物件_文字檔案寫入
- with 語句_上下文管理
- 文字檔案的讀取
- 二進位制檔案的讀取和寫入
- 使用 pickle 序列化
- CSV 檔案的操作_csv 檔案讀取
- CSV 檔案的操作_csv 檔案寫入
- os 和 os.path 模組_檔案級操作
- os 和 os.path 模組_目錄級操作
- 儲存資料_json.dump()和 json.load()
- 練習 1
- 練習 2
- 練習 3
第九章 異常和錯誤
- 異常處理結構_try_except
- try_finally
- raise 丟擲異常
- 自定義異常
- 常見異常的解決
- with 上下文管理器
- 程式除錯
- 使用 pdb 模組除錯程式
- IDLE 的除錯 10. Pycharm 開發環境的除錯
第十章 數值日期和複雜文字處理
- math 模組
- random 模組_隨機整數_隨機序列
- 數值運算模組 NumPy_陣列
- 數值運算模組 NumPy_應用
- 時間表示原理_時間物件
- 日期物件 7. 字串和日期物件互相轉換
第 11 章 GUI 程式設計
- Tkinter 介紹
- 第一個 GUI 程式
- 事件處理
- 控制元件_Button 按鈕
- 控制元件_Canvas 畫布
- 控制元件_Chekbutton_Radiobutton
- 控制元件_Entry 文字輸入框
- 控制元件_Frame 容器
- 控制元件_Label_Message
- 控制元件_選單製作_Menu_Menubutton
- 對話方塊_通用訊息對話方塊
- 對話方塊_檔案對話方塊
- 對話方塊_顏色選擇對話方塊
- 幾何管理器_網路管理器
- 幾何管理器_包管理器
- 幾何管理器_位置管理器
- 鍵盤事件處理
- 滑鼠事件處理
- 動畫的實現
- 【專案】_房貸計算器_1
- 【專案】_房貸計算器_2
- 【專案】_房貸計算器_3
- 【專案】_房貸計算器_4
- 【專案】_房貸計算器_4
第 12 章 【專案】坦克大戰遊戲開發
- pygame 使用介紹_簡單遊戲程式設計
- pygame 事件處理_圖形影像處理
- pygame 精靈_碰撞檢測
- 坦克大戰物件導向分析
- 坦克大戰之載入主頁面
- 坦克大戰之-新增左上角提示文字
- 坦克大戰之新增事件監聽
- 坦克大戰之載入我方坦克_
- 坦克大戰之實現我方坦克的移動
- 坦克大戰之優化坦克移動方式
- 坦克大戰之新增隨機生成敵方坦克
- 坦克大戰之實現敵方坦克的隨機移動
- 坦克大戰之我方坦克發射子彈
- 坦克大戰之子彈的優化處理
- 新增敵方坦克隨機發射子彈?
- 敵方子彈與我方法坦克的碰撞以及我方坦克重生
- 坦克大戰之敵方坦克的碰撞檢測
- 坦克大戰之爆炸效果的實現
- 爆炸效果補充
- 基於 turtle 的畫筆繪圖_矩形_多邊形_圓形
- 基於 turtle 的畫筆繪圖_遞迴圖形
- 基於 turtle 的畫筆繪圖_實操練習
- 基於 tkinter 圖形繪製_矩形_線條_字串
- 基於 tkinter 圖形繪製_繪製函式圖形
- Matplotlib_安裝
- Matplotlib_簡單圖形繪製
- Matplotlib_函式曲線
- Matplotlib_多個圖形
- Matplotlib_繪製直方圖第 14 章 資料庫程式設計
- 內建資料庫 SQLite
- 操作 SQLite 資料庫_Connection_Cursor_Row
- Mysql 資料庫的安裝和配置
- Mysql 視覺化客戶端軟體安裝
- PyMysql 的使用
- 對於資料庫表的增刪改查操作 1
- 對於資料庫表的增刪改查操作 2
- 對於資料庫表的增刪改查操作 3
第 15 章 多執行緒和併發程式設計
13:生產者消費者模式 14:threadlocal 變數第 16 章 網路程式設計
14. 聊天室專案_1
15_聊天室專案_2
16_聊天室專案_3
- 聊天室專案_4
- 聊天室專案_5
第 17 章 協程和非同步 IO
- 協程的概念
- 定義一個協程
- 建立 task 執行協程_繫結回撥
- future 與 result
- 阻塞和 await
- 併發和並行
- 協程巢狀
- 協程停止
- master-worker 主從模式
- aiohttp
第 18 章 指令碼化系統管理
- 增強的互動式環境 IPython_IPython 安裝_配置_啟動
- 和 IPython 簡單互動
- magic 函式
- 執行外部系統命令和執行檔案
- 檔案管理
- 定時執行任務
第19章 語音識別模組_中英文實時翻譯專案
- 語音識別基本原理
- 文字轉換語音
- 呼叫訊飛動態連結庫實現語音識別
- 百度語音 API 呼叫
- 【專案】實現中英文實時口語翻譯_1
- 【專案】實現中英文實時口語翻譯_2
- 【專案】實現中英文實時口語翻譯_3
- 【專案】實現中英文實時口語翻譯_4
- 【專案】實現中英文實時口語翻譯_5
- 【專案】實現中英文實時口語翻譯_6
第 20 章 Windows 下的 Python 開發
- Python3 安裝 pywin32 模組
- Windows 程式設計基本知識_元件物件
- 實現 COM 元件
- 對 word 的自動訪問
- 對 excel 的自動訪問
- 操作視窗
- 操作程式
- 操作執行緒
- 桌面便籤軟體開發_1
- 桌面便籤軟體開發_2
- 桌面便籤軟體開發_3
- 桌面便籤軟體開發_4
- 桌面便籤軟體開發_5
第 21 章 【實戰】演算法
- 演算法概述
- 時間複雜度分析
- 增長量級的概念
- 空間複雜度分析
- 查詢演算法_順序查詢法
- 查詢演算法_二分查詢法
- 排序演算法_氣泡排序法
- 排序演算法_選擇排序法
- 排序演算法_插入排序法
- 排序演算法_歸併排序法
- 排序演算法_快速排序法
- Python 提供的查詢演算法
- Python 提供的排序演算法
第 22 章 【實戰】手寫資料結構
- 自定義佇列_佇列特點_入隊
- 自定義佇列_出隊_測試
- 自定義棧_入棧
- 自定義棧_出棧
- 自定義二叉樹_分析和建立類
- 自定義二叉樹_建立左子樹_建立右子樹
- 自定義二叉樹_前序遍歷_後序遍歷_中序遍歷
- 自定義二叉樹_測試
- 自定義有向圖_特點分析
10.自定義有向圖_建立
- 自定義有向圖_路徑搜尋功能
- 自定義集合_類的建立和分析
- 自定義集合_新增元素_刪除元素
- 自定義集合_差集運算
- 自定義集合_交集運算
- 自定義集合_並集運算
- 自定義集合_運算子過載_集合包含關係處理
- 自定義集合_運算子過載_支援 in 運算子
- 手寫資料結構總結
第 23 章 正規表示式和複雜文字操作
11:search()在一個字串中查詢 12:案例:程式設計實現抓取 163 主頁所有圖片地址資訊
第 24 章 函數語言程式設計和高階函式
- 函數語言程式設計是什麼
- 高階函式是什麼
- 將函式作為引數
- 返回值為函式
- map()高階函式_1 6. map()高階函式_2
- map()高階函式_3
- reduce()高階函式_1
- reduce()高階函式_2
- reduce()高階函式_3
- filter()高階函式
- sorted()高階函式
- 閉包_1 14. 閉包_2
- 閉包_3
- 匿名函式
- 裝飾器_1 18. 裝飾器_2
- 裝飾器_3
- 偏函式
第 25 章 測試
- 單元測試_unittest
- 單元測試_coverage
- 效能測試_timeit
- 效能測試_profile
- 效能測試_line profiler
- 效能測試_memory profiler
- 效能測試_pympler
第 26 章 【專案】手工實現神經網路
- 神經網路基本原理_1
- 神經網路基本原理_2
- MNIST 資料集簡介
- 矩陣原理_1
- 矩陣原理_相關運算_2
- 正態分佈原理_1
- 正態分佈原理_2
- Sigmoid 函式_神經網路啟用函式_1
- Sigmoid 函式_神經網路啟用函式_2
- 使用 matplotlib 繪製 Sigmoid 函式
- 前向反饋_feedforward 函式_1
- 前向反饋_feedforward 函式_2
- 前向反饋_feedforward 函式_3
- 神經網路學習能力構建_隨機梯度下降函式_1
- 神經網路學習能力構建_隨機梯度下降函式_2
16.神經網路學習能力構建_隨機梯度下降函式_3
17.神經網路學習能力構建_隨機梯度下降函式_4
18.神經網路學習能力構建_隨機梯度下降函式_5
19.MNIST 資料集的載入
20.測試和訓練神經網路