python筆記總結
認識 Python
人生苦短,我用 Python —— Life is short, you need Python
目標
- Python 的起源
- 為什麼要用 Python?
- Python 的特點
- Python 的優缺點
01. Python 的起源
Python 的創始人為吉多·範羅蘇姆(Guido van Rossum)
- 1989 年的聖誕節期間,吉多·範羅蘇姆為了在阿姆斯特丹打發時間,決心開發一個新的解釋程式,作為 ABC 語言的一種繼承(感覺下什麼叫牛人)
- ABC 是由吉多參加設計的一種教學語言,就吉多本人看來,ABC 這種語言非常優美和強大,是專門為非專業程式設計師設計的。但是 ABC 語言並沒有成功,究其原因,吉多認為是非開放
- 之所以選中 Python(蟒蛇) 作為程式的名字,是因為他是 BBC 電視劇——蒙提·派森的飛行馬戲團(Monty Python's Flying Circus)的愛好者
- 1991 年,第一個 Python 直譯器 誕生,它是用 C 語言實現的,並能夠呼叫 C 語言的庫檔案
1.1 直譯器(科普)
計算機不能直接理解任何除機器語言以外的語言,所以必須要把程式設計師所寫的程式語言翻譯成機器語言,計算機才能執行程式。將其他語言翻譯成機器語言的工具,被稱為編譯器
編譯器翻譯的方式有兩種:一個是編譯,另外一個是解釋。兩種方式之間的區別在於翻譯時間點的不同
- 編譯型語言:程式在執行之前需要一個專門的編譯過程,把程式編譯成為機器語言的檔案,執行時不需要重新翻譯,直接使用編譯的結果就行了。程式執行效率高,依賴編譯器,跨平臺性差些。如 C、C++
- 解釋型語言:解釋型語言編寫的程式不進行預先編譯,以文字方式儲存程式程式碼,會將程式碼一句一句直接執行。在釋出程式時,看起來省了道編譯工序,但是在執行程式的時候,必須先解釋再執行
編譯型語言和解釋型語言對比
- 速度 —— 編譯型語言比解釋型語言執行速度快
- 跨平臺性 —— 解釋型語言比編譯型語言跨平臺性好
1.2 Python 的設計目標
1999 年,吉多·範羅蘇姆向 DARPA 提交了一條名為 “Computer Programming for Everybody” 的資金申請,並在後來說明了他對 Python 的目標:
- 一門簡單直觀的語言並與主要競爭者一樣強大
- 開源,以便任何人都可以為它做貢獻
- 程式碼像純英語那樣容易理解
- 適用於短期開發的日常任務
這些想法中的基本都已經成為現實,Python 已經成為一門流行的程式語言
1.3 Python 的設計哲學
- 優雅
- 明確
- 簡單
- Python 開發者的哲學是:用一種方法,最好是隻有一種方法來做一件事
- 如果面臨多種選擇,Python 開發者一般會拒絕花俏的語法,而選擇明確沒有或者很少有歧義的語法
在 Python 社群,吉多被稱為“仁慈的獨裁者”
02. 為什麼選擇 Python?
- 程式碼量少
- ……
同一樣問題,用不同的語言解決,程式碼量差距還是很多的,一般情況下
Python
是Java
的 1/5,所以說 人生苦短,我用 Python
03. Python 特點
- Python 是完全面向物件的語言
- 函式、模組、數字、字串都是物件,在 Python 中一切皆物件
- 完全支援繼承、過載、多重繼承
- 支援過載運算子,也支援泛型設計
- Python 擁有一個強大的標準庫,Python 語言的核心只包含 數字、字串、列表、字典、檔案 等常見型別和函式,而由 Python 標準庫提供了 系統管理、網路通訊、文字處理、資料庫介面、圖形系統、XML 處理 等額外的功能
- Python 社群提供了大量的第三方模組,使用方式與標準庫類似。它們的功能覆蓋 科學計算、人工智慧、機器學習、Web 開發、資料庫介面、圖形系統 多個領域
面向物件的思維方式
- 面向物件 是一種 思維方式,也是一門 程式設計技術
- 要解決一個問題前,首先考慮 由誰 來做,怎麼做事情是 誰 的職責,最後把事情做好就行!
- 物件 就是 誰
- 要解決複雜的問題,就可以找多個不同的物件,各司其職,共同實現,最終完成需求
04. Python 的優缺點
4.1 優點
- 簡單、易學
- 免費、開源
- 面向物件
- 豐富的庫
- 可擴充套件性
- 如果需要一段關鍵程式碼執行得更快或者希望某些演算法不公開,可以把這部分程式用
C
或C++
編寫,然後在Python
程式中使用它們
- 如果需要一段關鍵程式碼執行得更快或者希望某些演算法不公開,可以把這部分程式用
- ……
4.2 缺點
- 執行速度
- 國內市場較小
- 中文資料匱乏
第一個 Python 程式
目標
- 第一個
HelloPython
程式 Python 2.x
與3.x
版本簡介- 執行
Python
程式的三種方式- 直譯器 ——
python
/python3
- 互動式 ——
ipython
- 整合開發環境 ——
PyCharm
- 直譯器 ——
01. 第一個 HelloPython
程式
1.1 Python 源程式的基本概念
- Python 源程式就是一個特殊格式的文字檔案,可以使用任意文字編輯軟體做
Python
的開發 - Python 程式的 副檔名 通常都是
.py
1.2 演練步驟
- 在桌面下,新建
認識Python
目錄 - 在
認識Python
目錄下新建01-HelloPython.py
檔案 - 使用 gedit 編輯
01-HelloPython.py
並且輸入以下內容:
print("hello python")
print("hello world")
- 在終端中輸入以下命令執行
01-HelloPython.py
$ python 01-HelloPython.py
python
中我們學習的第一個 函式
1.3 演練擴充套件 —— 認識錯誤(BUG)
關於錯誤
- 編寫的程式不能正常執行,或者執行的結果不是我們期望的
- 俗稱
BUG
,是程式設計師在開發時非常常見的,初學者常見錯誤的原因包括:- 手誤
- 對已經學習過的知識理解還存在不足
- 對語言還有需要學習和提升的內容
- 在學習語言時,不僅要學會語言的語法,而且還要學會如何認識錯誤和解決錯誤的方法
每一個程式設計師都是在不斷地修改錯誤中成長的
第一個演練中的常見錯誤
- 1> 手誤,例如使用
pirnt("Hello world")
NameError: name 'pirnt' is not defined
名稱錯誤:'pirnt' 名字沒有定義
- 2> 將多條
print
寫在一行
SyntaxError: invalid syntax
語法錯誤:語法無效
每行程式碼負責完成一個動作
- 3> 縮排錯誤
IndentationError: unexpected indent
縮排錯誤:不期望出現的縮排
- Python 是一個格式非常嚴格的程式設計語言
- 目前而言,大家記住每行程式碼前面都不要增加空格
- 4> python 2.x 預設不支援中文
目前市場上有兩個 Python 的版本並存著,分別是 Python 2.x
和 Python 3.x
- Python 2.x 預設不支援中文,具體原因,等到介紹 字元編碼 時給大家講解
- Python 2.x 的直譯器名稱是 python
- Python 3.x 的直譯器名稱是 python3
SyntaxError: Non-ASCII character '\xe4' in file 01-HelloPython.py on line 3,
but no encoding declared;
see http://python.org/dev/peps/pep-0263/ for details
語法錯誤: 在 01-HelloPython.py 中第 3 行出現了非 ASCII 字元 '\xe4',但是沒有宣告檔案編碼
請訪問 http://python.org/dev/peps/pep-0263/ 瞭解詳細資訊
ASCII
字元只包含256
個字元,不支援中文- 有關字元編碼的問題,後續會講
單詞列表
* error 錯誤
* name 名字
* defined 已經定義
* syntax 語法
* invalid 無效
* Indentation 索引
* unexpected 意外的,不期望的
* character 字元
* line 行
* encoding 編碼
* declared 宣告
* details 細節,詳細資訊
* ASCII 一種字元編碼
02. Python 2.x
與 3.x
版本簡介
目前市場上有兩個 Python 的版本並存著,分別是 Python 2.x
和 Python 3.x
新的 Python 程式建議使用
Python 3.0
版本的語法
- Python 2.x 是 過去的版本
- 直譯器名稱是 python
- Python 3.x 是 現在和未來 主流的版本
- 直譯器名稱是 python3
- 相對於
Python
的早期版本,這是一個 較大的升級 - 為了不帶入過多的累贅,
Python 3.0
在設計的時候 沒有考慮向下相容- 許多早期
Python
版本設計的程式都無法在Python 3.0
上正常執行
- 許多早期
- Python 3.0 釋出於 2008 年
- 到目前為止,Python 3.0 的穩定版本已經有很多年了
- Python 3.3 釋出於 2012
- Python 3.4 釋出於 2014
- Python 3.5 釋出於 2015
- Python 3.6 釋出於 2016
- 為了照顧現有的程式,官方提供了一個過渡版本 —— Python 2.6
- 基本使用了
Python 2.x
的語法和庫 - 同時考慮了向
Python 3.0
的遷移,允許使用部分Python 3.0
的語法與函式 - 2010 年中推出的
Python 2.7
被確定為 最後一個Python 2.x 版本
- 基本使用了
提示:如果開發時,無法立即使用 Python 3.0(還有極少的第三方庫不支援 3.0 的語法),建議
- 先使用
Python 3.0
版本進行開發- 然後使用
Python 2.6
、Python 2.7
來執行,並且做一些相容性的處理
03. 執行 Python 程式的三種方式
3.1. 直譯器 python
/ python3
Python 的直譯器
# 使用 python 2.x 直譯器
$ python xxx.py
# 使用 python 3.x 直譯器
$ python3 xxx.py
其他直譯器(知道)
Python 的直譯器 如今有多個語言的實現,包括:
CPython
—— 官方版本的 C 語言實現Jython
—— 可以執行在 Java 平臺IronPython
—— 可以執行在 .NET 和 Mono 平臺PyPy
—— Python 實現的,支援 JIT 即時編譯
3.2. 互動式執行 Python 程式
- 直接在終端中執行直譯器,而不輸入要執行的檔名
- 在 Python 的
Shell
中直接輸入 Python 的程式碼,會立即看到程式執行結果
1) 互動式執行 Python 的優缺點
優點
- 適合於學習/驗證 Python 語法或者區域性程式碼
缺點
- 程式碼不能儲存
- 不適合執行太大的程式
2) 退出 官方的直譯器
1> 直接輸入 exit()
>>> exit()
2> 使用熱鍵退出
在 python 直譯器中,按熱鍵 ctrl + d
可以退出直譯器
3) IPython
- IPython 中 的 “I” 代表 互動 interactive
特點
- IPython 是一個 python 的 互動式 shell,比預設的
python shell
好用得多- 支援自動補全
- 自動縮排
- 支援
bash shell
命令 - 內建了許多很有用的功能和函式
- IPython 是基於 BSD 開源的
版本
-
Python 2.x 使用的直譯器是 ipython
-
Python 3.x 使用的直譯器是 ipython3
-
要退出直譯器可以有以下兩種方式:
1> 直接輸入 exit
In [1]: exit
2> 使用熱鍵退出
在 IPython 直譯器中,按熱鍵 ctrl + d
,IPython
會詢問是否退出直譯器
IPython 的安裝
$ sudo apt install ipython
3.3. Python 的 IDE —— PyCharm
1) 整合開發環境(IDE)
整合開發環境(IDE
,Integrated Development Environment)—— 集成了開發軟體需要的所有工具,一般包括以下工具:
- 圖形使用者介面
- 程式碼編輯器(支援 程式碼補全/自動縮排)
- 編譯器/直譯器
- 偵錯程式(斷點/單步執行)
- ……
2)PyCharm 介紹
PyCharm
是 Python 的一款非常優秀的整合開發環境PyCharm
除了具有一般 IDE 所必備功能外,還可以在Windows
、Linux
、macOS
下使用PyCharm
適合開發大型專案- 一個專案通常會包含 很多原始檔
- 每個 原始檔 的程式碼行數是有限的,通常在幾百行之內
- 每個 原始檔 各司其職,共同完成複雜的業務功能
3)PyCharm 快速體驗
-
檔案導航區域 能夠 瀏覽/定位/開啟 專案檔案
-
檔案編輯區域 能夠 編輯 當前開啟的檔案
-
控制檯區域 能夠:
- 輸出程式執行內容
- 跟蹤除錯程式碼的執行
-
右上角的 工具欄 能夠 執行(SHIFT + F10) / 除錯(SHIFT + F9) 程式碼
-
通過控制檯上方的單步執行按鈕(F8),可以單步執行程式碼
註釋
目標
- 註釋的作用
- 單行註釋(行註釋)
- 多行註釋(塊註釋)
01. 註釋的作用
使用用自己熟悉的語言,在程式中對某些程式碼進行標註說明,增強程式的可讀性
02. 單行註釋(行註釋)
-
以
#
開頭,#
右邊的所有東西都被當做說明文字,而不是真正要執行的程式,只起到輔助說明作用 -
示例程式碼如下:
# 這是第一個單行註釋
print("hello python")
為了保證程式碼的可讀性,
#
後面建議先新增一個空格,然後再編寫相應的說明文字
在程式碼後面增加的單行註釋
-
在程式開發時,同樣可以使用
#
在程式碼的後面(旁邊)增加說明性的文字 -
但是,需要注意的是,為了保證程式碼的可讀性,註釋和程式碼之間 至少要有 兩個空格
-
示例程式碼如下:
print("hello python") # 輸出 `hello python`
03. 多行註釋(塊註釋)
-
如果希望編寫的 註釋資訊很多,一行無法顯示,就可以使用多行註釋
-
要在 Python 程式中使用多行註釋,可以用 一對 連續的 三個 引號(單引號和雙引號都可以)
-
示例程式碼如下:
"""
這是一個多行註釋
在多行註釋之間,可以寫很多很多的內容……
"""
print("hello python")
什麼時候需要使用註釋?
- 註釋不是越多越好,對於一目瞭然的程式碼,不需要添加註釋
- 對於 複雜的操作,應該在操作開始前寫上若干行註釋
- 對於 不是一目瞭然的程式碼,應在其行尾添加註釋(為了提高可讀性,註釋應該至少離開程式碼 2 個空格)
- 絕不要描述程式碼,假設閱讀程式碼的人比你更懂 Python,他只是不知道你的程式碼要做什麼
在一些正規的開發團隊,通常會有 程式碼稽核 的慣例,就是一個團隊中彼此閱讀對方的程式碼
關於程式碼規範
Python
官方提供有一系列 PEP(Python Enhancement Proposals) 文件- 其中第 8 篇文件專門針對 Python 的程式碼格式 給出了建議,也就是俗稱的 PEP 8
- 文件地址:https://www.python.org/dev/peps/pep-0008/
- 谷歌有對應的中文文件:http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/
任何語言的程式設計師,編寫出符合規範的程式碼,是開始程式生涯的第一步
# 算數運算子
**計算機**,顧名思義就是負責進行 **數學計算** 並且 **儲存計算結果** 的電子裝置
## 目標
* 算術運算子的基本使用
## 01. 算數運算子
* 算數運算子是 **運算子的一種**
* 是完成基本的算術運算使用的符號,用來處理四則運算
| 運算子 | 描述 | 例項 |
| :----: | :----: | ------------------------------------------ |
| + | 加 | 10 + 20 = 30 |
| - | 減 | 10 - 20 = -10 |
| * | 乘 | 10 * 20 = 200 |
| / | 除 | 10 / 20 = 0.5 |
| // | 取整除 | 返回除法的整數部分(商) 9 // 2 輸出結果 4 |
| % | 取餘數 | 返回除法的餘數 9 % 2 = 1 |
| ** | 冪 | 又稱次方、乘方,2 ** 3 = 8 |
* 在 Python 中 `*` 運算子還可以用於字串,計算結果就是字串重複指定次數的結果
```python
In [1]: "-" * 50
Out[1]: '----------------------------------------'
02. 算數運算子的優先順序
-
和數學中的運算子的優先順序一致,在 Python 中進行數學計算時,同樣也是:
- 先乘除後加減
- 同級運算子是 從左至右 計算
- 可以使用
()
調整計算的優先順序
-
以下表格的算數優先順序由高到最低順序排列
運算子 | 描述 |
---|---|
** | 冪 (最高優先順序) |
* / % // | 乘、除、取餘數、取整除 |
+ - | 加法、減法 |
- 例如:
2 + 3 * 5 = 17
(2 + 3) * 5 = 25
2 * 3 + 5 = 11
2 * (3 + 5) = 16
程式執行原理(科普)
目標
- 計算機中的 三大件
- 程式執行的原理
- 程式的作用
計算機中的三大件
計算機中包含有較多的硬體,但是一個程式要執行,有 三個 核心的硬體,分別是:
- CPU
- 中央處理器,是一塊超大規模的積體電路
- 負責 處理資料/計算
- 記憶體
- 臨時 儲存資料(斷電之後,資料會消失)
- 速度快
- 空間小(單位價格高)
- 硬碟
- 永久 儲存資料
- 速度慢
- 空間大(單位價格低)
程式執行的原理
- 程式 執行之前,程式是 儲存在硬碟 中的
- 當要執行一個程式時
- 作業系統會首先讓 CPU 把程式複製到 記憶體 中
- CPU 執行 記憶體 中的 程式程式碼
程式要執行,首先要被載入到記憶體
2.1 Python 程式執行原理
- 作業系統會首先讓 CPU 把 Python 直譯器 的程式複製到 記憶體 中
- Python 直譯器 根據語法規則,從上向下 讓 CPU 翻譯 Python 程式中的程式碼
- CPU 負責執行翻譯完成的程式碼
Python 的直譯器有多大?
- 執行以下終端命令可以檢視 Python 直譯器的大小
# 1. 確認直譯器所在位置
$ which python
# 2. 檢視 python 檔案大小(只是一個軟連結)
$ ls -lh /usr/bin/python
# 3. 檢視具體檔案大小
$ ls -lh /usr/bin/python2.7
提示:建立 軟連結 的目的,是為了方便使用者不用記住使用的直譯器是 哪一個具體版本
程式的作用
程式就是 用來處理資料 的!
- 新聞軟體 提供的 新聞內容、評論…… 是資料
- 電商軟體 提供的 商品資訊、配送資訊…… 是資料
- 運動類軟體 提供的 運動資料…… 是資料
- 地圖類軟體 提供的 地圖資訊、定位資訊、車輛資訊…… 是資料
- 即時通訊軟體 提供的 聊天資訊、好友資訊…… 是資料
- ……
變數的基本使用
程式就是用來處理資料的,而變數就是用來儲存資料的
目標
- 變數定義
- 變數的型別
- 變數的命名
01. 變數定義
- 在 Python 中,每個變數 在使用前都必須賦值,變數 賦值以後 該變數 才會被建立
- 等號(=)用來給變數賦值
=
左邊是一個變數名=
右邊是儲存在變數中的值
變數名 = 值
變數定義之後,後續就可以直接使用了
1) 變數演練1 —— iPython
# 定義 qq_number 的變數用來儲存 qq 號碼
In [1]: qq_number = "1234567"
# 輸出 qq_number 中儲存的內容
In [2]: qq_number
Out[2]: '1234567'
# 定義 qq_password 的變數用來儲存 qq 密碼
In [3]: qq_password = "123"
# 輸出 qq_password 中儲存的內容
In [4]: qq_password
Out[4]: '123'
使用互動式方式,如果要檢視變數內容,直接輸入變數名即可,不需要使用
2) 變數演練 2 —— PyCharm
# 定義 qq 號碼變數
qq_number = "1234567"
# 定義 qq 密碼變數
qq_password = "123"
# 在程式中,如果要輸出變數的內容,需要使用 print 函式
print(qq_number)
print(qq_password)
使用直譯器執行,如果要輸出變數的內容,必須要要使用
3) 變數演練 3 —— 超市買蘋果
- 可以用 其他變數的計算結果 來定義變數
- 變數定義之後,後續就可以直接使用了
需求
- 蘋果的價格是 8.5 元/斤
- 買了 7.5 斤 蘋果
- 計算付款金額
# 定義蘋果價格變數
price = 8.5
# 定義購買重量
weight = 7.5
# 計算金額
money = price * weight
print(money)
思考題
- 如果 只要買蘋果,就返 5 塊錢
- 請重新計算購買金額
# 定義蘋果價格變數
price = 8.5
# 定義購買重量
weight = 7.5
# 計算金額
money = price * weight
# 只要買蘋果就返 5 元
money = money - 5
print(money)
提問
- 上述程式碼中,一共定義有幾個變數?
- 三個:
price
/weight
/money
- 三個:
money = money - 5
是在定義新的變數還是在使用變數?- 直接使用之前已經定義的變數
- 變數名 只有在 第一次出現 才是 定義變數
- 變數名 再次出現,不是定義變數,而是直接使用之前定義過的變數
- 在程式開發中,可以修改之前定義變數中儲存的值嗎?
- 可以
- 變數中儲存的值,就是可以 變 的
02. 變數的型別
- 在記憶體中建立一個變數,會包括:
- 變數的名稱
- 變數儲存的資料
- 變數儲存資料的型別
- 變數的地址(標示)
2.1 變數型別的演練 —— 個人資訊
需求
- 定義變數儲存小明的個人資訊
- 姓名:小明
- 年齡:18 歲
- 性別:是男生
- 身高:1.75 米
- 體重:75.0 公斤
利用 單步除錯 確認變數中儲存資料的型別
提問
- 在演練中,一共有幾種資料型別?
- 4 種
str
—— 字串bool
—— 布林(真假)int
—— 整數float
—— 浮點數(小數)
- 在
Python
中定義變數時需要指定型別嗎?- 不需要
Python
可以根據=
等號右側的值,自動推匯出變數中儲存資料的型別
2.2 變數的型別
- 在
Python
中定義變數是 不需要指定型別(在其他很多高階語言中都需要) - 資料型別可以分為 數字型 和 非數字型
- 數字型
- 整型 (
int
) - 浮點型(
float
) - 布林型(
bool
)- 真
True
非 0 數
—— 非零即真 - 假
False
0
- 真
- 複數型 (
complex
)- 主要用於科學計算,例如:平面場問題、波動問題、電感電容等問題
- 整型 (
- 非數字型
- 字串
- 列表
- 元組
- 字典
提示:在 Python 2.x 中,整數 根據儲存數值的長度還分為:
int
(整數)long
(長整數)
- 使用
type
函式可以檢視一個變數的型別
In [1]: type(name)
2.3 不同型別變數之間的計算
1) 數字型變數 之間可以直接計算
- 在 Python 中,兩個數字型變數是可以直接進行 算數運算的
- 如果變數是
bool
型,在計算時True
對應的數字是1
False
對應的數字是0
演練步驟
- 定義整數
i = 10
- 定義浮點數
f = 10.5
- 定義布林型
b = True
- 在 iPython 中,使用上述三個變數相互進行算術運算
2) 字串變數 之間使用 +
拼接字串
- 在 Python 中,字串之間可以使用
+
拼接生成新的字串
In [1]: first_name = "三"
In [2]: last_name = "張"
In [3]: first_name + last_name
Out[3]: '三張'
3) 字串變數 可以和 整數 使用 *
重複拼接相同的字串
In [1]: "-" * 50
Out[1]: '--------------------------------------------------'
4) 數字型變數 和 字串 之間 不能進行其他計算
In [1]: first_name = "zhang"
In [2]: x = 10
In [3]: x + first_name
---------------------------------------------------------------------------
TypeError: unsupported operand type(s) for +: 'int' and 'str'
型別錯誤:`+` 不支援的操作型別:`int` 和 `str`
2.4 變數的輸入
- 所謂 輸入,就是 用程式碼 獲取 使用者通過 鍵盤 輸入的資訊
- 例如:去銀行取錢,在 ATM 上輸入密碼
- 在 Python 中,如果要獲取使用者在 鍵盤 上的輸入資訊,需要使用到
input
函式
1) 關於函式
- 一個 提前準備好的功能(別人或者自己寫的程式碼),可以直接使用,而 不用關心內部的細節
- 目前已經學習過的函式
函式 | 說明 |
---|---|
print(x) | 將 x 輸出到控制檯 |
type(x) | 檢視 x 的變數型別 |
2) input 函式實現鍵盤輸入
- 在 Python 中可以使用
input
函式從鍵盤等待使用者的輸入 - 使用者輸入的 任何內容 Python 都認為是一個 字串
- 語法如下:
字串變數 = input("提示資訊:")
3) 型別轉換函式
函式 | 說明 |
---|---|
int(x) | 將 x 轉換為一個整數 |
float(x) | 將 x 轉換到一個浮點數 |
4) 變數輸入演練 —— 超市買蘋果增強版
需求
- 收銀員輸入 蘋果的價格,單位:元/斤
- 收銀員輸入 使用者購買蘋果的重量,單位:斤
- 計算並且 輸出 付款金額
演練方式 1
# 1. 輸入蘋果單價
price_str = input("請輸入蘋果價格:")
# 2. 要求蘋果重量
weight_str = input("請輸入蘋果重量:")
# 3. 計算金額
# 1> 將蘋果單價轉換成小數
price = float(price_str)
# 2> 將蘋果重量轉換成小數
weight = float(weight_str)
# 3> 計算付款金額
money = price * weight
print(money)
提問
- 演練中,針對 價格 定義了幾個變數?
- 兩個
price_str
記錄使用者輸入的價格字串price
記錄轉換後的價格數值
- 思考 —— 如果開發中,需要使用者通過控制檯 輸入 很多個 數字,針對每一個數字都要定義兩個變數,方便嗎?
演練方式 2 —— 買蘋果改進版
- 定義 一個 浮點變數 接收使用者輸入的同時,就使用
float
函式進行轉換
price = float(input("請輸入價格:"))
- 改進後的好處:
- 節約空間,只需要為一個變數分配空間
- 起名字方便,不需要為中間變數起名字
- 改進後的“缺點”:
- 初學者需要知道,兩個函式能夠巢狀使用,稍微有一些難度
提示
- 如果輸入的不是一個數字,程式執行時會出錯,有關資料轉換的高階話題,後續會講!
2.5 變數的格式化輸出
蘋果單價
9.00
元/斤,購買了5.00
斤,需要支付45.00
元
- 在 Python 中可以使用
print
函式將資訊輸出到控制檯 - 如果希望輸出文字資訊的同時,一起輸出 資料,就需要使用到 格式化操作符
%
被稱為 格式化操作符,專門用於處理字串中的格式- 包含
%
的字串,被稱為 格式化字串 %
和不同的 字元 連用,不同型別的資料 需要使用 不同的格式化字元
- 包含
格式化字元 | 含義 |
---|---|
%s | 字串 |
%d | 有符號十進位制整數,%06d 表示輸出的整數顯示位數,不足的地方使用 0 補全 |
%f | 浮點數,%.2f 表示小數點後只顯示兩位 |
%% | 輸出 % |
- 語法格式如下:
print("格式化字串" % 變數1)
print("格式化字串" % (變數1, 變數2...))
格式化輸出演練 —— 基本練習
需求
- 定義字串變數
name
,輸出 我的名字叫 小明,請多多關照! - 定義整數變數
student_no
,輸出 我的學號是 000001 - 定義小數
price
、weight
、money
,輸出 蘋果單價 9.00 元/斤,購買了 5.00 斤,需要支付 45.00 元 - 定義一個小數
scale
,輸出 資料比例是 10.00%
print("我的名字叫 %s,請多多關照!" % name)
print("我的學號是 %06d" % student_no)
print("蘋果單價 %.02f 元/斤,購買 %.02f 斤,需要支付 %.02f 元" % (price, weight, money))
print("資料比例是 %.02f%%" % (scale * 100))
課後練習 —— 個人名片
需求
- 在控制檯依次提示使用者輸入:姓名、公司、職位、電話、郵箱
- 按照以下格式輸出:
**************************************************
公司名稱
姓名 (職位)
電話:電話
郵箱:郵箱
**************************************************
實現程式碼如下:
"""
在控制檯依次提示使用者輸入:姓名、公司、職位、電話、電子郵箱
"""
name = input("請輸入姓名:")
company = input("請輸入公司:")
title = input("請輸入職位:")
phone = input("請輸入電話:")
email = input("請輸入郵箱:")
print("*" * 50)
print(company)
print()
print("%s (%s)" % (name, title))
print()
print("電話:%s" % phone)
print("郵箱:%s" % email)
print("*" * 50)
變數的命名
目標
- 識別符號和關鍵字
- 變數的命名規則
0.1 識別符號和關鍵字
1.1 識別符號
標示符就是程式設計師定義的 變數名、函式名
名字 需要有 見名知義 的效果
- 標示符可以由 字母、下劃線 和 數字 組成
- 不能以數字開頭
- 不能與關鍵字重名
思考:下面的標示符哪些是正確的,哪些不正確為什麼?
fromNo12
from#12
my_Boolean
my-Boolean
Obj2
2ndObj
myInt
My_tExt
_test
test!32
haha(da)tt
jack_rose
jack&rose
GUI
G.U.I
1.2 關鍵字
- 關鍵字 就是在
Python
內部已經使用的識別符號 - 關鍵字 具有特殊的功能和含義
- 開發者 不允許定義和關鍵字相同的名字的標示符
通過以下命令可以檢視 Python
中的關鍵字
In [1]: import keyword
In [2]: print(keyword.kwlist)
提示:關鍵字的學習及使用,會在後面的課程中不斷介紹
import
關鍵字 可以匯入一個 “工具包”在
Python
中不同的工具包,提供有不同的工具
02. 變數的命名規則
命名規則 可以被視為一種 慣例,並無絕對與強制
目的是為了 增加程式碼的識別和可讀性
注意 Python
中的 識別符號 是 區分大小寫的
-
在定義變數時,為了保證程式碼格式,
=
的左右應該各保留一個空格 -
在
Python
中,如果 變數名 需要由 二個 或 多個單詞 組成時,可以按照以下方式命名- 每個單詞都使用小寫字母
- 單詞與單詞之間使用
_
下劃線 連線
- 例如:
first_name
、last_name
、qq_number
、qq_password
駝峰命名法
- 當 變數名 是由二個或多個單片語成時,還可以利用駝峰命名法來命名
- 小駝峰式命名法
- 第一個單詞以小寫字母開始,後續單詞的首字母大寫
- 例如:
firstName
、lastName
- 大駝峰式命名法
- 每一個單詞的首字母都採用大寫字母
- 例如:
FirstName
、LastName
、CamelCase
判斷(if)語句
目標
- 開發中的應用場景
- if 語句體驗
- if 語句進階
- 綜合應用
01. 開發中的應用場景
生活中的判斷幾乎是無所不在的,我們每天都在做各種各樣的選擇,如果這樣?如果那樣?……
程式中的判斷
if 今天發工資:
先還信用卡的錢
if 有剩餘:
又可以happy了,O(∩_∩)O哈哈~
else:
噢,no。。。還的等30天
else:
盼著發工資
判斷的定義
- 如果 條件滿足,才能做某件事情,
- 如果 條件不滿足,就做另外一件事情,或者什麼也不做
正是因為有了判斷,才使得程式世界豐富多彩,充滿變化!
判斷語句 又被稱為 “分支語句”,正是因為有了判斷,才讓程式有了很多的分支
02. if 語句體驗
2.1 if 判斷語句基本語法
在 Python
中,if 語句 就是用來進行判斷的,格式如下:
if 要判斷的條件:
條件成立時,要做的事情
……
注意:程式碼的縮排為一個
tab
鍵,或者 4 個空格 —— 建議使用空格
- 在 Python 開發中,Tab 和空格不要混用!
我們可以把整個 if 語句看成一個完整的程式碼塊
2.2 判斷語句演練 —— 判斷年齡
需求
- 定義一個整數變數記錄年齡
- 判斷是否滿 18 歲 (>=)
- 如果滿 18 歲,允許進網咖嗨皮
# 1. 定義年齡變數
age = 18
# 2. 判斷是否滿 18 歲
# if 語句以及縮排部分的程式碼是一個完整的程式碼塊
if age >= 18:
print("可以進網咖嗨皮……")
# 3. 思考!- 無論條件是否滿足都會執行
print("這句程式碼什麼時候執行?")
注意:
if
語句以及縮排部分是一個 完整的程式碼塊
2.3 else 處理條件不滿足的情況
思考
在使用 if
判斷時,只能做到滿足條件時要做的事情。那如果需要在 不滿足條件的時候,做某些事情,該如何做呢?
答案
else
,格式如下:
if 要判斷的條件:
條件成立時,要做的事情
……
else:
條件不成立時,要做的事情
……
注意:
if
和else
語句以及各自的縮排部分共同是一個 完整的程式碼塊
2.4 判斷語句演練 —— 判斷年齡改進
需求
- 輸入使用者年齡
- 判斷是否滿 18 歲 (>=)
- 如果滿 18 歲,允許進網咖嗨皮
- 如果未滿 18 歲,提示回家寫作業
# 1. 輸入使用者年齡
age = int(input("今年多大了?"))
# 2. 判斷是否滿 18 歲
# if 語句以及縮排部分的程式碼是一個完整的語法塊
if age >= 18:
print("可以進網咖嗨皮……")
else:
print("你還沒長大,應該回家寫作業!")
# 3. 思考!- 無論條件是否滿足都會執行
print("這句程式碼什麼時候執行?")
03. 邏輯運算
- 在程式開發中,通常 在判斷條件時,會需要同時判斷多個條件
- 只有多個條件都滿足,才能夠執行後續程式碼,這個時候需要使用到 邏輯運算子
- 邏輯運算子 可以把 多個條件 按照 邏輯 進行 連線,變成 更復雜的條件
- Python 中的 邏輯運算子 包括:與 and/或 or/非 not 三種
3.1 and
條件1 and 條件2
- 與/並且
- 兩個條件同時滿足,返回
True
- 只要有一個不滿足,就返回
False
條件 1 | 條件 2 | 結果 |
---|---|---|
成立 | 成立 | 成立 |
成立 | 不成立 | 不成立 |
不成立 | 成立 | 不成立 |
不成立 | 不成立 | 不成立 |
3.2 or
條件1 or 條件2
- 或/或者
- 兩個條件只要有一個滿足,返回
True
- 兩個條件都不滿足,返回
False
條件 1 | 條件 2 | 結果 |
---|---|---|
成立 | 成立 | 成立 |
成立 | 不成立 | 成立 |
不成立 | 成立 | 成立 |
不成立 | 不成立 | 不成立 |
3.3 not
not 條件
- 非/不是
條件 | 結果 |
---|---|
成立 | 不成立 |
不成立 | 成立 |
邏輯運算演練
- 練習1: 定義一個整數變數
age
,編寫程式碼判斷年齡是否正確- 要求人的年齡在 0-120 之間
- 練習2: 定義兩個整數變數
python_score
、c_score
,編寫程式碼判斷成績- 要求只要有一門成績 > 60 分就算合格
- 練習3: 定義一個布林型變數
is_employee
,編寫程式碼判斷是否是本公司員工- 如果不是提示不允許入內
答案 1:
# 練習1: 定義一個整數變數 age,編寫程式碼判斷年齡是否正確
age = 100
# 要求人的年齡在 0-120 之間
if age >= 0 and age <= 120:
print("年齡正確")
else:
print("年齡不正確")
答案 2:
# 練習2: 定義兩個整數變數 python_score、c_score,編寫程式碼判斷成績
python_score = 50
c_score = 50
# 要求只要有一門成績 > 60 分就算合格
if python_score > 60 or c_score > 60:
print("考試通過")
else:
print("再接再厲!")
答案 3:
# 練習3: 定義一個布林型變數 `is_employee`,編寫程式碼判斷是否是本公司員工
is_employee = True
# 如果不是提示不允許入內
if not is_employee:
print("非公勿內")
04. if 語句進階
4.1 elif
- 在開發中,使用
if
可以 判斷條件 - 使用
else
可以處理 條件不成立 的情況 - 但是,如果希望 再增加一些條件,條件不同,需要執行的程式碼也不同 時,就可以使用
elif
- 語法格式如下:
if 條件1:
條件1滿足執行的程式碼
……
elif 條件2:
條件2滿足時,執行的程式碼
……
elif 條件3:
條件3滿足時,執行的程式碼
……
else:
以上條件都不滿足時,執行的程式碼
……
- 對比邏輯運算子的程式碼
if 條件1 and 條件2:
條件1滿足 並且 條件2滿足 執行的程式碼
……
注意
elif
和else
都必須和if
聯合使用,而不能單獨使用- 可以將
if
、elif
和else
以及各自縮排的程式碼,看成一個 完整的程式碼塊
elif 演練 —— 女友的節日
需求
- 定義
holiday_name
字串變數記錄節日名稱 - 如果是 情人節 應該 買玫瑰/看電影
- 如果是 平安夜 應該 買蘋果/吃大餐
- 如果是 生日 應該 買蛋糕
- 其他的日子每天都是節日啊……
holiday_name = "平安夜"
if holiday_name == "情人節":
print("買玫瑰")
print("看電影")
elif holiday_name == "平安夜":
print("買蘋果")
print("吃大餐")
elif holiday_name == "生日":
print("買蛋糕")
else:
print("每天都是節日啊……")
4.2 if
的巢狀
elif 的應用場景是:同時 判斷 多個條件,所有的條件是 平級 的
- 在開發中,使用
if
進行條件判斷,如果希望 在條件成立的執行語句中 再 增加條件判斷,就可以使用 if 的巢狀 - if 的巢狀 的應用場景就是:在之前條件滿足的前提下,再增加額外的判斷
- if 的巢狀 的語法格式,除了縮排之外 和之前的沒有區別
- 語法格式如下:
if 條件 1:
條件 1 滿足執行的程式碼
……
if 條件 1 基礎上的條件 2:
條件 2 滿足時,執行的程式碼
……
# 條件 2 不滿足的處理
else:
條件 2 不滿足時,執行的程式碼
# 條件 1 不滿足的處理
else:
條件1 不滿足時,執行的程式碼
……
if 的巢狀 演練 —— 火車站安檢
需求
- 定義布林型變數
has_ticket
表示是否有車票 - 定義整型變數
knife_length
表示刀的長度,單位:釐米 - 首先檢查是否有車票,如果有,才允許進行 安檢
- 安檢時,需要檢查刀的長度,判斷是否超過 20 釐米
- 如果超過 20 釐米,提示刀的長度,不允許上車
- 如果不超過 20 釐米,安檢通過
- 如果沒有車票,不允許進門
# 定義布林型變數 has_ticket 表示是否有車票
has_ticket = True
# 定義整數型變數 knife_length 表示刀的長度,單位:釐米
knife_length = 20
# 首先檢查是否有車票,如果有,才允許進行 安檢
if has_ticket:
print("有車票,可以開始安檢...")
# 安檢時,需要檢查刀的長度,判斷是否超過 20 釐米
# 如果超過 20 釐米,提示刀的長度,不允許上車
if knife_length >= 20:
print("不允許攜帶 %d 釐米長的刀上車" % knife_length)
# 如果不超過 20 釐米,安檢通過
else:
print("安檢通過,祝您旅途愉快……")
# 如果沒有車票,不允許進門
else:
print("大哥,您要先買票啊")
05. 綜合應用 —— 石頭剪刀布
目標
- 強化 多個條件 的 邏輯運算
- 體會
import
匯入模組(“工具包”)的使用
需求
- 從控制檯輸入要出的拳 —— 石頭(1)/剪刀(2)/布(3)
- 電腦 隨機 出拳 —— 先假定電腦只會出石頭,完成整體程式碼功能
- 比較勝負
序號 | 規則 |
---|---|
1 | 石頭 勝 剪刀 |
2 | 剪刀 勝 布 |
3 | 布 勝 石頭 |
5.1 基礎程式碼實現
- 先 假定電腦就只會出石頭,完成整體程式碼功能
# 從控制檯輸入要出的拳 —— 石頭(1)/剪刀(2)/布(3)
player = int(input("請出拳 石頭(1)/剪刀(2)/布(3):"))
# 電腦 隨機 出拳 - 假定電腦永遠出石頭
computer = 1
# 比較勝負
# 如果條件判斷的內容太長,可以在最外側的條件增加一對大括號
# 再在每一個條件之間,使用回車,PyCharm 可以自動增加 8 個空格
if ((player == 1 and computer == 2) or
(player == 2 and computer == 3) or
(player == 3 and computer == 1)):
print("噢耶!!!電腦弱爆了!!!")
elif player == computer:
print("心有靈犀,再來一盤!")
else:
print("不行,我要和你決戰到天亮!")
5.2 隨機數的處理
- 在
Python
中,要使用隨機數,首先需要匯入 隨機數 的 模組 —— “工具包”
import random
-
匯入模組後,可以直接在 模組名稱 後面敲一個
.
然後按Tab
鍵,會提示該模組中包含的所有函式 -
random.randint(a, b)
,返回[a, b]
之間的整數,包含a
和b
-
例如:
random.randint(12, 20) # 生成的隨機數n: 12 <= n <= 20
random.randint(20, 20) # 結果永遠是 20
random.randint(20, 10) # 該語句是錯誤的,下限必須小於上限
運算子
目標
- 算數運算子
- 比較(關係)運算子
- 邏輯運算子
- 賦值運算子
- 運算子的優先順序
數學符號表連結:https://zh.wikipedia.org/wiki/數學符號表
01. 算數運算子
- 是完成基本的算術運算使用的符號,用來處理四則運算
運算子 | 描述 | 例項 |
---|---|---|
+ | 加 | 10 + 20 = 30 |
- | 減 | 10 - 20 = -10 |
* | 乘 | 10 * 20 = 200 |
/ | 除 | 10 / 20 = 0.5 |
// | 取整除 | 返回除法的整數部分(商) 9 // 2 輸出結果 4 |
% | 取餘數 | 返回除法的餘數 9 % 2 = 1 |
** | 冪 | 又稱次方、乘方,2 ** 3 = 8 |
- 在 Python 中
*
運算子還可以用於字串,計算結果就是字串重複指定次數的結果
In [1]: "-" * 50
Out[1]: '----------------------------------------'
02. 比較(關係)運算子
運算子 | 描述 |
---|---|
== | 檢查兩個運算元的值是否 相等,如果是,則條件成立,返回 True |
!= | 檢查兩個運算元的值是否 不相等,如果是,則條件成立,返回 True |
> | 檢查左運算元的值是否 大於 右運算元的值,如果是,則條件成立,返回 True |
< | 檢查左運算元的值是否 小於 右運算元的值,如果是,則條件成立,返回 True |
>= | 檢查左運算元的值是否 大於或等於 右運算元的值,如果是,則條件成立,返回 True |
<= | 檢查左運算元的值是否 小於或等於 右運算元的值,如果是,則條件成立,返回 True |
Python 2.x 中判斷 不等於 還可以使用
<>
運算子
!=
在 Python 2.x 中同樣可以用來判斷 不等於
03. 邏輯運算子
運算子 | 邏輯表示式 | 描述 |
---|---|---|
and | x and y | 只有 x 和 y 的值都為 True,才會返回 True 否則只要 x 或者 y 有一個值為 False,就返回 False |
or | x or y | 只要 x 或者 y 有一個值為 True,就返回 True 只有 x 和 y 的值都為 False,才會返回 False |
not | not x | 如果 x 為 True,返回 False 如果 x 為 False,返回 True |
04. 賦值運算子
- 在 Python 中,使用
=
可以給變數賦值 - 在算術運算時,為了簡化程式碼的編寫,
Python
還提供了一系列的 與 算術運算子 對應的 賦值運算子 - 注意:賦值運算子中間不能使用空格
運算子 | 描述 | 例項 |
---|---|---|
= | 簡單的賦值運算子 | c = a + b 將 a + b 的運算結果賦值為 c |
+= | 加法賦值運算子 | c += a 等效於 c = c + a |
-= | 減法賦值運算子 | c -= a 等效於 c = c - a |
*= | 乘法賦值運算子 | c *= a 等效於 c = c * a |
/= | 除法賦值運算子 | c /= a 等效於 c = c / a |
//= | 取整除賦值運算子 | c //= a 等效於 c = c // a |
%= | 取 模 (餘數)賦值運算子 | c %= a 等效於 c = c % a |
**= | 冪賦值運算子 | c **= a 等效於 c = c ** a |
05. 運算子的優先順序
- 以下表格的算數優先順序由高到最低順序排列
運算子 | 描述 |
---|---|
** | 冪 (最高優先順序) |
* / % // | 乘、除、取餘數、取整除 |
+ - | 加法、減法 |
<= < > >= | 比較運算子 |
== != | 等於運算子 |
= %= /= //= -= += *= **= | 賦值運算子 |
not or and | 邏輯運算子 |
迴圈
目標
- 程式的三大流程
- while 迴圈基本使用
- break 和 continue
- while 迴圈巢狀
01. 程式的三大流程
-
在程式開發中,一共有三種流程方式:
- 順序 —— 從上向下,順序執行程式碼
- 分支 —— 根據條件判斷,決定執行程式碼的 分支
- 迴圈 —— 讓 特定程式碼 重複 執行
02. while
迴圈基本使用
-
迴圈的作用就是讓 指定的程式碼 重複的執行
-
while
迴圈最常用的應用場景就是 讓執行的程式碼 按照 指定的次數 重複 執行 -
需求 —— 列印 5 遍
Hello Python
-
思考 —— 如果要求列印 100 遍怎麼辦?
2.1 while
語句基本語法
初始條件設定 —— 通常是重複執行的 計數器
while 條件(判斷 計數器 是否達到 目標次數):
條件滿足時,做的事情1
條件滿足時,做的事情2
條件滿足時,做的事情3
...(省略)...
處理條件(計數器 + 1)
注意:
while
語句以及縮排部分是一個 完整的程式碼塊
第一個 while 迴圈
需求
- 列印 5 遍 Hello Python
# 1. 定義重複次數計數器
i = 1
# 2. 使用 while 判斷條件
while i <= 5:
# 要重複執行的程式碼
print("Hello Python")
# 處理計數器 i
i = i + 1
print("迴圈結束後的 i = %d" % i)
注意:迴圈結束後,之前定義的計數器條件的數值是依舊存在的
死迴圈
由於程式設計師的原因,忘記 在迴圈內部 修改迴圈的判斷條件,導致迴圈持續執行,程式無法終止!
2.2 賦值運算子
- 在 Python 中,使用
=
可以給變數賦值 - 在算術運算時,為了簡化程式碼的編寫,
Python
還提供了一系列的 與 算術運算子 對應的 賦值運算子 - 注意:賦值運算子中間不能使用空格
運算子 | 描述 | 例項 |
---|---|---|
= | 簡單的賦值運算子 | c = a + b 將 a + b 的運算結果賦值為 c |
+= | 加法賦值運算子 | c += a 等效於 c = c + a |
-= | 減法賦值運算子 | c -= a 等效於 c = c - a |
*= | 乘法賦值運算子 | c *= a 等效於 c = c * a |
/= | 除法賦值運算子 | c /= a 等效於 c = c / a |
//= | 取整除賦值運算子 | c //= a 等效於 c = c // a |
%= | 取 模 (餘數)賦值運算子 | c %= a 等效於 c = c % a |
**= | 冪賦值運算子 | c **= a 等效於 c = c ** a |
2.3 Python 中的計數方法
常見的計數方法有兩種,可以分別稱為:
- 自然計數法(從
1
開始)—— 更符合人類的習慣 - 程式計數法(從
0
開始)—— 幾乎所有的程式語言都選擇從 0 開始計數
因此,大家在編寫程式時,應該儘量養成習慣:除非需求的特殊要求,否則 迴圈 的計數都從 0 開始
2.4 迴圈計算
在程式開發中,通常會遇到 利用迴圈 重複計算 的需求
遇到這種需求,可以:
- 在
while
上方定義一個變數,用於 存放最終計算結果 - 在迴圈體內部,每次迴圈都用 最新的計算結果,更新 之前定義的變數
需求
- 計算 0 ~ 100 之間所有數字的累計求和結果
# 計算 0 ~ 100 之間所有數字的累計求和結果
# 0. 定義最終結果的變數
result = 0
# 1. 定義一個整數的變數記錄迴圈的次數
i = 0
# 2. 開始迴圈
while i <= 100:
print(i)
# 每一次迴圈,都讓 result 這個變數和 i 這個計數器相加
result += i
# 處理計數器
i += 1
print("0~100之間的數字求和結果 = %d" % result)
需求進階
- 計算 0 ~ 100 之間 所有 偶數 的累計求和結果
開發步驟
- 編寫迴圈 確認 要計算的數字
- 新增 結果 變數,在迴圈內部 處理計算結果
# 0. 最終結果
result = 0
# 1. 計數器
i = 0
# 2. 開始迴圈
while i <= 100:
# 判斷偶數
if i % 2 == 0:
print(i)
result += i
# 處理計數器
i += 1
print("0~100之間偶數求和結果 = %d" % result)
03. break 和 continue
break
和continue
是專門在迴圈中使用的關鍵字
break
某一條件滿足時,退出迴圈,不再執行後續重複的程式碼continue
某一條件滿足時,不執行後續重複的程式碼
break
和continue
只針對 當前所在迴圈 有效
3.1 break
- 在迴圈過程中,如果 某一個條件滿足後,不 再希望 迴圈繼續執行,可以使用
break
退出迴圈
i = 0
while i < 10:
# break 某一條件滿足時,退出迴圈,不再執行後續重複的程式碼
# i == 3
if i == 3:
break
print(i)
i += 1
print("over")
break
只針對當前所在迴圈有效
3.2 continue
- 在迴圈過程中,如果 某一個條件滿足後,不 希望 執行迴圈程式碼,但是又不希望退出迴圈,可以使用
continue
- 也就是:在整個迴圈中,只有某些條件,不需要執行迴圈程式碼,而其他條件都需要執行
i = 0
while i < 10:
# 當 i == 7 時,不希望執行需要重複執行的程式碼
if i == 7:
# 在使用 continue 之前,同樣應該修改計數器
# 否則會出現死迴圈
i += 1
continue
# 重複執行的程式碼
print(i)
i += 1
- 需要注意:使用
continue
時,條件處理部分的程式碼,需要特別注意,不小心會出現 死迴圈
continue
只針對當前所在迴圈有效
04. while
迴圈巢狀
4.1 迴圈巢狀
while
巢狀就是:while
裡面還有while
while 條件 1:
條件滿足時,做的事情1
條件滿足時,做的事情2
條件滿足時,做的事情3
...(省略)...
while 條件 2:
條件滿足時,做的事情1
條件滿足時,做的事情2
條件滿足時,做的事情3
...(省略)...
處理條件 2
處理條件 1
4.2 迴圈巢狀演練 —— 九九乘法表
第 1 步:用巢狀列印小星星
需求
- 在控制檯連續輸出五行
*
,每一行星號的數量依次遞增
*
**
***
****
*****
- 使用字串 * 列印
# 1. 定義一個計數器變數,從數字1開始,迴圈會比較方便
row = 1
while row <= 5:
print("*" * row)
row += 1
第 2 步:使用迴圈巢狀列印小星星
知識點 對 print
函式的使用做一個增強
-
在預設情況下,
print
函式輸出內容之後,會自動在內容末尾增加換行 -
如果不希望末尾增加換行,可以在
print
函式輸出內容的後面增加, end=""
-
其中
""
中間可以指定print
函式輸出內容之後,繼續希望顯示的內容 -
語法格式如下:
# 向控制檯輸出內容結束之後,不會換行
print("*", end="")
# 單純的換行
print("")
end=""
表示向控制檯輸出內容結束之後,不會換行
假設 Python
沒有提供 字串的 *
操作 拼接字串
需求
- 在控制檯連續輸出五行
*
,每一行星號的數量依次遞增
*
**
***
****
*****
開發步驟
- 1> 完成 5 行內容的簡單輸出
- 2> 分析每行內部的
*
應該如何處理?- 每行顯示的星星和當前所在的行數是一致的
- 巢狀一個小的迴圈,專門處理每一行中
列
的星星顯示
row = 1
while row <= 5:
# 假設 python 沒有提供字串 * 操作
# 在迴圈內部,再增加一個迴圈,實現每一行的 星星 列印
col = 1
while col <= row:
print("*", end="")
col += 1
# 每一行星號輸出完成後,再增加一個換行
print("")
row += 1
第 3 步: 九九乘法表
需求 輸出 九九乘法表,格式如下:
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81
開發步驟
-
- 列印 9 行小星星
*
**
***
****
*****
******
*******
********
*********
-
- 將每一個
*
替換成對應的行與列相乘
- 將每一個
# 定義起始行
row = 1
# 最大列印 9 行
while row <= 9:
# 定義起始列
col = 1
# 最大列印 row 列
while col <= row:
# end = "",表示輸出結束後,不換行
# "\t" 可以在控制檯輸出一個製表符,協助在輸出文字時對齊
print("%d * %d = %d" % (col, row, row * col), end="\t")
# 列數 + 1
col += 1
# 一行列印完成的換行
print("")
# 行數 + 1
row += 1
字串中的轉義字元
\t
在控制檯輸出一個 製表符,協助在輸出文字時 垂直方向 保持對齊\n
在控制檯輸出一個 換行符
製表符 的功能是在不使用表格的情況下在 垂直方向 按列對齊文字
轉義字元 | 描述 |
---|---|
\\ | 反斜槓符號 |
\' | 單引號 |
\" | 雙引號 |
\n | 換行 |
\t | 橫向製表符 |
\r | 回車 |
面向物件(OOP)基本概念
面向物件程式設計 —— Object Oriented Programming
簡寫 OOP
目標
- 瞭解 面向物件 基本概念
01. 面向物件基本概念
- 我們之前學習的程式設計方式就是 面向過程 的
- 面相過程 和 面相物件,是兩種不同的 程式設計方式
- 對比 面向過程 的特點,可以更好地瞭解什麼是 面向物件
1.1 過程和函式(科普)
- 過程 是早期的一個程式設計概念
- 過程 類似於函式,只能執行,但是沒有返回值
- 函式 不僅能執行,還可以返回結果
1.2 面相過程 和 面相物件 基本概念
1) 面相過程 —— 怎麼做?
- 把完成某一個需求的
所有步驟
從頭到尾
逐步實現 - 根據開發需求,將某些 功能獨立 的程式碼 封裝 成一個又一個 函式
- 最後完成的程式碼,就是順序地呼叫 不同的函式
特點
- 注重 步驟與過程,不注重職責分工
- 如果需求複雜,程式碼會變得很複雜
- 開發複雜專案,沒有固定的套路,開發難度很大!
2) 面向物件 —— 誰來做?
相比較函式,面向物件 是 更大 的 封裝,根據 職責 在 一個物件中 封裝 多個方法
- 在完成某一個需求前,首先確定 職責 —— 要做的事情(方法)
- 根據 職責 確定不同的 物件,在 物件 內部封裝不同的 方法(多個)
- 最後完成的程式碼,就是順序地讓 不同的物件 呼叫 不同的方法
特點
- 注重 物件和職責,不同的物件承擔不同的職責
- 更加適合應對複雜的需求變化,是專門應對複雜專案開發,提供的固定套路
- 需要在面向過程基礎上,再學習一些面向物件的語法
類和物件
目標
- 類和物件的概念
- 類和物件的關係
- 類的設計
01. 類和物件的概念
類 和 物件 是 面向物件程式設計的 兩個 核心概念
1.1 類
- 類 是對一群具有 相同 特徵 或者 行為 的事物的一個統稱,是抽象的,不能直接使用
- 特徵 被稱為 屬性
- 行為 被稱為 方法
- 類 就相當於製造飛機時的圖紙,是一個 模板,是 負責建立物件的
1.2 物件
- 物件 是 由類創建出來的一個具體存在,可以直接使用
- 由 哪一個類 創建出來的 物件,就擁有在 哪一個類 中定義的:
- 屬性
- 方法
- 物件 就相當於用 圖紙 製造 的飛機
在程式開發中,應該 先有類,再有物件
02. 類和物件的關係
- 類是模板,物件 是根據 類 這個模板創建出來的,應該 先有類,再有物件
- 類 只有一個,而 物件 可以有很多個
- 不同的物件 之間 屬性 可能會各不相同
- 類 中定義了什麼 屬性和方法,物件 中就有什麼屬性和方法,不可能多,也不可能少
03. 類的設計
在使用面相物件開發前,應該首先分析需求,確定一下,程式中需要包含哪些類!
在程式開發中,要設計一個類,通常需要滿足一下三個要素:
- 類名 這類事物的名字,滿足大駝峰命名法
- 屬性 這類事物具有什麼樣的特徵
- 方法 這類事物具有什麼樣的行為
大駝峰命名法
CapWords
- 每一個單詞的首字母大寫
- 單詞與單詞之間沒有下劃線
3.1 類名的確定
名詞提煉法 分析 整個業務流程,出現的 名詞,通常就是找到的類
3.2 屬性和方法的確定
- 對 物件的特徵描述,通常可以定義成 屬性
- 物件具有的行為(動詞),通常可以定義成 方法
提示:需求中沒有涉及的屬性或者方法在設計類時,不需要考慮
練習 1
需求
- 小明 今年 18 歲,身高 1.75,每天早上 跑 完步,會去 吃 東西
- 小美 今年 17 歲,身高 1.65,小美不跑步,小美喜歡 吃 東西
練習 2
需求
- 一隻 黃顏色 的 狗狗 叫 大黃
- 看見生人 汪汪叫
- 看見家人 搖尾巴
面相物件基礎語法
目標
dir
內建函式- 定義簡單的類(只包含方法)
- 方法中的
self
引數 - 初始化方法
- 內建方法和屬性
01. dir
內建函式(知道)
- 在
Python
中 物件幾乎是無所不在的,我們之前學習的 變數、資料、函式 都是物件
在 Python
中可以使用以下兩個方法驗證:
- 在 識別符號 / 資料 後輸入一個
.
,然後按下TAB
鍵,iPython
會提示該物件能夠呼叫的 方法列表 - 使用內建函式
dir
傳入 識別符號 / 資料,可以檢視物件內的 所有屬性及方法
提示 __方法名__
格式的方法是 Python
提供的 內建方法 / 屬性,稍後會給大家介紹一些常用的 內建方法 / 屬性
序號 | 方法名 | 型別 | 作用 |
---|---|---|---|
01 | __new__ |
方法 | 建立物件時,會被 自動 呼叫 |
02 | __init__ |
方法 | 物件被初始化時,會被 自動 呼叫 |
03 | __del__ |
方法 | 物件被從記憶體中銷燬前,會被 自動 呼叫 |
04 | __str__ |
方法 | 返回物件的描述資訊,print 函式輸出使用 |
提示 利用好 dir()
函式,在學習時很多內容就不需要死記硬背了
02. 定義簡單的類(只包含方法)
面向物件 是 更大 的 封裝,在 一個類中 封裝 多個方法,這樣 通過這個類創建出來的物件,就可以直接呼叫這些方法了!
2.1 定義只包含方法的類
- 在
Python
中要定義一個只包含方法的類,語法格式如下:
class 類名:
def 方法1(self, 引數列表):
pass
def 方法2(self, 引數列表):
pass
- 方法 的定義格式和之前學習過的函式 幾乎一樣
- 區別在於第一個引數必須是
self
,大家暫時先記住,稍後介紹self
注意:類名 的 命名規則 要符合 大駝峰命名法
2.2 建立物件
- 當一個類定義完成之後,要使用這個類來建立物件,語法格式如下:
物件變數 = 類名()
2.3 第一個面向物件程式
需求
- 小貓 愛 吃 魚,小貓 要 喝 水
分析
- 定義一個貓類
Cat
- 定義兩個方法
eat
和drink
- 按照需求 —— 不需要定義屬性
class Cat:
"""這是一個貓類"""
def eat(self):
print("小貓愛吃魚")
def drink(self):
print("小貓在喝水")
tom = Cat()
tom.drink()
tom.eat()
引用概念的強調
在面向物件開發中,引用的概念是同樣適用的!
- 在
Python
中使用類 建立物件之後,tom
變數中 仍然記錄的是 物件在記憶體中的地址 - 也就是
tom
變數 引用 了 新建的貓物件 - 使用
print
輸出 物件變數,預設情況下,是能夠輸出這個變數 引用的物件 是 由哪一個類建立的物件,以及 在記憶體中的地址(十六進位制表示)
提示:在計算機中,通常使用 十六進位制 表示 記憶體地址
- 十進位制 和 十六進位制 都是用來表達數字的,只是表示的方式不一樣
- 十進位制 和 十六進位制 的數字之間可以來回轉換
%d
可以以 10 進位制 輸出數字%x
可以以 16 進位制 輸出數字
案例進階 —— 使用 Cat 類再建立一個物件
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
提問:
tom
和lazy_cat
是同一個物件嗎?
03. 方法中的 self
引數
3.1 案例改造 —— 給物件增加屬性
- 在
Python
中,要 給物件設定屬性,非常的容易,但是不推薦使用- 因為:物件屬性的封裝應該封裝在類的內部
- 只需要在 類的外部的程式碼 中直接通過
.
設定一個屬性即可
注意:這種方式雖然簡單,但是不推薦使用!
tom.name = "Tom"
...
lazy_cat.name = "大懶貓"
3.2 使用 self
在方法內部輸出每一隻貓的名字
由 哪一個物件 呼叫的方法,方法內的
self
就是 哪一個物件的引用
- 在類封裝的方法內部,
self
就表示 當前呼叫方法的物件自己 - 呼叫方法時,程式設計師不需要傳遞
self
引數 - 在方法內部
- 可以通過
self.
訪問物件的屬性 - 也可以通過
self.
呼叫其他的物件方法
- 可以通過
- 改造程式碼如下:
class Cat:
def eat(self):
print("%s 愛吃魚" % self.name)
tom = Cat()
tom.name = "Tom"
tom.eat()
lazy_cat = Cat()
lazy_cat.name = "大懶貓"
lazy_cat.eat()
- 在 類的外部,通過
變數名.
訪問物件的 屬性和方法 - 在 類封裝的方法中,通過
self.
訪問物件的 屬性和方法
04. 初始化方法
4.1 之前程式碼存在的問題 —— 在類的外部給物件增加屬性
- 將案例程式碼進行調整,先呼叫方法 再設定屬性,觀察一下執行效果
tom = Cat()
tom.drink()
tom.eat()
tom.name = "Tom"
print(tom)
- 程式執行報錯如下:
AttributeError: 'Cat' object has no attribute 'name'
屬性錯誤:'Cat' 物件沒有 'name' 屬性
提示
- 在日常開發中,不推薦在 類的外部 給物件增加屬性
- 如果在執行時,沒有找到屬性,程式會報錯
- 物件應該包含有哪些屬性,應該 封裝在類的內部
4.2 初始化方法
- 當使用
類名()
建立物件時,會 自動 執行以下操作:- 為物件在記憶體中 分配空間 —— 建立物件
- 為物件的屬性 設定初始值 —— 初始化方法(
init
)
- 這個 初始化方法 就是
__init__
方法,__init__
是物件的內建方法
__init__
方法是 專門 用來定義一個類 具有哪些屬性的方法!
在 Cat
中增加 __init__
方法,驗證該方法在建立物件時會被自動呼叫
class Cat:
"""這是一個貓類"""
def __init__(self):
print("初始化方法")
4.3 在初始化方法內部定義屬性
- 在
__init__
方法內部使用self.屬性名 = 屬性的初始值
就可以 定義屬性 - 定義屬性之後,再使用
Cat
類建立的物件,都會擁有該屬性
class Cat:
def __init__(self):
print("這是一個初始化方法")
# 定義用 Cat 類建立的貓物件都有一個 name 的屬性
self.name = "Tom"
def eat(self):
print("%s 愛吃魚" % self.name)
# 使用類名()建立物件的時候,會自動呼叫初始化方法 __init__
tom = Cat()
tom.eat()
4.4 改造初始化方法 —— 初始化的同時設定初始值
- 在開發中,如果希望在 建立物件的同時,就設定物件的屬性,可以對
__init__
方法進行 改造- 把希望設定的屬性值,定義成
__init__
方法的引數 - 在方法內部使用
self.屬性 = 形參
接收外部傳遞的引數 - 在建立物件時,使用
類名(屬性1, 屬性2...)
呼叫
- 把希望設定的屬性值,定義成
class Cat:
def __init__(self, name):
print("初始化方法 %s" % name)
self.name = name
...
tom = Cat("Tom")
...
lazy_cat = Cat("大懶貓")
...
05. 內建方法和屬性
序號 | 方法名 | 型別 | 作用 |
---|---|---|---|
01 | __del__ |
方法 | 物件被從記憶體中銷燬前,會被 自動 呼叫 |
02 | __str__ |
方法 | 返回物件的描述資訊,print 函式輸出使用 |
5.1 __del__
方法(知道)
-
在
Python
中- 當使用
類名()
建立物件時,為物件 分配完空間後,自動 呼叫__init__
方法 - 當一個 物件被從記憶體中銷燬 前,會 自動 呼叫
__del__
方法
- 當使用
-
應用場景
__init__
改造初始化方法,可以讓建立物件更加靈活__del__
如果希望在物件被銷燬前,再做一些事情,可以考慮一下__del__
方法
-
生命週期
- 一個物件從呼叫
類名()
建立,生命週期開始 - 一個物件的
__del__
方法一旦被呼叫,生命週期結束 - 在物件的生命週期內,可以訪問物件屬性,或者讓物件呼叫方法
- 一個物件從呼叫
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 來了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
# tom 是一個全域性變數
tom = Cat("Tom")
print(tom.name)
# del 關鍵字可以刪除一個物件
del tom
print("-" * 50)
5.2 __str__
方法
- 在
Python
中,使用print
輸出 物件變數,預設情況下,會輸出這個變數 引用的物件 是 由哪一個類建立的物件,以及 在記憶體中的地址(十六進位制表示) - 如果在開發中,希望使用
print
輸出 物件變數 時,能夠列印 自定義的內容,就可以利用__str__
這個內建方法了
注意:
__str__
方法必須返回一個字串
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 來了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
def __str__(self):
return "我是小貓:%s" % self.name
tom = Cat("Tom")
print(tom)
面向物件封裝案例
目標
- 封裝
- 小明愛跑步
- 存放傢俱
01. 封裝
- 封裝 是面向物件程式設計的一大特點
- 面向物件程式設計的 第一步 —— 將 屬性 和 方法 封裝 到一個抽象的 類 中
- 外界 使用 類 建立 物件,然後 讓物件呼叫方法
- 物件方法的細節 都被 封裝 在 類的內部
02. 小明愛跑步
需求
- 小明 體重
75.0
公斤 - 小明每次 跑步 會減肥
0.5
公斤 - 小明每次 吃東西 體重增加
1
公斤
提示:在 物件的方法內部,是可以 直接訪問物件的屬性 的!
- 程式碼實現:
class Person:
"""人類"""
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __str__(self):
return "我的名字叫 %s 體重 %.2f 公斤" % (self.name, self.weight)
def run(self):
"""跑步"""
print("%s 愛跑步,跑步鍛鍊身體" % self.name)
self.weight -= 0.5
def eat(self):
"""吃東西"""
print("%s 是吃貨,吃完這頓再減肥" % self.name)
self.weight += 1
xiaoming = Person("小明", 75)
xiaoming.run()
xiaoming.eat()
xiaoming.eat()
print(xiaoming)
2.1 小明愛跑步擴充套件 —— 小美也愛跑步
需求
- 小明 和 小美 都愛跑步
- 小明 體重
75.0
公斤 - 小美 體重
45.0
公斤 - 每次 跑步 都會減少
0.5
公斤 - 每次 吃東西 都會增加
1
公斤
提示
- 在 物件的方法內部,是可以 直接訪問物件的屬性 的
- 同一個類 建立的 多個物件 之間,屬性 互不干擾!
03. 擺放傢俱
需求
- 房子(House) 有 戶型、總面積 和 傢俱名稱列表
- 新房子沒有任何的傢俱
- 傢俱(HouseItem) 有 名字 和 佔地面積,其中
- 席夢思(bed) 佔地
4
平米 - 衣櫃(chest) 佔地
2
平米 - 餐桌(table) 佔地
1.5
平米
- 席夢思(bed) 佔地
- 將以上三件 傢俱 新增 到 房子 中
- 列印房子時,要求輸出:戶型、總面積、剩餘面積、傢俱名稱列表
剩餘面積
- 在建立房子物件時,定義一個 剩餘面積的屬性,初始值和總面積相等
- 當呼叫
add_item
方法,向房間 新增傢俱 時,讓 剩餘面積 -= 傢俱面積
思考:應該先開發哪一個類?
答案 —— 傢俱類
- 傢俱簡單
- 房子要使用到傢俱,被使用的類,通常應該先開發
3.1 建立傢俱
class HouseItem:
def __init__(self, name, area):
"""
:param name: 傢俱名稱
:param area: 佔地面積
"""
self.name = name
self.area = area
def __str__(self):
return "[%s] 佔地面積 %.2f" % (self.name, self.area)
# 1. 建立傢俱
bed = HouseItem("席夢思", 4)
chest = HouseItem("衣櫃", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
小結
- 建立了一個 傢俱類,使用到
__init__
和__str__
兩個內建方法 - 使用 傢俱類 建立了 三個傢俱物件,並且 輸出傢俱資訊
3.2 建立房間
class House:
def __init__(self, house_type, area):
"""
:param house_type: 戶型
:param area: 總面積
"""
self.house_type = house_type
self.area = area
# 剩餘面積預設和總面積一致
self.free_area = area
# 預設沒有任何的傢俱
self.item_list = []
def __str__(self):
# Python 能夠自動的將一對括號內部的程式碼連線在一起
return ("戶型:%s\n總面積:%.2f[剩餘:%.2f]\n傢俱:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要新增 %s" % item)
...
# 2. 建立房子物件
my_home = House("兩室一廳", 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
小結
- 建立了一個 房子類,使用到
__init__
和__str__
兩個內建方法 - 準備了一個
add_item
方法 準備新增傢俱 - 使用 房子類 建立了 一個房子物件
- 讓 房子物件 呼叫了三次
add_item
方法,將 三件傢俱 以實參傳遞到add_item
內部
3.3 新增傢俱
需求
- 1> 判斷 傢俱的面積 是否 超過剩餘面積,如果超過,提示不能新增這件傢俱
- 2> 將 傢俱的名稱 追加到 傢俱名稱列表 中
- 3> 用 房子的剩餘面積 - 傢俱面積
def add_item(self, item):
print("要新增 %s" % item)
# 1. 判斷傢俱面積是否大於剩餘面積
if item.area > self.free_area:
print("%s 的面積太大,不能新增到房子中" % item.name)
return
# 2. 將傢俱的名稱追加到名稱列表中
self.item_list.append(item.name)
# 3. 計算剩餘面積
self.free_area -= item.area
3.4 小結
- 主程式只負責建立 房子 物件和 傢俱 物件
- 讓 房子 物件呼叫
add_item
方法 將傢俱新增到房子中 - 面積計算、剩餘面積、傢俱列表 等處理都被 封裝 到 房子類的內部
面向物件封裝案例 II
目標
- 士兵突擊案例
- 身份運算子
封裝
- 封裝 是面向物件程式設計的一大特點
- 面向物件程式設計的 第一步 —— 將 屬性 和 方法 封裝 到一個抽象的 類 中
- 外界 使用 類 建立 物件,然後 讓物件呼叫方法
- 物件方法的細節 都被 封裝 在 類的內部
一個物件的 屬性 可以是 另外一個類建立的物件
01. 士兵突擊
需求
- 士兵 許三多 有一把 AK47
- 士兵 可以 開火
- 槍 能夠 發射 子彈
- 槍 裝填 裝填子彈 —— 增加子彈數量
1.1 開發槍類
shoot
方法需求
- 1> 判斷是否有子彈,沒有子彈無法射擊
- 2> 使用
print
提示射擊,並且輸出子彈數量
class Gun:
def __init__(self, model):
# 槍的型號
self.model = model
# 子彈數量
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
# 判斷是否還有子彈
if self.bullet_count <= 0:
print("沒有子彈了...")
return
# 發射一顆子彈
self.bullet_count -= 1
print("%s 發射子彈[%d]..." % (self.model, self.bullet_count))
# 建立槍物件
ak47 = Gun("ak47")
ak47.add_bullet(50)
ak47.shoot()
1.2 開發士兵類
假設:每一個新兵 都 沒有槍
定義沒有初始值的屬性
在定義屬性時,如果 不知道設定什麼初始值,可以設定為 None
None
關鍵字 表示 什麼都沒有- 表示一個 空物件,沒有方法和屬性,是一個特殊的常量
- 可以將
None
賦值給任何一個變數
fire
方法需求
- 1> 判斷是否有槍,沒有槍沒法衝鋒
- 2> 喊一聲口號
- 3> 裝填子彈
- 4> 射擊
class Soldier:
def __init__(self, name):
# 姓名
self.name = name
# 槍,士兵初始沒有槍 None 關鍵字表示什麼都沒有
self.gun = None
def fire(self):
# 1. 判斷士兵是否有槍
if self.gun is None:
print("[%s] 還沒有槍..." % self.name)
return
# 2. 高喊口號
print("衝啊...[%s]" % self.name)
# 3. 讓槍裝填子彈
self.gun.add_bullet(50)
# 4. 讓槍發射子彈
self.gun.shoot()
小結
- 建立了一個 士兵類,使用到
__init__
內建方法 - 在定義屬性時,如果 不知道設定什麼初始值,可以設定為
None
- 在 封裝的 方法內部,還可以讓 自己的 使用其他類建立的物件屬性 呼叫已經 封裝好的方法
02. 身份運算子
身份運算子用於 比較 兩個物件的 記憶體地址 是否一致 —— 是否是對同一個物件的引用
is 與 == 區別:
is
用於判斷 兩個變數 引用物件是否為同一個
==
用於判斷 引用變數的值 是否相等
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a
False
>>> b == a
True
私有屬性和私有方法
01. 應用場景及定義方式
應用場景
- 在實際開發中,物件 的 某些屬性或方法 可能只希望 在物件的內部被使用,而 不希望在外部被訪問到
- 私有屬性 就是 物件 不希望公開的 屬性
- 私有方法 就是 物件 不希望公開的 方法
定義方式
- 在 定義屬性或方法時,在 屬性名或者方法名前 增加 兩個下劃線,定義的就是 私有 屬性或方法
class Women:
def __init__(self, name):
self.name = name
# 不要問女生的年齡
self.__age = 18
def __secret(self):
print("我的年齡是 %d" % self.__age)
xiaofang = Women("小芳")
# 私有屬性,外部不能直接訪問
# print(xiaofang.__age)
# 私有方法,外部不能直接呼叫
# xiaofang.__secret()
02. 偽私有屬性和私有方法(科普)
提示:在日常開發中,不要使用這種方式,訪問物件的 私有屬性 或 私有方法
Python
中,並沒有 真正意義 的 私有
- 在給 屬性、方法 命名時,實際是對 名稱 做了一些特殊處理,使得外界無法訪問到
- 處理方式:在 名稱 前面加上
_類名
=>_類名__名稱
# 私有屬性,外部不能直接訪問到
print(xiaofang._Women__age)
# 私有方法,外部不能直接呼叫
xiaofang._Women__secret()
單例
目標
- 單例設計模式
__new__
方法- Python 中的單例
01. 單例設計模式
-
設計模式
- 設計模式 是 前人工作的總結和提煉,通常,被人們廣泛流傳的設計模式都是針對 某一特定問題 的成熟的解決方案
- 使用 設計模式 是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性
-
單例設計模式
- 目的 —— 讓 類 建立的物件,在系統中 只有 唯一的一個例項
- 每一次執行
類名()
返回的物件,記憶體地址是相同的
單例設計模式的應用場景
- 音樂播放 物件
- 回收站 物件
- 印表機 物件
- ……
02. __new__
方法
- 使用 類名() 建立物件時,
Python
的直譯器 首先 會 呼叫__new__
方法為物件 分配空間 __new__
是一個 由object
基類提供的 內建的靜態方法,主要作用有兩個:-
- 在記憶體中為物件 分配空間
-
- 返回 物件的引用
-
Python
的直譯器獲得物件的 引用 後,將引用作為 第一個引數,傳遞給__init__
方法
重寫
__new__
方法 的程式碼非常固定!
- 重寫
__new__
方法 一定要return super().__new__(cls)
- 否則 Python 的直譯器 得不到 分配了空間的 物件引用,就不會呼叫物件的初始化方法
- 注意:
__new__
是一個靜態方法,在呼叫時需要 主動傳遞cls
引數
示例程式碼
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
# 如果不返回任何結果,
return super().__new__(cls)
def __init__(self):
print("初始化音樂播放物件")
player = MusicPlayer()
print(player)
03. Python 中的單例
- 單例 —— 讓 類 建立的物件,在系統中 只有 唯一的一個例項
- 定義一個 類屬性,初始值是
None
,用於記錄 單例物件的引用 - 重寫
__new__
方法 - 如果 類屬性
is None
,呼叫父類方法分配空間,並在類屬性中記錄結果 - 返回 類屬性 中記錄的 物件引用
- 定義一個 類屬性,初始值是
class MusicPlayer(object):
# 定義類屬性記錄單例物件引用
instance = None
def __new__(cls, *args, **kwargs):
# 1. 判斷類屬性是否已經被賦值
if cls.instance is None:
cls.instance = super().__new__(cls)
# 2. 返回類屬性的單例引用
return cls.instance
只執行一次初始化工作
- 在每次使用
類名()
建立物件時,Python
的直譯器都會自動呼叫兩個方法:__new__
分配空間__init__
物件初始化
- 在上一小節對
__new__
方法改造之後,每次都會得到 第一次被建立物件的引用 - 但是:初始化方法還會被再次呼叫
需求
- 讓 初始化動作 只被 執行一次
解決辦法
- 定義一個類屬性
init_flag
標記是否 執行過初始化動作,初始值為False
- 在
__init__
方法中,判斷init_flag
,如果為False
就執行初始化動作 - 然後將
init_flag
設定為True
- 這樣,再次 自動 呼叫
__init__
方法時,初始化動作就不會被再次執行 了
class MusicPlayer(object):
# 記錄第一個被建立物件的引用
instance = None
# 記錄是否執行過初始化動作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1. 判斷類屬性是否是空物件
if cls.instance is None:
# 2. 呼叫父類的方法,為第一個物件分配空間
cls.instance = super().__new__(cls)
# 3. 返回類屬性儲存的物件引用
return cls.instance
def __init__(self):
if not MusicPlayer.init_flag:
print("初始化音樂播放器")
MusicPlayer.init_flag = True
# 建立多個物件
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
多型
目標
- 多型
面向物件三大特性
-
封裝 根據 職責 將 屬性 和 方法 封裝 到一個抽象的 類 中
- 定義類的準則
-
繼承 實現程式碼的重用,相同的程式碼不需要重複的編寫
- 設計類的技巧
- 子類針對自己特有的需求,編寫特定的程式碼
-
多型 不同的 子類物件 呼叫相同的 父類方法,產生不同的執行結果
- 多型 可以 增加程式碼的靈活度
- 以 繼承 和 重寫父類方法 為前提
- 是呼叫方法的技巧,不會影響到類的內部設計
多型案例演練
需求
- 在
Dog
類中封裝方法game
- 普通狗只是簡單的玩耍
- 定義
XiaoTianDog
繼承自Dog
,並且重寫game
方法- 哮天犬需要在天上玩耍
- 定義
Person
類,並且封裝一個 和狗玩 的方法- 在方法內部,直接讓 狗物件 呼叫
game
方法
- 在方法內部,直接讓 狗物件 呼叫
案例小結
Person
類中只需要讓 狗物件 呼叫game
方法,而不關心具體是 什麼狗game
方法是在Dog
父類中定義的
- 在程式執行時,傳入不同的 狗物件 實參,就會產生不同的執行效果
多型 更容易編寫出出通用的程式碼,做出通用的程式設計,以適應需求的不斷變化!
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianDog(Dog):
def game(self):
print("%s 飛到天上去玩耍..." % self.name)
class Person(object):
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s 和 %s 快樂的玩耍..." % (self.name, dog.name))
# 讓狗玩耍
dog.game()
# 1. 建立一個狗物件
# wangcai = Dog("旺財")
wangcai = XiaoTianDog("飛天旺財")
# 2. 建立一個小明物件
xiaoming = Person("小明")
# 3. 讓小明呼叫和狗玩的方法
xiaoming.game_with_dog(wangcai)
繼承
目標
- 單繼承
- 多繼承
面向物件三大特性
- 封裝 根據 職責 將 屬性 和 方法 封裝 到一個抽象的 類 中
- 繼承 實現程式碼的重用,相同的程式碼不需要重複的編寫
- 多型 不同的物件呼叫相同的方法,產生不同的執行結果,增加程式碼的靈活度
01. 單繼承
1.1 繼承的概念、語法和特點
繼承的概念:子類 擁有 父類 的所有 方法 和 屬性
1) 繼承的語法
class 類名(父類名):
pass
- 子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
- 子類 中應該根據 職責,封裝 子類特有的 屬性和方法
2) 專業術語
Dog
類是Animal
類的子類,Animal
類是Dog
類的父類,Dog
類從Animal
類繼承Dog
類是Animal
類的派生類,Animal
類是Dog
類的基類,Dog
類從Animal
類派生
3) 繼承的傳遞性
C
類從B
類繼承,B
類又從A
類繼承- 那麼
C
類就具有B
類和A
類的所有屬性和方法
子類 擁有 父類 以及 父類的父類 中封裝的所有 屬性 和 方法
提問
哮天犬 能夠呼叫 Cat
類中定義的 catch
方法嗎?
答案
不能,因為 哮天犬 和 Cat
之間沒有 繼承 關係
1.2 方法的重寫
- 子類 擁有 父類 的所有 方法 和 屬性
- 子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
應用場景
- 當 父類 的方法實現不能滿足子類需求時,可以對方法進行 重寫(override)
重寫 父類方法有兩種情況:
- 覆蓋 父類的方法
- 對父類方法進行 擴充套件
1) 覆蓋父類的方法
- 如果在開發中,父類的方法實現 和 子類的方法實現,完全不同
- 就可以使用 覆蓋 的方式,在子類中 重新編寫 父類的方法實現
具體的實現方式,就相當於在 子類中 定義了一個 和父類同名的方法並且實現
重寫之後,在執行時,只會呼叫 子類中重寫的方法,而不再會呼叫 父類封裝的方法
2) 對父類方法進行 擴充套件
- 如果在開發中,子類的方法實現 中 包含 父類的方法實現
- 父類原本封裝的方法實現 是 子類方法的一部分
- 就可以使用 擴充套件 的方式
- 在子類中 重寫 父類的方法
- 在需要的位置使用
super().父類方法
來呼叫父類方法的執行 - 程式碼其他的位置針對子類的需求,編寫 子類特有的程式碼實現
關於 super
- 在
Python
中super
是一個 特殊的類 super()
就是使用super
類創建出來的物件- 最常 使用的場景就是在 重寫父類方法時,呼叫 在父類中封裝的方法實現
呼叫父類方法的另外一種方式(知道)
在
Python 2.x
時,如果需要呼叫父類的方法,還可以使用以下方式:
父類名.方法(self)
- 這種方式,目前在
Python 3.x
還支援這種方式 - 這種方法 不推薦使用,因為一旦 父類發生變化,方法呼叫位置的 類名 同樣需要修改
提示
- 在開發時,
父類名
和super()
兩種方式不要混用 - 如果使用 當前子類名 呼叫方法,會形成遞迴呼叫,出現死迴圈
1.3 父類的 私有屬性 和 私有方法
- 子類物件 不能 在自己的方法內部,直接 訪問 父類的 私有屬性 或 私有方法
- 子類物件 可以通過 父類 的 公有方法 間接 訪問到 私有屬性 或 私有方法
- 私有屬性、方法 是物件的隱私,不對外公開,外界 以及 子類 都不能直接訪問
- 私有屬性、方法 通常用於做一些內部的事情
示例
B
的物件不能直接訪問__num2
屬性B
的物件不能在demo
方法內訪問__num2
屬性B
的物件可以在demo
方法內,呼叫父類的test
方法- 父類的
test
方法內部,能夠訪問__num2
屬性和__test
方法
02. 多繼承
概念
- 子類 可以擁有 多個父類,並且具有 所有父類 的 屬性 和 方法
- 例如:孩子 會繼承自己 父親 和 母親 的 特性
語法
class 子類名(父類名1, 父類名2...)
pass
2.1 多繼承的使用注意事項
問題的提出
- 如果 不同的父類 中存在 同名的方法,子類物件 在呼叫方法時,會呼叫 哪一個父類中的方法呢?
提示:開發時,應該儘量避免這種容易產生混淆的情況! —— 如果 父類之間 存在 同名的屬性或者方法,應該 儘量避免 使用多繼承
Python 中的 MRO —— 方法搜尋順序(知道)
Python
中針對 類 提供了一個 內建屬性__mro__
可以檢視 方法 搜尋順序- MRO 是
method resolution order
,主要用於 在多繼承時判斷 方法、屬性 的呼叫 路徑
print(C.__mro__)
輸出結果
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
- 在搜尋方法時,是按照
__mro__
的輸出結果 從左至右 的順序查詢的 - 如果在當前類中 找到方法,就直接執行,不再搜尋
- 如果 沒有找到,就查詢下一個類 中是否有對應的方法,如果找到,就直接執行,不再搜尋
- 如果找到最後一個類,還沒有找到方法,程式報錯
2.2 新式類與舊式(經典)類
object
是Python
為所有物件提供的 基類,提供有一些內建的屬性和方法,可以使用dir
函式檢視
-
新式類:以
object
為基類的類,推薦使用 -
經典類:不以
object
為基類的類,不推薦使用 -
在
Python 3.x
中定義類時,如果沒有指定父類,會 預設使用object
作為該類的 基類 ——Python 3.x
中定義的類都是 新式類 -
在
Python 2.x
中定義類時,如果沒有指定父類,則不會以object
作為 基類
新式類 和 經典類 在多繼承時 —— 會影響到方法的搜尋順序
為了保證編寫的程式碼能夠同時在 Python 2.x
和 Python 3.x
執行!
今後在定義類時,如果沒有父類,建議統一繼承自 object
class 類名(object):
pass
類屬性和類方法
目標
- 類的結構
- 類屬性和例項屬性
- 類方法和靜態方法
01. 類的結構
1.1 術語 —— 例項
- 使用面相物件開發,第 1 步 是設計 類
- 使用 類名() 建立物件,建立物件 的動作有兩步:
-
- 在記憶體中為物件 分配空間
-
- 呼叫初始化方法
__init__
為 物件初始化
- 呼叫初始化方法
-
- 物件建立後,記憶體 中就有了一個物件的 實實在在 的存在 —— 例項
因此,通常也會把:
- 創建出來的 物件 叫做 類 的 例項
- 建立物件的 動作 叫做 例項化
- 物件的屬性 叫做 例項屬性
- 物件呼叫的方法 叫做 例項方法
在程式執行時:
- 物件各自擁有自己的 例項屬性
- 呼叫物件方法,可以通過
self.
- 訪問自己的屬性
- 呼叫自己的方法
結論
- 每一個物件 都有自己 獨立的記憶體空間,儲存各自不同的屬性
- 多個物件的方法,在記憶體中只有一份,在呼叫方法時,需要把物件的引用 傳遞到方法內部
1.2 類是一個特殊的物件
Python
中 一切皆物件:
class AAA:
定義的類屬於 類物件obj1 = AAA()
屬於 例項物件
- 在程式執行時,類 同樣 會被載入到記憶體
- 在
Python
中,類 是一個特殊的物件 —— 類物件 - 在程式執行時,類物件 在記憶體中 只有一份,使用 一個類 可以創建出 很多個物件例項
- 除了封裝 例項 的 屬性 和 方法外,類物件 還可以擁有自己的 屬性 和 方法
- 類屬性
- 類方法
- 通過 類名. 的方式可以 訪問類的屬性 或者 呼叫類的方法
02. 類屬性和例項屬性
2.1 概念和使用
- 類屬性 就是給 類物件 中定義的 屬性
- 通常用來記錄 與這個類相關 的特徵
- 類屬性 不會用於記錄 具體物件的特徵
示例需求
- 定義一個 工具類
- 每件工具都有自己的
name
- 需求 —— 知道使用這個類,建立了多少個工具物件?
class Tool(object):
# 使用賦值語句,定義類屬性,記錄建立工具物件的總數
count = 0
def __init__(self, name):
self.name = name
# 針對類屬性做一個計數+1
Tool.count += 1
# 建立工具物件
tool1 = Tool("斧頭")
tool2 = Tool("榔頭")
tool3 = Tool("鐵鍬")
# 知道使用 Tool 類到底建立了多少個物件?
print("現在建立了 %d 個工具" % Tool.count)
2.2 屬性的獲取機制(科普)
-
在
Python
中 屬性的獲取 存在一個 向上查詢機制 -
因此,要訪問類屬性有兩種方式:
- 類名.類屬性
- 物件.類屬性 (不推薦)
注意
- 如果使用
物件.類屬性 = 值
賦值語句,只會 給物件新增一個屬性,而不會影響到 類屬性的值
03. 類方法和靜態方法
3.1 類方法
- 類屬性 就是針對 類物件 定義的屬性
- 使用 賦值語句 在
class
關鍵字下方可以定義 類屬性 - 類屬性 用於記錄 與這個類相關 的特徵
- 使用 賦值語句 在
- 類方法 就是針對 類物件 定義的方法
- 在 類方法 內部可以直接訪問 類屬性 或者呼叫其他的 類方法
語法如下
@classmethod
def 類方法名(cls):
pass
- 類方法需要用 修飾器
@classmethod
來標識,告訴直譯器這是一個類方法 - 類方法的 第一個引數 應該是
cls
- 由 哪一個類 呼叫的方法,方法內的
cls
就是 哪一個類的引用 - 這個引數和 例項方法 的第一個引數是
self
類似 - 提示 使用其他名稱也可以,不過習慣使用
cls
- 由 哪一個類 呼叫的方法,方法內的
- 通過 類名. 呼叫 類方法,呼叫方法時,不需要傳遞
cls
引數 - 在方法內部
- 可以通過
cls.
訪問類的屬性 - 也可以通過
cls.
呼叫其他的類方法
- 可以通過
示例需求
- 定義一個 工具類
- 每件工具都有自己的
name
- 需求 —— 在 類 封裝一個
show_tool_count
的類方法,輸出使用當前這個類,建立的物件個數
@classmethod
def show_tool_count(cls):
"""顯示工具物件的總數"""
print("工具物件的總數 %d" % cls.count)
在類方法內部,可以直接使用
cls
訪問 類屬性 或者 呼叫類方法
3.2 靜態方法
-
在開發時,如果需要在 類 中封裝一個方法,這個方法:
- 既 不需要 訪問 例項屬性 或者呼叫 例項方法
- 也 不需要 訪問 類屬性 或者呼叫 類方法
-
這個時候,可以把這個方法封裝成一個 靜態方法
語法如下
@staticmethod
def 靜態方法名():
pass
- 靜態方法 需要用 修飾器
@staticmethod
來標識,告訴直譯器這是一個靜態方法 - 通過 類名. 呼叫 靜態方法
class Dog(object):
# 狗物件計數
dog_count = 0
@staticmethod
def run():
# 不需要訪問例項屬性也不需要訪問類屬性的方法
print("狗在跑...")
def __init__(self, name):
self.name = name
3.3 方法綜合案例
需求
- 設計一個
Game
類 - 屬性:
- 定義一個 類屬性
top_score
記錄遊戲的 歷史最高分 - 定義一個 例項屬性
player_name
記錄 當前遊戲的玩家姓名
- 定義一個 類屬性
- 方法:
- 靜態方法
show_help
顯示遊戲幫助資訊 - 類方法
show_top_score
顯示歷史最高分 - 例項方法
start_game
開始當前玩家的遊戲
- 靜態方法
- 主程式步驟
-
- 檢視幫助資訊
-
- 檢視歷史最高分
-
- 建立遊戲物件,開始遊戲
-
案例小結
- 例項方法 —— 方法內部需要訪問 例項屬性
- 例項方法 內部可以使用 類名. 訪問類屬性
- 類方法 —— 方法內部 只 需要訪問 類屬性
- 靜態方法 —— 方法內部,不需要訪問 例項屬性 和 類屬性
提問
如果方法內部 即需要訪問 例項屬性,又需要訪問 類屬性,應該定義成什麼方法?
答案
- 應該定義 例項方法
- 因為,類只有一個,在 例項方法 內部可以使用 類名. 訪問類屬性
class Game(object):
# 遊戲最高分,類屬性
top_score = 0
@staticmethod
def show_help():
print("幫助資訊:讓殭屍走進房間")
@classmethod
def show_top_score(cls):
print("遊戲最高分是 %d" % cls.top_score)
def __init__(self, player_name):
self.player_name = player_name
def start_game(self):
print("[%s] 開始遊戲..." % self.player_name)
# 使用類名.修改歷史最高分
Game.top_score = 999
# 1. 檢視遊戲幫助
Game.show_help()
# 2. 檢視遊戲最高分
Game.show_top_score()
# 3. 建立遊戲物件,開始遊戲
game = Game("小明")
game.start_game()
# 4. 遊戲結束,檢視遊戲最高分
Game.show_top_score()
基本的數學計算
In [1]: eval("1 + 1")
Out[1]: 2
字串重複
In [2]: eval("'' * 10")
Out[2]: '*********'
將字串轉換成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list
將字串轉換成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict
模組和包
目標
- 模組
- 包
- 釋出模組
01. 模組
1.1 模組的概念
模組是 Python 程式架構的一個核心概念
- 每一個以副檔名
py
結尾的Python
原始碼檔案都是一個 模組 - 模組名 同樣也是一個 識別符號,需要符合識別符號的命名規則
- 在模組中定義的 全域性變數 、函式、類 都是提供給外界直接使用的 工具
- 模組 就好比是 工具包,要想使用這個工具包中的工具,就需要先 匯入 這個模組
1.2 模組的兩種匯入方式
1)import 匯入
import 模組名1, 模組名2
提示:在匯入模組時,每個匯入應該獨佔一行
import 模組名1
import 模組名2
- 匯入之後
- 通過
模組名.
使用 模組提供的工具 —— 全域性變數、函式、類
- 通過
使用 as
指定模組的別名
如果模組的名字太長,可以使用
as
指定模組的名稱,以方便在程式碼中的使用
import 模組名1 as 模組別名
注意:模組別名 應該符合 大駝峰命名法
2)from...import 匯入
- 如果希望 從某一個模組 中,匯入 部分 工具,就可以使用
from ... import
的方式 import 模組名
是 一次性 把模組中 所有工具全部匯入,並且通過 模組名/別名 訪問
# 從 模組 匯入 某一個工具
from 模組名1 import 工具名
- 匯入之後
- 不需要 通過
模組名.
- 可以直接使用 模組提供的工具 —— 全域性變數、函式、類
- 不需要 通過
注意
如果 兩個模組,存在 同名的函式,那麼 後匯入模組的函式,會 覆蓋掉先匯入的函式
- 開發時
import
程式碼應該統一寫在 程式碼的頂部,更容易及時發現衝突 - 一旦發現衝突,可以使用
as
關鍵字 給其中一個工具起一個別名
from...import *(知道)
# 從 模組 匯入 所有工具
from 模組名1 import *
注意
這種方式不推薦使用,因為函式重名並沒有任何的提示,出現問題不好排查
1.3 模組的搜尋順序[擴充套件]
Python
的直譯器在 匯入模組 時,會:
- 搜尋 當前目錄 指定模組名的檔案,如果有就直接匯入
- 如果沒有,再搜尋 系統目錄
在開發時,給檔案起名,不要和 系統的模組檔案 重名
Python
中每一個模組都有一個內建屬性 __file__
可以 檢視模組 的 完整路徑
示例
import random
# 生成一個 0~10 的數字
rand = random.randint(0, 10)
print(rand)
注意:如果當前目錄下,存在一個
random.py
的檔案,程式就無法正常執行了!
- 這個時候,
Python
的直譯器會 載入當前目錄 下的random.py
而不會載入 系統的random
模組
1.4 原則 —— 每一個檔案都應該是可以被匯入的
- 一個 獨立的
Python
檔案 就是一個 模組 - 在匯入檔案時,檔案中 所有沒有任何縮排的程式碼 都會被執行一遍!
實際開發場景
- 在實際開發中,每一個模組都是獨立開發的,大多都有專人負責
- 開發人員 通常會在 模組下方 增加一些測試程式碼
- 僅在模組內使用,而被匯入到其他檔案中不需要執行
__name__
屬性
__name__
屬性可以做到,測試模組的程式碼 只在測試情況下被執行,而在 被匯入時不會被執行!
__name__
是Python
的一個內建屬性,記錄著一個 字串- 如果 是被其他檔案匯入的,
__name__
就是 模組名 - 如果 是當前執行的程式
__name__
是__main__
在很多 Python
檔案中都會看到以下格式的程式碼:
# 匯入模組
# 定義全域性變數
# 定義類
# 定義函式
# 在程式碼的最下方
def main():
# ...
pass
# 根據 __name__ 判斷是否執行下方程式碼
if __name__ == "__main__":
main()
02. 包(Package)
概念
- 包 是一個 包含多個模組 的 特殊目錄
- 目錄下有一個 特殊的檔案
__init__.py
- 包名的 命名方式 和變數名一致,小寫字母 +
_
好處
- 使用
import 包名
可以一次性匯入 包 中 所有的模組
案例演練
- 新建一個
hm_message
的 包 - 在目錄下,新建兩個檔案
send_message
和receive_message
- 在
send_message
檔案中定義一個send
函式 - 在
receive_message
檔案中定義一個receive
函式 - 在外部直接匯入
hm_message
的包
__init__.py
- 要在外界使用 包 中的模組,需要在
__init__.py
中指定 對外界提供的模組列表
# 從 當前目錄 匯入 模組列表
from . import send_message
from . import receive_message
03. 釋出模組(知道)
- 如果希望自己開發的模組,分享 給其他人,可以按照以下步驟操作
3.1 製作釋出壓縮包步驟
1) 建立 setup.py
setup.py
的檔案
from distutils.core import setup
setup(name="hm_message", # 包名
version="1.0", # 版本
description="itheima's 傳送和接收訊息模組", # 描述資訊
long_description="完整的傳送和接收訊息模組", # 完整描述資訊
author="itheima", # 作者
author_email="[email protected]", # 作者郵箱
url="www.itheima.com", # 主頁
py_modules=["hm_message.send_message",
"hm_message.receive_message"])
有關字典引數的詳細資訊,可以參閱官方網站:
https://docs.python.org/2/distutils/apiref.html
2) 構建模組
$ python3 setup.py build
3) 生成釋出壓縮包
$ python3 setup.py sdist
注意:要製作哪個版本的模組,就使用哪個版本的直譯器執行!
3.2 安裝模組
$ tar -zxvf hm_message-1.0.tar.gz
$ sudo python3 setup.py install
解除安裝模組
直接從安裝目錄下,把安裝模組的 目錄 刪除就可以
$ cd /usr/local/lib/python3.5/dist-packages/
$ sudo rm -r hm_message*
3.3 pip
安裝第三方模組
- 第三方模組 通常是指由 知名的第三方團隊 開發的 並且被 程式設計師廣泛使用 的
Python
包 / 模組- 例如
pygame
就是一套非常成熟的 遊戲開發模組
- 例如
pip
是一個現代的,通用的Python
包管理工具- 提供了對
Python
包的查詢、下載、安裝、解除安裝等功能
安裝和解除安裝命令如下:
# 將模組安裝到 Python 2.x 環境
$ sudo pip install pygame
$ sudo pip uninstall pygame
# 將模組安裝到 Python 3.x 環境
$ sudo pip3 install pygame
$ sudo pip3 uninstall pygame
在 Mac
下安裝 iPython
$ sudo pip install ipython
在 Linux
下安裝 iPython
$ sudo apt install ipython
$ sudo apt install ipython3
檔案
目標
- 檔案的概念
- 檔案的基本操作
- 檔案/資料夾的常用操作
- 文字檔案的編碼方式
01. 檔案的概念
1.1 檔案的概念和作用
- 計算機的 檔案,就是儲存在某種 長期儲存裝置 上的一段 資料
- 長期儲存裝置包括:硬碟、U 盤、行動硬碟、光碟...
檔案的作用
將資料長期儲存下來,在需要的時候使用
1.2 檔案的儲存方式
- 在計算機中,檔案是以 二進位制 的方式儲存在磁碟上的
文字檔案和二進位制檔案
-
文字檔案
- 可以使用 文字編輯軟體 檢視
- 本質上還是二進位制檔案
- 例如:python 的源程式
-
二進位制檔案
- 儲存的內容 不是給人直接閱讀的,而是 提供給其他軟體使用的
- 例如:圖片檔案、音訊檔案、視訊檔案等等
- 二進位制檔案不能使用 文字編輯軟體 檢視
02. 檔案的基本操作
2.1 操作檔案的套路
在 計算機 中要操作檔案的套路非常固定,一共包含三個步驟:
- 開啟檔案
- 讀、寫檔案
- 讀 將檔案內容讀入記憶體
- 寫 將記憶體內容寫入檔案
- 關閉檔案
2.2 操作檔案的函式/方法
- 在
Python
中要操作檔案需要記住 1 個函式和 3 個方法
序號 | 函式/方法 | 說明 |
---|---|---|
01 | open | 開啟檔案,並且返回檔案操作物件 |
02 | read | 將檔案內容讀取到記憶體 |
03 | write | 將指定內容寫入檔案 |
04 | close | 關閉檔案 |
open
函式負責開啟檔案,並且返回檔案物件read
/write
/close
三個方法都需要通過 檔案物件 來呼叫
2.3 read 方法 —— 讀取檔案
open
函式的第一個引數是要開啟的檔名(檔名區分大小寫)- 如果檔案 存在,返回 檔案操作物件
- 如果檔案 不存在,會 丟擲異常
read
方法可以一次性 讀入 並 返回 檔案的 所有內容close
方法負責 關閉檔案- 如果 忘記關閉檔案,會造成系統資源消耗,而且會影響到後續對檔案的訪問
- 注意:
read
方法執行後,會把 檔案指標 移動到 檔案的末尾
# 1. 開啟 - 檔名需要注意大小寫
file = open("README")
# 2. 讀取
text = file.read()
print(text)
# 3. 關閉
file.close()
提示
- 在開發中,通常會先編寫 開啟 和 關閉 的程式碼,再編寫中間針對檔案的 讀/寫 操作!
檔案指標(知道)
- 檔案指標 標記 從哪個位置開始讀取資料
- 第一次開啟 檔案時,通常 檔案指標會指向檔案的開始位置
- 當執行了
read
方法後,檔案指標 會移動到 讀取內容的末尾- 預設情況下會移動到 檔案末尾
思考
- 如果執行了一次
read
方法,讀取了所有內容,那麼再次呼叫read
方法,還能夠獲得到內容嗎?
答案
- 不能
- 第一次讀取之後,檔案指標移動到了檔案末尾,再次呼叫不會讀取到任何的內容
2.4 開啟檔案的方式
open
函式預設以 只讀方式 開啟檔案,並且返回檔案物件
語法如下:
f = open("檔名", "訪問方式")
訪問方式 | 說明 |
---|---|
r | 以只讀方式開啟檔案。檔案的指標將會放在檔案的開頭,這是預設模式。如果檔案不存在,丟擲異常 |
w | 以只寫方式開啟檔案。如果檔案存在會被覆蓋。如果檔案不存在,建立新檔案 |
a | 以追加方式開啟檔案。如果該檔案已存在,檔案指標將會放在檔案的結尾。如果檔案不存在,建立新檔案進行寫入 |
r+ | 以讀寫方式開啟檔案。檔案的指標將會放在檔案的開頭。如果檔案不存在,丟擲異常 |
w+ | 以讀寫方式開啟檔案。如果檔案存在會被覆蓋。如果檔案不存在,建立新檔案 |
a+ | 以讀寫方式開啟檔案。如果該檔案已存在,檔案指標將會放在檔案的結尾。如果檔案不存在,建立新檔案進行寫入 |
提示
- 頻繁的移動檔案指標,會影響檔案的讀寫效率,開發中更多的時候會以 只讀、只寫 的方式來操作檔案
寫入檔案示例
# 開啟檔案
f = open("README", "w")
f.write("hello python!\n")
f.write("今天天氣真好")
# 關閉檔案
f.close()
2.5 按行讀取檔案內容
read
方法預設會把檔案的 所有內容 一次性讀取到記憶體- 如果檔案太大,對記憶體的佔用會非常嚴重
readline
方法
readline
方法可以一次讀取一行內容- 方法執行後,會把 檔案指標 移動到下一行,準備再次讀取
讀取大檔案的正確姿勢
# 開啟檔案
file = open("README")
while True:
# 讀取一行內容
text = file.readline()
# 判斷是否讀到內容
if not text:
break
# 每讀取一行的末尾已經有了一個 `\n`
print(text, end="")
# 關閉檔案
file.close()
2.6 檔案讀寫案例 —— 複製檔案
目標
用程式碼的方式,來實現檔案複製過程
小檔案複製
- 開啟一個已有檔案,讀取完整內容,並寫入到另外一個檔案
# 1. 開啟檔案
file_read = open("README")
file_write = open("README[復件]", "w")
# 2. 讀取並寫入檔案
text = file_read.read()
file_write.write(text)
# 3. 關閉檔案
file_read.close()
file_write.close()
大檔案複製
- 開啟一個已有檔案,逐行讀取內容,並順序寫入到另外一個檔案
# 1. 開啟檔案
file_read = open("README")
file_write = open("README[復件]", "w")
# 2. 讀取並寫入檔案
while True:
# 每次讀取一行
text = file_read.readline()
# 判斷是否讀取到內容
if not text:
break
file_write.write(text)
# 3. 關閉檔案
file_read.close()
file_write.close()
03. 檔案/目錄的常用管理操作
- 在 終端 / 檔案瀏覽器、 中可以執行常規的 檔案 / 目錄 管理操作,例如:
- 建立、重新命名、刪除、改變路徑、檢視目錄內容、……
- 在
Python
中,如果希望通過程式實現上述功能,需要匯入os
模組
檔案操作
序號 | 方法名 | 說明 | 示例 |
---|---|---|---|
01 | rename | 重新命名檔案 | os.rename(原始檔名, 目標檔名) |
02 | remove | 刪除檔案 | os.remove(檔名) |
目錄操作
序號 | 方法名 | 說明 | 示例 |
---|---|---|---|
01 | listdir | 目錄列表 | os.listdir(目錄名) |
02 | mkdir | 建立目錄 | os.mkdir(目錄名) |
03 | rmdir | 刪除目錄 | os.rmdir(目錄名) |
04 | getcwd | 獲取當前目錄 | os.getcwd() |
05 | chdir | 修改工作目錄 | os.chdir(目標目錄) |
06 | path.isdir | 判斷是否是檔案 | os.path.isdir(檔案路徑) |
提示:檔案或者目錄操作都支援 相對路徑 和 絕對路徑
04. 文字檔案的編碼格式(科普)
- 文字檔案儲存的內容是基於 字元編碼 的檔案,常見的編碼有
ASCII
編碼,UNICODE
編碼等
Python 2.x 預設使用
ASCII
編碼格式
Python 3.x 預設使用UTF-8
編碼格式
4.1 ASCII 編碼和 UNICODE 編碼
ASCII
編碼
- 計算機中只有
256
個ASCII
字元 - 一個
ASCII
在記憶體中佔用 1 個位元組 的空間8
個0/1
的排列組合方式一共有256
種,也就是2 ** 8
UTF-8
編碼格式
- 計算機中使用 1~6 個位元組 來表示一個
UTF-8
字元,涵蓋了 地球上幾乎所有地區的文字 - 大多數漢字會使用 3 個位元組 表示
UTF-8
是UNICODE
編碼的一種編碼格式
4.2 Ptyhon 2.x 中如何使用中文
Python 2.x 預設使用
ASCII
編碼格式
Python 3.x 預設使用UTF-8
編碼格式
- 在 Python 2.x 檔案的 第一行 增加以下程式碼,直譯器會以
utf-8
編碼來處理 python 檔案
# *-* coding:utf8 *-*
這方式是官方推薦使用的!
- 也可以使用
# coding=utf8
unicode 字串
- 在
Python 2.x
中,即使指定了檔案使用UTF-8
的編碼格式,但是在遍歷字串時,仍然會 以位元組為單位遍歷 字串 - 要能夠 正確的遍歷字串,在定義字串時,需要 在字串的引號前,增加一個小寫字母
u
,告訴直譯器這是一個unicode
字串(使用UTF-8
編碼格式的字串)
# *-* coding:utf8 *-*
# 在字串前,增加一個 `u` 表示這個字串是一個 utf8 字串
hello_str = u"你好世界"
print(hello_str)
for c in hello_str:
print(c)
異常
目標
- 異常的概念
- 捕獲異常
- 異常的傳遞
- 丟擲異常
01. 異常的概念
- 程式在執行時,如果
Python 直譯器
遇到 到一個錯誤,會停止程式的執行,並且提示一些錯誤資訊,這就是 異常 - 程式停止執行並且提示錯誤資訊 這個動作,我們通常稱之為:丟擲(raise)異常
程式開發時,很難將 所有的特殊情況 都處理的面面俱到,通過 異常捕獲 可以針對突發事件做集中的處理,從而保證程式的 穩定性和健壯性
02. 捕獲異常
2.1 簡單的捕獲異常語法
- 在程式開發中,如果 對某些程式碼的執行不能確定是否正確,可以增加
try(嘗試)
來 捕獲異常 - 捕獲異常最簡單的語法格式:
try:
嘗試執行的程式碼
except:
出現錯誤的處理
try
嘗試,下方編寫要嘗試程式碼,不確定是否能夠正常執行的程式碼except
如果不是,下方編寫嘗試失敗的程式碼
簡單異常捕獲演練 —— 要求使用者輸入整數
try:
# 提示使用者輸入一個數字
num = int(input("請輸入數字:"))
except:
print("請輸入正確的數字")
2.2 錯誤型別捕獲
-
在程式執行時,可能會遇到 不同型別的異常,並且需要 針對不同型別的異常,做出不同的響應,這個時候,就需要捕獲錯誤型別了
-
語法如下:
try:
# 嘗試執行的程式碼
pass
except 錯誤型別1:
# 針對錯誤型別1,對應的程式碼處理
pass
except (錯誤型別2, 錯誤型別3):
# 針對錯誤型別2 和 3,對應的程式碼處理
pass
except Exception as result:
print("未知錯誤 %s" % result)
- 當
Python
直譯器 丟擲異常 時,最後一行錯誤資訊的第一個單詞,就是錯誤型別
異常型別捕獲演練 —— 要求使用者輸入整數
需求
- 提示使用者輸入一個整數
- 使用
8
除以使用者輸入的整數並且輸出
try:
num = int(input("請輸入整數:"))
result = 8 / num
print(result)
except ValueError:
print("請輸入正確的整數")
except ZeroDivisionError:
print("除 0 錯誤")
捕獲未知錯誤
- 在開發時,要預判到所有可能出現的錯誤,還是有一定難度的
- 如果希望程式 無論出現任何錯誤,都不會因為
Python
直譯器 丟擲異常而被終止,可以再增加一個except
語法如下:
except Exception as result:
print("未知錯誤 %s" % result)
2.3 異常捕獲完整語法
- 在實際開發中,為了能夠處理複雜的異常情況,完整的異常語法如下:
提示:
- 有關完整語法的應用場景,在後續學習中,結合實際的案例會更好理解
- 現在先對這個語法結構有個印象即可
try:
# 嘗試執行的程式碼
pass
except 錯誤型別1:
# 針對錯誤型別1,對應的程式碼處理
pass
except 錯誤型別2:
# 針對錯誤型別2,對應的程式碼處理
pass
except (錯誤型別3, 錯誤型別4):
# 針對錯誤型別3 和 4,對應的程式碼處理
pass
except Exception as result:
# 列印錯誤資訊
print(result)
else:
# 沒有異常才會執行的程式碼
pass
finally:
# 無論是否有異常,都會執行的程式碼
print("無論是否有異常,都會執行的程式碼")
-
else
只有在沒有異常時才會執行的程式碼 -
finally
無論是否有異常,都會執行的程式碼 -
之前一個演練的 完整捕獲異常 的程式碼如下:
try:
num = int(input("請輸入整數:"))
result = 8 / num
print(result)
except ValueError:
print("請輸入正確的整數")
except ZeroDivisionError:
print("除 0 錯誤")
except Exception as result:
print("未知錯誤 %s" % result)
else:
print("正常執行")
finally:
print("執行完成,但是不保證正確")
03. 異常的傳遞
- 異常的傳遞 —— 當 函式/方法 執行 出現異常,會 將異常傳遞 給 函式/方法 的 呼叫一方
- 如果 傳遞到主程式,仍然 沒有異常處理,程式才會被終止
提示
- 在開發中,可以在主函式中增加 異常捕獲
- 而在主函式中呼叫的其他函式,只要出現異常,都會傳遞到主函式的 異常捕獲 中
- 這樣就不需要在程式碼中,增加大量的 異常捕獲,能夠保證程式碼的整潔
需求
- 定義函式
demo1()
提示使用者輸入一個整數並且返回 - 定義函式
demo2()
呼叫demo1()
- 在主程式中呼叫
demo2()
def demo1():
return int(input("請輸入一個整數:"))
def demo2():
return demo1()
try:
print(demo2())
except ValueError:
print("請輸入正確的整數")
except Exception as result:
print("未知錯誤 %s" % result)
04. 丟擲 raise
異常
4.1 應用場景
- 在開發中,除了 程式碼執行出錯
Python
直譯器會 丟擲 異常之外 - 還可以根據 應用程式 特有的業務需求 主動丟擲異常
示例
- 提示使用者 輸入密碼,如果 長度少於 8,丟擲 異常
注意
- 當前函式 只負責 提示使用者輸入密碼,如果 密碼長度不正確,需要其他的函式進行額外處理
- 因此可以 丟擲異常,由其他需要處理的函式 捕獲異常
4.2 丟擲異常
Python
中提供了一個Exception
異常類- 在開發時,如果滿足 特定業務需求時,希望 丟擲異常,可以:
- 建立 一個
Exception
的 物件 - 使用
raise
關鍵字 丟擲 異常物件
- 建立 一個
需求
- 定義
input_password
函式,提示使用者輸入密碼 - 如果使用者輸入長度 < 8,丟擲異常
- 如果使用者輸入長度 >=8,返回輸入的密碼
def input_password():
# 1. 提示使用者輸入密碼
pwd = input("請輸入密碼:")
# 2. 判斷密碼長度,如果長度 >= 8,返回使用者輸入的密碼
if len(pwd) >= 8:
return pwd
# 3. 密碼長度不夠,需要丟擲異常
# 1> 建立異常物件 - 使用異常的錯誤資訊字串作為引數
ex = Exception("密碼長度不夠")
# 2> 丟擲異常物件
raise ex
try:
user_pwd = input_password()
print(user_pwd)
except Exception as result:
print("發現錯誤:%s" % result)
函式基礎
目標
- 函式的快速體驗
- 函式的基本使用
- 函式的引數
- 函式的返回值
- 函式的巢狀呼叫
- 在模組中定義函式
01. 函式的快速體驗
1.1 快速體驗
- 所謂函式,就是把 具有獨立功能的程式碼塊 組織為一個小模組,在需要的時候 呼叫
- 函式的使用包含兩個步驟:
- 定義函式 —— 封裝 獨立的功能
- 呼叫函式 —— 享受 封裝 的成果
- 函式的作用,在開發程式時,使用函式可以提高編寫的效率以及程式碼的 重用
演練步驟
- 新建
04_函式
專案 - 複製之前完成的 乘法表 檔案
- 修改檔案,增加函式定義
multiple_table():
- 新建另外一個檔案,使用
import
匯入並且呼叫函式
02. 函式基本使用
2.1 函式的定義
定義函式的格式如下:
def 函式名():
函式封裝的程式碼
……
def
是英文define
的縮寫- 函式名稱 應該能夠表達 函式封裝程式碼 的功能,方便後續的呼叫
- 函式名稱 的命名應該 符合 識別符號的命名規則
- 可以由 字母、下劃線 和 數字 組成
- 不能以數字開頭
- 不能與關鍵字重名
2.2 函式呼叫
呼叫函式很簡單的,通過 函式名()
即可完成對函式的呼叫
2.3 第一個函式演練
需求
-
- 編寫一個打招呼
say_hello
的函式,封裝三行打招呼的程式碼
- 編寫一個打招呼
-
- 在函式下方呼叫打招呼的程式碼
name = "小明"
# 直譯器知道這裡定義了一個函式
def say_hello():
print("hello 1")
print("hello 2")
print("hello 3")
print(name)
# 只有在呼叫函式時,之前定義的函式才會被執行
# 函式執行完成之後,會重新回到之前的程式中,繼續執行後續的程式碼
say_hello()
print(name)
用 單步執行 F8 和 F7 觀察以下程式碼的執行過程
- 定義好函式之後,只表示這個函式封裝了一段程式碼而已
- 如果不主動呼叫函式,函式是不會主動執行的
思考
-
能否將 函式呼叫 放在 函式定義 的上方?
- 不能!
- 因為在 使用函式名 呼叫函式之前,必須要保證
Python
已經知道函式的存在 - 否則控制檯會提示
NameError: name 'say_hello' is not defined
(名稱錯誤:say_hello 這個名字沒有被定義)
2.4 PyCharm 的除錯工具
- F8 Step Over 可以單步執行程式碼,會把函式呼叫看作是一行程式碼直接執行
- F7 Step Into 可以單步執行程式碼,如果是函式,會進入函式內部
2.5 函式的文件註釋
- 在開發中,如果希望給函式添加註釋,應該在 定義函式 的下方,使用 連續的三對引號
- 在 連續的三對引號 之間編寫對函式的說明文字
- 在 函式呼叫 位置,使用快捷鍵
CTRL + Q
可以檢視函式的說明資訊
注意:因為 函式體相對比較獨立,函式定義的上方,應該和其他程式碼(包括註釋)保留 兩個空行
03. 函式的引數
演練需求
- 開發一個
sum_2_num
的函式 - 函式能夠實現 兩個數字的求和 功能
演練程式碼如下:
def sum_2_num():
num1 = 10
num2 = 20
result = num1 + num2
print("%d + %d = %d" % (num1, num2, result))
sum_2_num()
思考一下存在什麼問題
函式只能處理 固定數值 的相加
如何解決?
- 如果能夠把需要計算的數字,在呼叫函式時,傳遞到函式內部就好了!
3.1 函式引數的使用
- 在函式名的後面的小括號內部填寫 引數
- 多個引數之間使用
,
分隔
def sum_2_num(num1, num2):
result = num1 + num2
print("%d + %d = %d" % (num1, num2, result))
sum_2_num(50, 20)
3.2 引數的作用
- 函式,把 具有獨立功能的程式碼塊 組織為一個小模組,在需要的時候 呼叫
- 函式的引數,增加函式的 通用性,針對 相同的資料處理邏輯,能夠 適應更多的資料
- 在函式 內部,把引數當做 變數 使用,進行需要的資料處理
- 函式呼叫時,按照函式定義的引數順序,把 希望在函式內部處理的資料,通過引數 傳遞
3.3 形參和實參
- 形參:定義 函式時,小括號中的引數,是用來接收引數用的,在函式內部 作為變數使用
- 實參:呼叫 函式時,小括號中的引數,是用來把資料傳遞到 函式內部 用的
04. 函式的返回值
- 在程式開發中,有時候,會希望 一個函式執行結束後,告訴呼叫者一個結果,以便呼叫者針對具體的結果做後續的處理
- 返回值 是函式 完成工作後,最後 給呼叫者的 一個結果
- 在函式中使用
return
關鍵字可以返回結果 - 呼叫函式一方,可以 使用變數 來 接收 函式的返回結果
注意:
return
表示返回,後續的程式碼都不會被執行
def sum_2_num(num1, num2):
"""對兩個數字的求和"""
return num1 + num2
# 呼叫函式,並使用 result 變數接收計算結果
result = sum_2_num(10, 20)
print("計算結果是 %d" % result)
05. 函式的巢狀呼叫
- 一個函式裡面 又呼叫 了 另外一個函式,這就是 函式巢狀呼叫
- 如果函式
test2
中,呼叫了另外一個函式test1
- 那麼執行到呼叫
test1
函式時,會先把函式test1
中的任務都執行完 - 才會回到
test2
中呼叫函式test1
的位置,繼續執行後續的程式碼
- 那麼執行到呼叫
def test1():
print("*" * 50)
print("test 1")
print("*" * 50)
def test2():
print("-" * 50)
print("test 2")
test1()
print("-" * 50)
test2()
函式巢狀的演練 —— 列印分隔線
體會一下工作中 需求是多變 的
需求 1
- 定義一個
print_line
函式能夠列印*
組成的 一條分隔線
def print_line(char):
print("*" * 50)
需求 2
- 定義一個函式能夠列印 由任意字元組成 的分隔線
def print_line(char):
print(char * 50)
需求 3
- 定義一個函式能夠列印 任意重複次數 的分隔線
def print_line(char, times):
print(char * times)
需求 4
- 定義一個函式能夠列印 5 行 的分隔線,分隔線要求符合需求 3
提示:工作中針對需求的變化,應該冷靜思考,不要輕易修改之前已經完成的,能夠正常執行的函式!
def print_line(char, times):
print(char * times)
def print_lines(char, times):
row = 0
while row < 5:
print_line(char, times)
row += 1
06. 使用模組中的函式
模組是 Python 程式架構的一個核心概念
- 模組 就好比是 工具包,要想使用這個工具包中的工具,就需要 匯入 import 這個模組
- 每一個以副檔名
py
結尾的Python
原始碼檔案都是一個 模組 - 在模組中定義的 全域性變數 、 函式 都是模組能夠提供給外界直接使用的工具
6.1 第一個模組體驗
步驟
- 新建
hm_10_分隔線模組.py
- 複製
hm_09_列印多條分隔線.py
中的內容,最後一行print
程式碼除外 - 增加一個字串變數
- 複製
name = "黑馬程式設計師"
- 新建
hm_10_體驗模組.py
檔案,並且編寫以下程式碼:
import hm_10_分隔線模組
hm_10_分隔線模組.print_line("-", 80)
print(hm_10_分隔線模組.name)
體驗小結
- 可以 在一個 Python 檔案 中 定義 變數 或者 函式
- 然後在 另外一個檔案中 使用
import
匯入這個模組 - 匯入之後,就可以使用
模組名.變數
/模組名.函式
的方式,使用這個模組中定義的變數或者函式
模組可以讓 曾經編寫過的程式碼 方便的被 複用!
6.2 模組名也是一個識別符號
- 標示符可以由 字母、下劃線 和 數字 組成
- 不能以數字開頭
- 不能與關鍵字重名
注意:如果在給 Python 檔案起名時,以數字開頭 是無法在
PyCharm
中通過匯入這個模組的
6.3 Pyc 檔案(瞭解)
C
是compiled
編譯過 的意思
操作步驟
- 瀏覽程式目錄會發現一個
__pycache__
的目錄 - 目錄下會有一個
hm_10_分隔線模組.cpython-35.pyc
檔案,cpython-35
表示Python
直譯器的版本 - 這個
pyc
檔案是由 Python 直譯器將 模組的原始碼 轉換為 位元組碼Python
這樣儲存 位元組碼 是作為一種啟動 速度的優化
位元組碼
-
Python
在解釋源程式時是分成兩個步驟的- 首先處理原始碼,編譯 生成一個二進位制 位元組碼
- 再對 位元組碼 進行處理,才會生成 CPU 能夠識別的 機器碼
-
有了模組的位元組碼檔案之後,下一次執行程式時,如果在 上次儲存位元組碼之後 沒有修改過原始碼,Python 將會載入 .pyc 檔案並跳過編譯這個步驟
-
當
Python
重編譯時,它會自動檢查原始檔和位元組碼檔案的時間戳 -
如果你又修改了原始碼,下次程式執行時,位元組碼將自動重新建立
提示:有關模組以及模組的其他匯入方式,後續課程還會逐漸展開!
模組是 Python 程式架構的一個核心概念
變數進階(理解)
目標
- 變數的引用
- 可變和不可變型別
- 區域性變數和全域性變數
01. 變數的引用
- 變數 和 資料 都是儲存在 記憶體 中的
- 在
Python
中 函式 的 引數傳遞 以及 返回值 都是靠 引用 傳遞的
1.1 引用的概念
在 Python
中
- 變數 和 資料 是分開儲存的
- 資料 儲存在記憶體中的一個位置
- 變數 中儲存著資料在記憶體中的地址
- 變數 中 記錄資料的地址,就叫做 引用
- 使用
id()
函式可以檢視變數中儲存資料所在的 記憶體地址
注意:如果變數已經被定義,當給一個變數賦值的時候,本質上是 修改了資料的引用
- 變數 不再 對之前的資料引用
- 變數 改為 對新賦值的資料引用
1.2 變數引用
的示例
在 Python
中,變數的名字類似於 便籤紙 貼在 資料 上
- 定義一個整數變數
a
,並且賦值為1
變數
b
是第 2 個貼在數字2
上的標籤
1.3 函式的引數和返回值的傳遞
在 Python
中,函式的 實參/返回值 都是是靠 引用 來傳遞來的
def test(num):
print("-" * 50)
print("%d 在函式內的記憶體地址是 %x" % (num, id(num)))
result = 100
print("返回值 %d 在記憶體中的地址是 %x" % (result, id(result)))
print("-" * 50)
return result
a = 10
print("呼叫函式前 記憶體地址是 %x" % id(a))
r = test(a)
print("呼叫函式後 實參記憶體地址是 %x" % id(a))
print("呼叫函式後 返回值記憶體地址是 %x" % id(r))
02. 可變和不可變型別
-
不可變型別,記憶體中的資料不允許被修改:
- 數字型別
int
,bool
,float
,complex
,long(2.x)
- 字串
str
- 元組
tuple
- 數字型別
-
可變型別,記憶體中的資料可以被修改:
- 列表
list
- 字典
dict
- 列表
a = 1
a = "hello"
a = [1, 2, 3]
a = [3, 2, 1]
demo_list = [1, 2, 3]
print("定義列表後的記憶體地址 %d" % id(demo_list))
demo_list.append(999)
demo_list.pop(0)
demo_list.remove(2)
demo_list[0] = 10
print("修改資料後的記憶體地址 %d" % id(demo_list))
demo_dict = {"name": "小明"}
print("定義字典後的記憶體地址 %d" % id(demo_dict))
demo_dict["age"] = 18
demo_dict.pop("name")
demo_dict["name"] = "老王"
print("修改資料後的記憶體地址 %d" % id(demo_dict))
注意:字典的
key
只能使用不可變型別的資料
注意
- 可變型別的資料變化,是通過 方法 來實現的
- 如果給一個可變型別的變數,賦值了一個新的資料,引用會修改
- 變數 不再 對之前的資料引用
- 變數 改為 對新賦值的資料引用
雜湊 (hash)
Python
中內建有一個名字叫做hash(o)
的函式- 接收一個 不可變型別 的資料作為 引數
- 返回 結果是一個 整數
雜湊
是一種 演算法,其作用就是提取資料的 特徵碼(指紋)- 相同的內容 得到 相同的結果
- 不同的內容 得到 不同的結果
- 在
Python
中,設定字典的 鍵值對 時,會首先對key
進行hash
已決定如何在記憶體中儲存字典的資料,以方便 後續 對字典的操作:增、刪、改、查- 鍵值對的
key
必須是不可變型別資料 - 鍵值對的
value
可以是任意型別的資料
- 鍵值對的
03. 區域性變數和全域性變數
- 區域性變數 是在 函式內部 定義的變數,只能在函式內部使用
- 全域性變數 是在 函式外部定義 的變數(沒有定義在某一個函式內),所有函式 內部 都可以使用這個變數
提示:在其他的開發語言中,大多 不推薦使用全域性變數 —— 可變範圍太大,導致程式不好維護!
3.1 區域性變數
- 區域性變數 是在 函式內部 定義的變數,只能在函式內部使用
- 函式執行結束後,函式內部的區域性變數,會被系統回收
- 不同的函式,可以定義相同的名字的區域性變數,但是 彼此之間 不會產生影響
區域性變數的作用
- 在函式內部使用,臨時 儲存 函式內部需要使用的資料
def demo1():
num = 10
print(num)
num = 20
print("修改後 %d" % num)
def demo2():
num = 100
print(num)
demo1()
demo2()
print("over")
區域性變數的生命週期
- 所謂 生命週期 就是變數從 被建立 到 被系統回收 的過程
- 區域性變數 在 函式執行時 才會被建立
- 函式執行結束後 區域性變數 被系統回收
- 區域性變數在生命週期 內,可以用來儲存 函式內部臨時使用到的資料
3.2 全域性變數
- 全域性變數 是在 函式外部定義 的變數,所有函式內部都可以使用這個變數
# 定義一個全域性變數
num = 10
def demo1():
print(num)
def demo2():
print(num)
demo1()
demo2()
print("over")
注意:函式執行時,需要處理變數時 會:
- 首先 查詢 函式內部 是否存在 指定名稱 的區域性變數,如果有,直接使用
- 如果沒有,查詢 函式外部 是否存在 指定名稱 的全域性變數,如果有,直接使用
- 如果還沒有,程式報錯!
1) 函式不能直接修改 全域性變數的引用
- 全域性變數 是在 函式外部定義 的變數(沒有定義在某一個函式內),所有函式 內部 都可以使用這個變數
提示:在其他的開發語言中,大多 不推薦使用全域性變數 —— 可變範圍太大,導致程式不好維護!
- 在函式內部,可以 通過全域性變數的引用獲取對應的資料
- 但是,不允許直接修改全域性變數的引用 —— 使用賦值語句修改全域性變數的值
num = 10
def demo1():
print("demo1" + "-" * 50)
# 只是定義了一個區域性變數,不會修改到全域性變數,只是變數名相同而已
num = 100
print(num)
def demo2():
print("demo2" + "-" * 50)
print(num)
demo1()
demo2()
print("over")
注意:只是在函式內部定義了一個區域性變數而已,只是變數名相同 —— 在函式內部不能直接修改全域性變數的值
2) 在函式內部修改全域性變數的值
- 如果在函式中需要修改全域性變數,需要使用
global
進行宣告
num = 10
def demo1():
print("demo1" + "-" * 50)
# global 關鍵字,告訴 Python 直譯器 num 是一個全域性變數
global num
# 只是定義了一個區域性變數,不會修改到全域性變數,只是變數名相同而已
num = 100
print(num)
def demo2():
print("demo2" + "-" * 50)
print(num)
demo1()
demo2()
print("over")
3) 全域性變數定義的位置
- 為了保證所有的函式都能夠正確使用到全域性變數,應該 將全域性變數定義在其他函式的上方
a = 10
def demo():
print("%d" % a)
print("%d" % b)
print("%d" % c)
b = 20
demo()
c = 30
注意
- 由於全域性變數 c,是在呼叫函式之後,才定義的,在執行函式時,變數還沒有定義,所以程式會報錯!
4) 全域性變數命名的建議
- 為了避免區域性變數和全域性變量出現混淆,在定義全域性變數時,有些公司會有一些開發要求,例如:
- 全域性變數名前應該增加
g_
或者gl_
的字首
提示:具體的要求格式,各公司要求可能會有些差異
函式進階
目標
- 函式引數和返回值的作用
- 函式的返回值 進階
- 函式的引數 進階
- 遞迴函式
01. 函式引數和返回值的作用
函式根據 有沒有引數 以及 有沒有返回值,可以 相互組合,一共有 4 種 組合形式
- 無引數,無返回值
- 無引數,有返回值
- 有引數,無返回值
- 有引數,有返回值
定義函式時,是否接收引數,或者是否返回結果,是根據 實際的功能需求 來決定的!
- 如果函式 內部處理的資料不確定,就可以將外界的資料以引數傳遞到函式內部
- 如果希望一個函式 執行完成後,向外界彙報執行結果,就可以增加函式的返回值
1.1 無引數,無返回值
此類函式,不接收引數,也沒有返回值,應用場景如下:
- 只是單純地做一件事情,例如 顯示選單
- 在函式內部 針對全域性變數進行操作,例如:新建名片,最終結果 記錄在全域性變數 中
注意:
- 如果全域性變數的資料型別是一個 可變型別,在函式內部可以使用 方法 修改全域性變數的內容 —— 變數的引用不會改變
- 在函式內部,使用賦值語句 才會 修改變數的引用
1.2 無引數,有返回值
此類函式,不接收引數,但是有返回值,應用場景如下:
- 採集資料,例如 溫度計,返回結果就是當前的溫度,而不需要傳遞任何的引數
1.3 有引數,無返回值
此類函式,接收引數,沒有返回值,應用場景如下:
- 函式內部的程式碼保持不變,針對 不同的引數 處理 不同的資料
- 例如 名片管理系統 針對 找到的名片 做 修改、刪除 操作
1.4 有引數,有返回值
此類函式,接收引數,同時有返回值,應用場景如下:
- 函式內部的程式碼保持不變,針對 不同的引數 處理 不同的資料,並且 返回期望的處理結果
- 例如 名片管理系統 使用 字典預設值 和 提示資訊 提示使用者輸入內容
- 如果輸入,返回輸入內容
- 如果沒有輸入,返回字典預設值
02. 函式的返回值 進階
- 在程式開發中,有時候,會希望 一個函式執行結束後,告訴呼叫者一個結果,以便呼叫者針對具體的結果做後續的處理
- 返回值 是函式 完成工作後,最後 給呼叫者的 一個結果
- 在函式中使用
return
關鍵字可以返回結果 - 呼叫函式一方,可以 使用變數 來 接收 函式的返回結果
問題:一個函式執行後能否返回多個結果?
示例 —— 溫度和溼度測量
- 假設要開發一個函式能夠同時返回當前的溫度和溼度
- 先完成返回溫度的功能如下:
def measure():
"""返回當前的溫度"""
print("開始測量...")
temp = 39
print("測量結束...")
return temp
result = measure()
print(result)
- 在利用 元組 在返回溫度的同時,也能夠返回 溼度
- 改造如下:
def measure():
"""返回當前的溫度"""
print("開始測量...")
temp = 39
wetness = 10
print("測量結束...")
return (temp, wetness)
提示:如果一個函式返回的是元組,括號可以省略
技巧
- 在
Python
中,可以 將一個元組 使用 賦值語句 同時賦值給 多個變數 - 注意:變數的數量需要和元組中的元素數量保持一致
result = temp, wetness = measure()
面試題 —— 交換兩個數字
題目要求
- 有兩個整數變數
a = 6
,b = 100
- 不使用其他變數,交換兩個變數的值
解法 1 —— 使用其他變數
# 解法 1 - 使用臨時變數
c = b
b = a
a = c
解法 2 —— 不使用臨時變數
# 解法 2 - 不使用臨時變數
a = a + b
b = a - b
a = a - b
解法 3 —— Python 專有,利用元組
a, b = b, a
03. 函式的引數 進階
3.1. 不可變和可變的引數
問題 1:在函式內部,針對引數使用 賦值語句,會不會影響呼叫函式時傳遞的 實參變數? —— 不會!
- 無論傳遞的引數是 可變 還是 不可變
- 只要 針對引數 使用 賦值語句,會在 函式內部 修改 區域性變數的引用,不會影響到 外部變數的引用
def demo(num, num_list):
print("函式內部")
# 賦值語句
num = 200
num_list = [1, 2, 3]
print(num)
print(num_list)
print("函式程式碼完成")
gl_num = 99
gl_list = [4, 5, 6]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
問題 2:如果傳遞的引數是 可變型別,在函式內部,使用 方法 修改了資料的內容,同樣會影響到外部的資料
def mutable(num_list):
# num_list = [1, 2, 3]
num_list.extend([1, 2, 3])
print(num_list)
gl_list = [6, 7, 8]
mutable(gl_list)
print(gl_list)
面試題 —— +=
- 在
python
中,列表變數呼叫+=
本質上是在執行列表變數的extend
方法,不會修改變數的引用
def demo(num, num_list):
print("函式內部程式碼")
# num = num + num
num += num
# num_list.extend(num_list) 由於是呼叫方法,所以不會修改變數的引用
# 函式執行結束後,外部資料同樣會發生變化
num_list += num_list
print(num)
print(num_list)
print("函式程式碼完成")
gl_num = 9
gl_list = [1, 2, 3]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
3.2 預設引數
- 定義函式時,可以給 某個引數 指定一個預設值,具有預設值的引數就叫做 預設引數
- 呼叫函式時,如果沒有傳入 預設引數 的值,則在函式內部使用定義函式時指定的 引數預設值
- 函式的預設引數,將常見的值設定為引數的預設值,從而 簡化函式的呼叫
- 例如:對列表排序的方法
gl_num_list = [6, 3, 9]
# 預設就是升序排序,因為這種應用需求更多
gl_num_list.sort()
print(gl_num_list)
# 只有當需要降序排序時,才需要傳遞 `reverse` 引數
gl_num_list.sort(reverse=True)
print(gl_num_list)
指定函式的預設引數
- 在引數後使用賦值語句,可以指定引數的預設值
def print_info(name, gender=True):
gender_text = "男生"
if not gender:
gender_text = "女生"
print("%s 是 %s" % (name, gender_text))
提示
- 預設引數,需要使用 最常見的值 作為預設值!
- 如果一個引數的值 不能確定,則不應該設定預設值,具體的數值在呼叫函式時,由外界傳遞!
預設引數的注意事項
1) 預設引數的定義位置
- 必須保證 帶有預設值的預設引數 在引數列表末尾
- 所以,以下定義是錯誤的!
def print_info(name, gender=True, title):
2) 呼叫帶有多個預設引數的函式
- 在 呼叫函式時,如果有 多個預設引數,需要指定引數名,這樣直譯器才能夠知道引數的對應關係!
def print_info(name, title="", gender=True):
"""
:param title: 職位
:param name: 班上同學的姓名
:param gender: True 男生 False 女生
"""
gender_text = "男生"
if not gender:
gender_text = "女生"
print("%s%s 是 %s" % (title, name, gender_text))
# 提示:在指定預設引數的預設值時,應該使用最常見的值作為預設值!
print_info("小明")
print_info("老王", title="班長")
print_info("小美", gender=False)
3.3 多值引數(知道)
定義支援多值引數的函式
-
有時可能需要 一個函式 能夠處理的引數 個數 是不確定的,這個時候,就可以使用 多值引數
-
python
中有 兩種 多值引數:- 引數名前增加 一個
*
可以接收 元組 - 引數名前增加 兩個
*
可以接收 字典
- 引數名前增加 一個
-
一般在給多值引數命名時,習慣使用以下兩個名字
*args
—— 存放 元組 引數,前面有一個*
**kwargs
—— 存放 字典 引數,前面有兩個*
-
args
是arguments
的縮寫,有變數的含義 -
kw
是keyword
的縮寫,kwargs
可以記憶 鍵值對引數
def demo(num, *args, **kwargs):
print(num)
print(args)
print(kwargs)
demo(1, 2, 3, 4, 5, name="小明", age=18, gender=True)
提示:多值引數 的應用會經常出現在網路上一些大牛開發的框架中,知道多值引數,有利於我們能夠讀懂大牛的程式碼
多值引數案例 —— 計算任意多個數字的和
需求
- 定義一個函式
sum_numbers
,可以接收的 任意多個整數 - 功能要求:將傳遞的 所有數字累加 並且返回累加結果
def sum_numbers(*args):
num = 0
# 遍歷 args 元組順序求和
for n in args:
num += n
return num
print(sum_numbers(1, 2, 3))
元組和字典的拆包(知道)
- 在呼叫帶有多值引數的函式時,如果希望:
- 將一個 元組變數,直接傳遞給
args
- 將一個 字典變數,直接傳遞給
kwargs
- 將一個 元組變數,直接傳遞給
- 就可以使用 拆包,簡化引數的傳遞,拆包 的方式是:
- 在 元組變數前,增加 一個
*
- 在 字典變數前,增加 兩個
*
- 在 元組變數前,增加 一個
def demo(*args, **kwargs):
print(args)
print(kwargs)
# 需要將一個元組變數/字典變數傳遞給函式對應的引數
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}
# 會把 num_tuple 和 xiaoming 作為元組傳遞個 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)
04. 函式的遞迴
函式呼叫自身的 程式設計技巧 稱為遞迴
4.1 遞迴函式的特點
特點
- 一個函式 內部 呼叫自己
- 函式內部可以呼叫其他函式,當然在函式內部也可以呼叫自己
程式碼特點
- 函式內部的 程式碼 是相同的,只是針對 引數 不同,處理的結果不同
- 當 引數滿足一個條件 時,函式不再執行
- 這個非常重要,通常被稱為遞迴的出口,否則 會出現死迴圈!
示例程式碼
def sum_numbers(num):
print(num)
# 遞迴的出口很重要,否則會出現死迴圈
if num == 1:
return
sum_numbers(num - 1)
sum_numbers(3)
4.2 遞迴案例 —— 計算數字累加
需求
- 定義一個函式
sum_numbers
- 能夠接收一個
num
的整數引數 - 計算 1 + 2 + ... num 的結果
def sum_numbers(num):
if num == 1:
return 1
# 假設 sum_numbers 能夠完成 num - 1 的累加
temp = sum_numbers(num - 1)
# 函式內部的核心演算法就是 兩個數字的相加
return num + temp
print(sum_numbers(2))
提示:遞迴是一個 程式設計技巧,初次接觸遞迴會感覺有些吃力!在處理 不確定的迴圈條件時,格外的有用,例如:遍歷整個檔案目錄的結構