1. 程式人生 > 其它 >python筆記總結

python筆記總結

認識 Python

人生苦短,我用 Python —— Life is short, you need Python

目標

  • Python 的起源
  • 為什麼要用 Python?
  • Python 的特點
  • Python 的優缺點

01. Python 的起源

Python 的創始人為吉多·範羅蘇姆(Guido van Rossum)

  1. 1989 年的聖誕節期間,吉多·範羅蘇姆為了在阿姆斯特丹打發時間,決心開發一個新的解釋程式,作為 ABC 語言的一種繼承(感覺下什麼叫牛人
  2. ABC 是由吉多參加設計的一種教學語言,就吉多本人看來,ABC 這種語言非常優美和強大,是專門為非專業程式設計師設計的。但是 ABC 語言並沒有成功,究其原因,吉多認為是非開放
    造成的。吉多決心在 Python 中避免這一錯誤,並獲取了非常好的效果
  3. 之所以選中 Python(蟒蛇) 作為程式的名字,是因為他是 BBC 電視劇——蒙提·派森的飛行馬戲團(Monty Python's Flying Circus)的愛好者
  4. 1991 年,第一個 Python 直譯器 誕生,它是用 C 語言實現的,並能夠呼叫 C 語言的庫檔案

1.1 直譯器(科普)

計算機不能直接理解任何除機器語言以外的語言,所以必須要把程式設計師所寫的程式語言翻譯成機器語言,計算機才能執行程式。將其他語言翻譯成機器語言的工具,被稱為編譯器

編譯器翻譯的方式有兩種:一個是編譯,另外一個是解釋。兩種方式之間的區別在於翻譯時間點的不同

。當編譯器以解釋方式執行的時候,也稱之為直譯器

  • 編譯型語言:程式在執行之前需要一個專門的編譯過程,把程式編譯成為機器語言的檔案,執行時不需要重新翻譯,直接使用編譯的結果就行了。程式執行效率高,依賴編譯器,跨平臺性差些。如 C、C++
  • 解釋型語言:解釋型語言編寫的程式不進行預先編譯,以文字方式儲存程式程式碼,會將程式碼一句一句直接執行。在釋出程式時,看起來省了道編譯工序,但是在執行程式的時候,必須先解釋再執行

編譯型語言和解釋型語言對比

  • 速度 —— 編譯型語言比解釋型語言執行速度快
  • 跨平臺性 —— 解釋型語言比編譯型語言跨平臺性好

1.2 Python 的設計目標

1999 年,吉多·範羅蘇姆向 DARPA 提交了一條名為 “Computer Programming for Everybody” 的資金申請,並在後來說明了他對 Python 的目標:

  • 一門簡單直觀的語言並與主要競爭者一樣強大
  • 開源,以便任何人都可以為它做貢獻
  • 程式碼像純英語那樣容易理解
  • 適用於短期開發的日常任務

這些想法中的基本都已經成為現實,Python 已經成為一門流行的程式語言

1.3 Python 的設計哲學

  1. 優雅
  2. 明確
  3. 簡單
  • Python 開發者的哲學是:用一種方法,最好是隻有一種方法來做一件事
  • 如果面臨多種選擇,Python 開發者一般會拒絕花俏的語法,而選擇明確沒有或者很少有歧義的語法

在 Python 社群,吉多被稱為“仁慈的獨裁者”

02. 為什麼選擇 Python?

  • 程式碼量少
  • ……

同一樣問題,用不同的語言解決,程式碼量差距還是很多的,一般情況下 PythonJava1/5,所以說 人生苦短,我用 Python

03. Python 特點

  • Python 是完全面向物件的語言
    • 函式模組數字字串都是物件,在 Python 中一切皆物件
    • 完全支援繼承、過載、多重繼承
    • 支援過載運算子,也支援泛型設計
  • Python 擁有一個強大的標準庫,Python 語言的核心只包含 數字字串列表字典檔案 等常見型別和函式,而由 Python 標準庫提供了 系統管理網路通訊文字處理資料庫介面圖形系統XML 處理 等額外的功能
  • Python 社群提供了大量的第三方模組,使用方式與標準庫類似。它們的功能覆蓋 科學計算人工智慧機器學習Web 開發資料庫介面圖形系統 多個領域

面向物件的思維方式

  • 面向物件 是一種 思維方式,也是一門 程式設計技術
  • 要解決一個問題前,首先考慮 由誰 來做,怎麼做事情是 的職責,最後把事情做好就行!
    • 物件 就是
  • 要解決複雜的問題,就可以找多個不同的物件各司其職,共同實現,最終完成需求

04. Python 的優缺點

4.1 優點

  • 簡單、易學
  • 免費、開源
  • 面向物件
  • 豐富的庫
  • 可擴充套件性
    • 如果需要一段關鍵程式碼執行得更快或者希望某些演算法不公開,可以把這部分程式用 CC++ 編寫,然後在 Python 程式中使用它們
  • ……

4.2 缺點

  • 執行速度
  • 國內市場較小
  • 中文資料匱乏

第一個 Python 程式

目標

  • 第一個 HelloPython 程式
  • Python 2.x3​​.x 版本簡介
  • 執行 Python 程式的三種方式
    • 直譯器 —— python / python3
    • 互動式 —— ipython
    • 整合開發環境 —— PyCharm

01. 第一個 HelloPython 程式

1.1 Python 源程式的基本概念

  1. Python 源程式就是一個特殊格式的文字檔案,可以使用任意文字編輯軟體Python 的開發
  2. 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

printpython 中我們學習的第一個 函式

print 函式的作用,可以把 "" 內部的內容,輸出到螢幕上

1.3 演練擴充套件 —— 認識錯誤(BUG)

關於錯誤

  • 編寫的程式不能正常執行,或者執行的結果不是我們期望的
  • 俗稱 BUG,是程式設計師在開發時非常常見的,初學者常見錯誤的原因包括:
    1. 手誤
    2. 對已經學習過的知識理解還存在不足
    3. 對語言還有需要學習和提升的內容
  • 在學習語言時,不僅要學會語言的語法,而且還要學會如何認識錯誤和解決錯誤的方法

每一個程式設計師都是在不斷地修改錯誤中成長的

第一個演練中的常見錯誤

  • 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.xPython 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.x3​​.x 版本簡介

目前市場上有兩個 Python 的版本並存著,分別是 Python 2.xPython 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.6Python 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 + dIPython 會詢問是否退出直譯器

IPython 的安裝

$ sudo apt install ipython

3.3. Python 的 IDE —— PyCharm

1) 整合開發環境(IDE)

整合開發環境(IDE,Integrated Development Environment)—— 集成了開發軟體需要的所有工具,一般包括以下工具:

  • 圖形使用者介面
  • 程式碼編輯器(支援 程式碼補全自動縮排
  • 編譯器/直譯器
  • 偵錯程式(斷點單步執行
  • ……

2)PyCharm 介紹

  • PyCharm 是 Python 的一款非常優秀的整合開發環境
  • PyCharm 除了具有一般 IDE 所必備功能外,還可以在 WindowsLinuxmacOS 下使用
  • PyCharm 適合開發大型專案
    • 一個專案通常會包含 很多原始檔
    • 每個 原始檔 的程式碼行數是有限的,通常在幾百行之內
    • 每個 原始檔 各司其職,共同完成複雜的業務功能

3)PyCharm 快速體驗

  • 檔案導航區域 能夠 瀏覽定位開啟 專案檔案

  • 檔案編輯區域 能夠 編輯 當前開啟的檔案

  • 控制檯區域 能夠:

    • 輸出程式執行內容
    • 跟蹤除錯程式碼的執行
  • 右上角的 工具欄 能夠 執行(SHIFT + F10) / 除錯(SHIFT + F9) 程式碼

  • 通過控制檯上方的單步執行按鈕(F8),可以單步執行程式碼

註釋

目標

  • 註釋的作用
  • 單行註釋(行註釋)
  • 多行註釋(塊註釋)

01. 註釋的作用

使用用自己熟悉的語言,在程式中對某些程式碼進行標註說明,增強程式的可讀性

02. 單行註釋(行註釋)

  • # 開頭,# 右邊的所有東西都被當做說明文字,而不是真正要執行的程式,只起到輔助說明作用

  • 示例程式碼如下:

# 這是第一個單行註釋
print("hello python")

為了保證程式碼的可讀性,# 後面建議先新增一個空格,然後再編寫相應的說明文字

在程式碼後面增加的單行註釋

  • 在程式開發時,同樣可以使用 # 在程式碼的後面(旁邊)增加說明性的文字

  • 但是,需要注意的是,為了保證程式碼的可讀性註釋和程式碼之間 至少要有 兩個空格

  • 示例程式碼如下:

print("hello python")  # 輸出 `hello python`

03. 多行註釋(塊註釋)

  • 如果希望編寫的 註釋資訊很多,一行無法顯示,就可以使用多行註釋

  • 要在 Python 程式中使用多行註釋,可以用 一對 連續的 三個 引號(單引號和雙引號都可以)

  • 示例程式碼如下:

"""
這是一個多行註釋

在多行註釋之間,可以寫很多很多的內容……
""" 
print("hello python")

什麼時候需要使用註釋?

  1. 註釋不是越多越好,對於一目瞭然的程式碼,不需要添加註釋
  2. 對於 複雜的操作,應該在操作開始前寫上若干行註釋
  3. 對於 不是一目瞭然的程式碼,應在其行尾添加註釋(為了提高可讀性,註釋應該至少離開程式碼 2 個空格)
  4. 絕不要描述程式碼,假設閱讀程式碼的人比你更懂 Python,他只是不知道你的程式碼要做什麼

在一些正規的開發團隊,通常會有 程式碼稽核 的慣例,就是一個團隊中彼此閱讀對方的程式碼

關於程式碼規範

任何語言的程式設計師,編寫出符合規範的程式碼,是開始程式生涯的第一步

# 算數運算子

**計算機**,顧名思義就是負責進行 **數學計算** 並且 **儲存計算結果** 的電子裝置

## 目標

* 算術運算子的基本使用

## 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

程式執行原理(科普)

目標

  • 計算機中的 三大件
  • 程式執行的原理
  • 程式的作用

計算機中的三大件

計算機中包含有較多的硬體,但是一個程式要執行,有 三個 核心的硬體,分別是:

  1. CPU
    • 中央處理器,是一塊超大規模的積體電路
    • 負責 處理資料計算
  2. 記憶體
    • 臨時 儲存資料(斷電之後,資料會消失)
    • 速度快
    • 空間小(單位價格高)
  3. 硬碟
    • 永久 儲存資料
    • 速度慢
    • 空間大(單位價格低)

程式執行的原理

  1. 程式 執行之前,程式是 儲存在硬碟 中的
  2. 當要執行一個程式時
    • 作業系統會首先讓 CPU 把程式複製到 記憶體
    • CPU 執行 記憶體 中的 程式程式碼

程式要執行,首先要被載入到記憶體

2.1 Python 程式執行原理

  1. 作業系統會首先讓 CPUPython 直譯器 的程式複製到 記憶體
  2. Python 直譯器 根據語法規則,從上向下CPU 翻譯 Python 程式中的程式碼
  3. 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'

使用互動式方式,如果要檢視變數內容,直接輸入變數名即可,不需要使用 print 函式

2) 變數演練 2 —— PyCharm

# 定義 qq 號碼變數
qq_number = "1234567"

# 定義 qq 密碼變數
qq_password = "123"

# 在程式中,如果要輸出變數的內容,需要使用 print 函式
print(qq_number)
print(qq_password)

使用直譯器執行,如果要輸出變數的內容,必須要要使用 print 函式

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)

提問

  • 上述程式碼中,一共定義有幾個變數?
    • 三個:priceweightmoney
  • money = money - 5 是在定義新的變數還是在使用變數?
    • 直接使用之前已經定義的變數
    • 變數名 只有在 第一次出現 才是 定義變數
    • 變數名 再次出現,不是定義變數,而是直接使用之前定義過的變數
  • 在程式開發中,可以修改之前定義變數中儲存的值嗎?
    • 可以
    • 變數中儲存的值,就是可以

02. 變數的型別

  • 在記憶體中建立一個變數,會包括:
    1. 變數的名稱
    2. 變數儲存的資料
    3. 變數儲存資料的型別
    4. 變數的地址(標示)

2.1 變數型別的演練 —— 個人資訊

需求

  • 定義變數儲存小明的個人資訊
  • 姓名:小明
  • 年齡:18
  • 性別:男生
  • 身高:1.75
  • 體重:75.0 公斤

利用 單步除錯 確認變數中儲存資料的型別

提問

  1. 在演練中,一共有幾種資料型別?
    • 4 種
    • str —— 字串
    • bool —— 布林(真假)
    • int —— 整數
    • float —— 浮點數(小數)
  2. 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

演練步驟

  1. 定義整數 i = 10
  2. 定義浮點數 f = 10.5
  3. 定義布林型 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)

提問

  1. 演練中,針對 價格 定義了幾個變數?
    • 兩個
    • price_str 記錄使用者輸入的價格字串
    • price 記錄轉換後的價格數值
  2. 思考 —— 如果開發中,需要使用者通過控制檯 輸入 很多個 數字,針對每一個數字都要定義兩個變數,方便嗎
演練方式 2 —— 買蘋果改進版
  1. 定義 一個 浮點變數 接收使用者輸入的同時,就使用 float 函式進行轉換
price = float(input("請輸入價格:"))
  • 改進後的好處:
  1. 節約空間,只需要為一個變數分配空間
  2. 起名字方便,不需要為中間變數起名字
  • 改進後的“缺點”:
  1. 初學者需要知道,兩個函式能夠巢狀使用,稍微有一些難度

提示

  • 如果輸入的不是一個數字,程式執行時會出錯,有關資料轉換的高階話題,後續會講!

2.5 變數的格式化輸出

蘋果單價 9.00 元/斤,購買了 5.00 斤,需要支付 45.00

  • 在 Python 中可以使用 print 函式將資訊輸出到控制檯
  • 如果希望輸出文字資訊的同時,一起輸出 資料,就需要使用到 格式化操作符
  • % 被稱為 格式化操作符,專門用於處理字串中的格式
    • 包含 % 的字串,被稱為 格式化字串
    • % 和不同的 字元 連用,不同型別的資料 需要使用 不同的格式化字元
格式化字元 含義
%s 字串
%d 有符號十進位制整數,%06d 表示輸出的整數顯示位數,不足的地方使用 0 補全
%f 浮點數,%.2f 表示小數點後只顯示兩位
%% 輸出 %
  • 語法格式如下:
print("格式化字串" % 變數1)

print("格式化字串" % (變數1, 變數2...))

格式化輸出演練 —— 基本練習

需求

  1. 定義字串變數 name,輸出 我的名字叫 小明,請多多關照!
  2. 定義整數變數 student_no,輸出 我的學號是 000001
  3. 定義小數 priceweightmoney,輸出 蘋果單價 9.00 元/斤,購買了 5.00 斤,需要支付 45.00 元
  4. 定義一個小數 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 中的 識別符號區分大小寫的

  1. 在定義變數時,為了保證程式碼格式,= 的左右應該各保留一個空格

  2. Python 中,如果 變數名 需要由 二個多個單詞 組成時,可以按照以下方式命名

    1. 每個單詞都使用小寫字母
    2. 單詞與單詞之間使用 _下劃線 連線
    • 例如:first_namelast_nameqq_numberqq_password

駝峰命名法

  • 變數名 是由二個或多個單片語成時,還可以利用駝峰命名法來命名
  • 小駝峰式命名法
    • 第一個單詞以小寫字母開始,後續單詞的首字母大寫
    • 例如:firstNamelastName
  • 大駝峰式命名法
    • 每一個單詞的首字母都採用大寫字母
    • 例如:FirstNameLastNameCamelCase

判斷(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 判斷語句演練 —— 判斷年齡

需求

  1. 定義一個整數變數記錄年齡
  2. 判斷是否滿 18 歲 (>=
  3. 如果滿 18 歲,允許進網咖嗨皮
# 1. 定義年齡變數
age = 18

# 2. 判斷是否滿 18 歲
# if 語句以及縮排部分的程式碼是一個完整的程式碼塊
if age >= 18:
    print("可以進網咖嗨皮……")

# 3. 思考!- 無論條件是否滿足都會執行
print("這句程式碼什麼時候執行?")

注意

  • if 語句以及縮排部分是一個 完整的程式碼塊

2.3 else 處理條件不滿足的情況

思考

在使用 if 判斷時,只能做到滿足條件時要做的事情。那如果需要在 不滿足條件的時候,做某些事情,該如何做呢?

答案

else,格式如下:

if 要判斷的條件:
    條件成立時,要做的事情
    ……
else:
    條件不成立時,要做的事情
    ……

注意

  • ifelse 語句以及各自的縮排部分共同是一個 完整的程式碼塊

2.4 判斷語句演練 —— 判斷年齡改進

需求

  1. 輸入使用者年齡
  2. 判斷是否滿 18 歲 (>=
  3. 如果滿 18 歲,允許進網咖嗨皮
  4. 如果未滿 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. 練習1: 定義一個整數變數 age,編寫程式碼判斷年齡是否正確
    • 要求人的年齡在 0-120 之間
  2. 練習2: 定義兩個整數變數 python_scorec_score,編寫程式碼判斷成績
    • 要求只要有一門成績 > 60 分就算合格
  3. 練習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滿足 執行的程式碼
    ……

注意

  1. elifelse 都必須和 if 聯合使用,而不能單獨使用
  2. 可以將 ifelifelse 以及各自縮排的程式碼,看成一個 完整的程式碼塊

elif 演練 —— 女友的節日

需求

  1. 定義 holiday_name 字串變數記錄節日名稱
  2. 如果是 情人節 應該 買玫瑰看電影
  3. 如果是 平安夜 應該 買蘋果吃大餐
  4. 如果是 生日 應該 買蛋糕
  5. 其他的日子每天都是節日啊……
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 的巢狀 演練 —— 火車站安檢

需求

  1. 定義布林型變數 has_ticket 表示是否有車票
  2. 定義整型變數 knife_length 表示刀的長度,單位:釐米
  3. 首先檢查是否有車票,如果有,才允許進行 安檢
  4. 安檢時,需要檢查刀的長度,判斷是否超過 20 釐米
    • 如果超過 20 釐米,提示刀的長度,不允許上車
    • 如果不超過 20 釐米,安檢通過
  5. 如果沒有車票,不允許進門
# 定義布林型變數 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. 綜合應用 —— 石頭剪刀布

目標

  1. 強化 多個條件邏輯運算
  2. 體會 import 匯入模組(“工具包”)的使用

需求

  1. 從控制檯輸入要出的拳 —— 石頭(1)/剪刀(2)/布(3)
  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] 之間的整數,包含 ab

  • 例如:

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 迴圈計算

在程式開發中,通常會遇到 利用迴圈 重複計算 的需求

遇到這種需求,可以:

  1. while 上方定義一個變數,用於 存放最終計算結果
  2. 在迴圈體內部,每次迴圈都用 最新的計算結果更新 之前定義的變數

需求

  • 計算 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 之間 所有 偶數 的累計求和結果

開發步驟

  1. 編寫迴圈 確認 要計算的數字
  2. 新增 結果 變數,在迴圈內部 處理計算結果
# 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

breakcontinue 是專門在迴圈中使用的關鍵字

  • break 某一條件滿足時,退出迴圈,不再執行後續重複的程式碼
  • continue 某一條件滿足時,不執行後續重複的程式碼

breakcontinue 只針對 當前所在迴圈 有效

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

開發步驟

    1. 列印 9 行小星星
*
**
***
****
*****
******
*******
********
*********
    1. 將每一個 * 替換成對應的行與列相乘
# 定義起始行
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) 面相過程 —— 怎麼做

  1. 把完成某一個需求的 所有步驟 從頭到尾 逐步實現
  2. 根據開發需求,將某些 功能獨立 的程式碼 封裝 成一個又一個 函式
  3. 最後完成的程式碼,就是順序地呼叫 不同的函式

特點

  1. 注重 步驟與過程,不注重職責分工
  2. 如果需求複雜,程式碼會變得很複雜
  3. 開發複雜專案,沒有固定的套路,開發難度很大!

2) 面向物件 —— 誰來做

相比較函式,面向物件更大封裝,根據 職責一個物件中 封裝 多個方法

  1. 在完成某一個需求前,首先確定 職責 —— 要做的事情(方法)
  2. 根據 職責 確定不同的 物件,在 物件 內部封裝不同的 方法(多個)
  3. 最後完成的程式碼,就是順序地讓 不同的物件 呼叫 不同的方法

特點

  1. 注重 物件和職責,不同的物件承擔不同的職責
  2. 更加適合應對複雜的需求變化,是專門應對複雜專案開發,提供的固定套路
  3. 需要在面向過程基礎上,再學習一些面向物件的語法

類和物件

目標

  • 類和物件的概念
  • 類和物件的關係
  • 類的設計

01. 類和物件的概念

物件面向物件程式設計的 兩個 核心概念

1.1 類

  • 是對一群具有 相同 特徵 或者 行為 的事物的一個統稱,是抽象的,不能直接使用
    • 特徵 被稱為 屬性
    • 行為 被稱為 方法
  • 就相當於製造飛機時的圖紙,是一個 模板,是 負責建立物件的

1.2 物件

  • 物件由類創建出來的一個具體存在,可以直接使用
  • 哪一個類 創建出來的 物件,就擁有在 哪一個類 中定義的:
    • 屬性
    • 方法
  • 物件 就相當於用 圖紙 製造 的飛機

在程式開發中,應該 先有類,再有物件

02. 類和物件的關係

  • 類是模板物件 是根據 這個模板創建出來的,應該 先有類,再有物件
  • 只有一個,而 物件 可以有很多個
    • 不同的物件 之間 屬性 可能會各不相同
  • 中定義了什麼 屬性和方法物件 中就有什麼屬性和方法,不可能多,也不可能少

03. 類的設計

在使用面相物件開發前,應該首先分析需求,確定一下,程式中需要包含哪些類!

在程式開發中,要設計一個類,通常需要滿足一下三個要素:

  1. 類名 這類事物的名字,滿足大駝峰命名法
  2. 屬性 這類事物具有什麼樣的特徵
  3. 方法 這類事物具有什麼樣的行為

大駝峰命名法

CapWords

  1. 每一個單詞的首字母大寫
  2. 單詞與單詞之間沒有下劃線

3.1 類名的確定

名詞提煉法 分析 整個業務流程,出現的 名詞,通常就是找到的類

3.2 屬性和方法的確定

  • 物件的特徵描述,通常可以定義成 屬性
  • 物件具有的行為(動詞),通常可以定義成 方法

提示:需求中沒有涉及的屬性或者方法在設計類時,不需要考慮

練習 1

需求

  • 小明 今年 18 歲身高 1.75,每天早上 完步,會去 東西
  • 小美 今年 17 歲身高 1.65,小美不跑步,小美喜歡 東西

練習 2

需求

  • 一隻 黃顏色狗狗大黃
  • 看見生人 汪汪叫
  • 看見家人 搖尾巴

面相物件基礎語法

目標

  • dir 內建函式
  • 定義簡單的類(只包含方法)
  • 方法中的 self 引數
  • 初始化方法
  • 內建方法和屬性

01. dir 內建函式(知道)

  • Python物件幾乎是無所不在的,我們之前學習的 變數資料函式 都是物件

Python 中可以使用以下兩個方法驗證:

  1. 識別符號 / 資料 後輸入一個 .,然後按下 TAB 鍵,iPython 會提示該物件能夠呼叫的 方法列表
  2. 使用內建函式 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 第一個面向物件程式

需求

  • 小貓 魚,小貓

分析

  1. 定義一個貓類 Cat
  2. 定義兩個方法 eatdrink
  3. 按照需求 —— 不需要定義屬性
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()

提問:tomlazy_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 初始化方法

  • 當使用 類名() 建立物件時,會 自動 執行以下操作:
    1. 為物件在記憶體中 分配空間 —— 建立物件
    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__ 方法進行 改造
    1. 把希望設定的屬性值,定義成 __init__ 方法的引數
    2. 在方法內部使用 self.屬性 = 形參 接收外部傳遞的引數
    3. 在建立物件時,使用 類名(屬性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. 封裝

  1. 封裝 是面向物件程式設計的一大特點
  2. 面向物件程式設計的 第一步 —— 將 屬性方法 封裝 到一個抽象的
  3. 外界 使用 建立 物件,然後 讓物件呼叫方法
  4. 物件方法的細節 都被 封裝類的內部

02. 小明愛跑步

需求

  1. 小明 體重 75.0 公斤
  2. 小明每次 跑步 會減肥 0.5 公斤
  3. 小明每次 吃東西 體重增加 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 小明愛跑步擴充套件 —— 小美也愛跑步

需求

  1. 小明小美 都愛跑步
  2. 小明 體重 75.0 公斤
  3. 小美 體重 45.0 公斤
  4. 每次 跑步 都會減少 0.5 公斤
  5. 每次 吃東西 都會增加 1 公斤

提示

  1. 物件的方法內部,是可以 直接訪問物件的屬性
  2. 同一個類 建立的 多個物件 之間,屬性 互不干擾!

03. 擺放傢俱

需求

  1. 房子(House)戶型總面積傢俱名稱列表
    • 新房子沒有任何的傢俱
  2. 傢俱(HouseItem)名字佔地面積,其中
    • 席夢思(bed) 佔地 4 平米
    • 衣櫃(chest) 佔地 2 平米
    • 餐桌(table) 佔地 1.5 平米
  3. 將以上三件 傢俱 新增房子
  4. 列印房子時,要求輸出:戶型總面積剩餘面積傢俱名稱列表

剩餘面積

  1. 在建立房子物件時,定義一個 剩餘面積的屬性初始值和總面積相等
  2. 當呼叫 add_item 方法,向房間 新增傢俱 時,讓 剩餘面積 -= 傢俱面積

思考:應該先開發哪一個類?

答案 —— 傢俱類

  1. 傢俱簡單
  2. 房子要使用到傢俱,被使用的類,通常應該先開發

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)

小結

  1. 建立了一個 傢俱類,使用到 __init____str__ 兩個內建方法
  2. 使用 傢俱類 建立了 三個傢俱物件,並且 輸出傢俱資訊

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)

小結

  1. 建立了一個 房子類,使用到 __init____str__ 兩個內建方法
  2. 準備了一個 add_item 方法 準備新增傢俱
  3. 使用 房子類 建立了 一個房子物件
  4. 房子物件 呼叫了三次 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

目標

  • 士兵突擊案例
  • 身份運算子
    封裝
  1. 封裝 是面向物件程式設計的一大特點
  2. 面向物件程式設計的 第一步 —— 將 屬性方法 封裝 到一個抽象的
  3. 外界 使用 建立 物件,然後 讓物件呼叫方法
  4. 物件方法的細節 都被 封裝類的內部

一個物件的 屬性 可以是 另外一個類建立的物件

01. 士兵突擊

需求

  1. 士兵 許三多 有一把 AK47
  2. 士兵 可以 開火
  3. 能夠 發射 子彈
  4. 裝填 裝填子彈 —— 增加子彈數量

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()

小結

  1. 建立了一個 士兵類,使用到 __init__ 內建方法
  2. 在定義屬性時,如果 不知道設定什麼初始值,可以設定為 None
  3. 封裝的 方法內部,還可以讓 自己的 使用其他類建立的物件屬性 呼叫已經 封裝好的方法

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 基類提供的 內建的靜態方法,主要作用有兩個:
      1. 在記憶體中為物件 分配空間
      1. 返回 物件的引用
  • 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 中的單例

  • 單例 —— 讓 建立的物件,在系統中 只有 唯一的一個例項
    1. 定義一個 類屬性,初始值是 None,用於記錄 單例物件的引用
    2. 重寫 __new__ 方法
    3. 如果 類屬性 is None,呼叫父類方法分配空間,並在類屬性中記錄結果
    4. 返回 類屬性 中記錄的 物件引用
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__ 方法改造之後,每次都會得到 第一次被建立物件的引用
  • 但是:初始化方法還會被再次呼叫

需求

  • 初始化動作 只被 執行一次

解決辦法

  1. 定義一個類屬性 init_flag 標記是否 執行過初始化動作,初始值為 False
  2. __init__ 方法中,判斷 init_flag,如果為 False 就執行初始化動作
  3. 然後將 init_flag 設定為 True
  4. 這樣,再次 自動 呼叫 __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)

多型

目標

  • 多型

面向物件三大特性

  1. 封裝 根據 職責屬性方法 封裝 到一個抽象的

    • 定義類的準則
  2. 繼承 實現程式碼的重用,相同的程式碼不需要重複的編寫

    • 設計類的技巧
    • 子類針對自己特有的需求,編寫特定的程式碼
  3. 多型 不同的 子類物件 呼叫相同的 父類方法,產生不同的執行結果

    • 多型 可以 增加程式碼的靈活度
    • 繼承重寫父類方法 為前提
    • 是呼叫方法的技巧,不會影響到類的內部設計

多型案例演練

需求

  1. Dog 類中封裝方法 game
    • 普通狗只是簡單的玩耍
  2. 定義 XiaoTianDog 繼承自 Dog,並且重寫 game 方法
    • 哮天犬需要在天上玩耍
  3. 定義 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)
        

繼承

目標

  • 單繼承
  • 多繼承

面向物件三大特性

  1. 封裝 根據 職責屬性方法 封裝 到一個抽象的
  2. 繼承 實現程式碼的重用,相同的程式碼不需要重複的編寫
  3. 多型 不同的物件呼叫相同的方法,產生不同的執行結果,增加程式碼的靈活度

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. 對父類方法進行 擴充套件

1) 覆蓋父類的方法

  • 如果在開發中,父類的方法實現子類的方法實現完全不同
  • 就可以使用 覆蓋 的方式,在子類中 重新編寫 父類的方法實現

具體的實現方式,就相當於在 子類中 定義了一個 和父類同名的方法並且實現

重寫之後,在執行時,只會呼叫 子類中重寫的方法,而不再會呼叫 父類封裝的方法

2) 對父類方法進行 擴充套件

  • 如果在開發中,子類的方法實現包含 父類的方法實現
    • 父類原本封裝的方法實現子類方法的一部分
  • 就可以使用 擴充套件 的方式
    1. 在子類中 重寫 父類的方法
    2. 在需要的位置使用 super().父類方法 來呼叫父類方法的執行
    3. 程式碼其他的位置針對子類的需求,編寫 子類特有的程式碼實現
關於 super
  • Pythonsuper 是一個 特殊的類
  • super() 就是使用 super 類創建出來的物件
  • 最常 使用的場景就是在 重寫父類方法時,呼叫 在父類中封裝的方法實現
呼叫父類方法的另外一種方式(知道)

Python 2.x 時,如果需要呼叫父類的方法,還可以使用以下方式:

父類名.方法(self)
  • 這種方式,目前在 Python 3.x 還支援這種方式
  • 這種方法 不推薦使用,因為一旦 父類發生變化,方法呼叫位置的 類名 同樣需要修改

提示

  • 在開發時,父類名super() 兩種方式不要混用
  • 如果使用 當前子類名 呼叫方法,會形成遞迴呼叫,出現死迴圈

1.3 父類的 私有屬性 和 私有方法

  1. 子類物件 不能 在自己的方法內部,直接 訪問 父類的 私有屬性私有方法
  2. 子類物件 可以通過 父類公有方法 間接 訪問到 私有屬性私有方法
  • 私有屬性、方法 是物件的隱私,不對外公開,外界 以及 子類 都不能直接訪問
  • 私有屬性、方法 通常用於做一些內部的事情

示例

  • 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 新式類與舊式(經典)類

objectPython 為所有物件提供的 基類,提供有一些內建的屬性和方法,可以使用 dir 函式檢視

  • 新式類:以 object 為基類的類,推薦使用

  • 經典類:不以 object 為基類的類,不推薦使用

  • Python 3.x 中定義類時,如果沒有指定父類,會 預設使用 object 作為該類的 基類 —— Python 3.x 中定義的類都是 新式類

  • Python 2.x 中定義類時,如果沒有指定父類,則不會以 object 作為 基類

新式類經典類 在多繼承時 —— 會影響到方法的搜尋順序

為了保證編寫的程式碼能夠同時在 Python 2.xPython 3.x 執行!
今後在定義類時,如果沒有父類,建議統一繼承自 object

class 類名(object):
    pass

類屬性和類方法

目標

  • 類的結構
  • 類屬性和例項屬性
  • 類方法和靜態方法

01. 類的結構

1.1 術語 —— 例項

  1. 使用面相物件開發,第 1 步 是設計
  2. 使用 類名() 建立物件,建立物件 的動作有兩步:
      1. 在記憶體中為物件 分配空間
      1. 呼叫初始化方法 __init__物件初始化
  3. 物件建立後,記憶體 中就有了一個物件的 實實在在 的存在 —— 例項

因此,通常也會把:

  1. 創建出來的 物件 叫做 例項
  2. 建立物件的 動作 叫做 例項化
  3. 物件的屬性 叫做 例項屬性
  4. 物件呼叫的方法 叫做 例項方法

在程式執行時:

  1. 物件各自擁有自己的 例項屬性
  2. 呼叫物件方法,可以通過 self.
    • 訪問自己的屬性
    • 呼叫自己的方法

結論

  • 每一個物件 都有自己 獨立的記憶體空間儲存各自不同的屬性
  • 多個物件的方法在記憶體中只有一份,在呼叫方法時,需要把物件的引用 傳遞到方法內部

1.2 類是一個特殊的物件

Python一切皆物件

  • class AAA: 定義的類屬於 類物件
  • obj1 = AAA() 屬於 例項物件
  • 在程式執行時, 同樣 會被載入到記憶體
  • Python 中, 是一個特殊的物件 —— 類物件
  • 在程式執行時,類物件 在記憶體中 只有一份,使用 一個類 可以創建出 很多個物件例項
  • 除了封裝 例項屬性方法外,類物件 還可以擁有自己的 屬性方法
    1. 類屬性
    2. 類方法
  • 通過 類名. 的方式可以 訪問類的屬性 或者 呼叫類的方法

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屬性的獲取 存在一個 向上查詢機制

  • 因此,要訪問類屬性有兩種方式:

    1. 類名.類屬性
    2. 物件.類屬性 (不推薦)

注意

  • 如果使用 物件.類屬性 = 值 賦值語句,只會 給物件新增一個屬性,而不會影響到 類屬性的值

03. 類方法和靜態方法

3.1 類方法

  • 類屬性 就是針對 類物件 定義的屬性
    • 使用 賦值語句class 關鍵字下方可以定義 類屬性
    • 類屬性 用於記錄 與這個類相關 的特徵
  • 類方法 就是針對 類物件 定義的方法
    • 類方法 內部可以直接訪問 類屬性 或者呼叫其他的 類方法

語法如下

@classmethod
def 類方法名(cls):
    pass
  • 類方法需要用 修飾器 @classmethod 來標識,告訴直譯器這是一個類方法
  • 類方法的 第一個引數 應該是 cls
    • 哪一個類 呼叫的方法,方法內的 cls 就是 哪一個類的引用
    • 這個引數和 例項方法 的第一個引數是 self 類似
    • 提示 使用其他名稱也可以,不過習慣使用 cls
  1. 通過 類名. 呼叫 類方法呼叫方法時,不需要傳遞 cls 引數
  2. 在方法內部
    • 可以通過 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 方法綜合案例

需求

  1. 設計一個 Game
  2. 屬性:
    • 定義一個 類屬性 top_score 記錄遊戲的 歷史最高分
    • 定義一個 例項屬性 player_name 記錄 當前遊戲的玩家姓名
  3. 方法:
    • 靜態方法 show_help 顯示遊戲幫助資訊
    • 類方法 show_top_score 顯示歷史最高分
    • 例項方法 start_game 開始當前玩家的遊戲
  4. 主程式步驟
      1. 檢視幫助資訊
      1. 檢視歷史最高分
      1. 建立遊戲物件,開始遊戲

案例小結

  1. 例項方法 —— 方法內部需要訪問 例項屬性
    • 例項方法 內部可以使用 類名. 訪問類屬性
  2. 類方法 —— 方法內部 需要訪問 類屬性
  3. 靜態方法 —— 方法內部,不需要訪問 例項屬性類屬性

提問

如果方法內部 即需要訪問 例項屬性,又需要訪問 類屬性,應該定義成什麼方法?

答案

  • 應該定義 例項方法
  • 因為,類只有一個,在 例項方法 內部可以使用 類名. 訪問類屬性
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 的直譯器在 匯入模組 時,會:

  1. 搜尋 當前目錄 指定模組名的檔案,如果有就直接匯入
  2. 如果沒有,再搜尋 系統目錄

在開發時,給檔案起名,不要和 系統的模組檔案 重名

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 包名 可以一次性匯入 所有的模組

案例演練

  1. 新建一個 hm_message
  2. 在目錄下,新建兩個檔案 send_messagereceive_message
  3. send_message 檔案中定義一個 send 函式
  4. receive_message 檔案中定義一個 receive 函式
  5. 在外部直接匯入 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 操作檔案的套路

計算機 中要操作檔案的套路非常固定,一共包含三個步驟

  1. 開啟檔案
  2. 讀、寫檔案
    • 將檔案內容讀入記憶體
    • 將記憶體內容寫入檔案
  3. 關閉檔案

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 編碼

  • 計算機中只有 256ASCII 字元
  • 一個 ASCII 在記憶體中佔用 1 個位元組 的空間
    • 80/1 的排列組合方式一共有 256 種,也就是 2 ** 8

UTF-8 編碼格式

  • 計算機中使用 1~6 個位元組 來表示一個 UTF-8 字元,涵蓋了 地球上幾乎所有地區的文字
  • 大多數漢字會使用 3 個位元組 表示
  • UTF-8UNICODE 編碼的一種編碼格式

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 直譯器 丟擲異常 時,最後一行錯誤資訊的第一個單詞,就是錯誤型別

異常型別捕獲演練 —— 要求使用者輸入整數

需求

  1. 提示使用者輸入一個整數
  2. 使用 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. 異常的傳遞

  • 異常的傳遞 —— 當 函式/方法 執行 出現異常,會 將異常傳遞 給 函式/方法 的 呼叫一方
  • 如果 傳遞到主程式,仍然 沒有異常處理,程式才會被終止

提示

  • 在開發中,可以在主函式中增加 異常捕獲
  • 而在主函式中呼叫的其他函式,只要出現異常,都會傳遞到主函式的 異常捕獲
  • 這樣就不需要在程式碼中,增加大量的 異常捕獲,能夠保證程式碼的整潔

需求

  1. 定義函式 demo1() 提示使用者輸入一個整數並且返回
  2. 定義函式 demo2() 呼叫 demo1()
  3. 在主程式中呼叫 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 異常類
  • 在開發時,如果滿足 特定業務需求時,希望 丟擲異常,可以:
    1. 建立 一個 Exception物件
    2. 使用 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 快速體驗

  • 所謂函式,就是把 具有獨立功能的程式碼塊 組織為一個小模組,在需要的時候 呼叫
  • 函式的使用包含兩個步驟:
    1. 定義函式 —— 封裝 獨立的功能
    2. 呼叫函式 —— 享受 封裝 的成果
  • 函式的作用,在開發程式時,使用函式可以提高編寫的效率以及程式碼的 重用

演練步驟

  1. 新建 04_函式 專案
  2. 複製之前完成的 乘法表 檔案
  3. 修改檔案,增加函式定義 multiple_table():
  4. 新建另外一個檔案,使用 import 匯入並且呼叫函式

02. 函式基本使用

2.1 函式的定義

定義函式的格式如下:

def 函式名():

    函式封裝的程式碼
    ……
  1. def 是英文 define 的縮寫
  2. 函式名稱 應該能夠表達 函式封裝程式碼 的功能,方便後續的呼叫
  3. 函式名稱 的命名應該 符合 識別符號的命名規則
    • 可以由 字母下劃線數字 組成
    • 不能以數字開頭
    • 不能與關鍵字重名

2.2 函式呼叫

呼叫函式很簡單的,通過 函式名() 即可完成對函式的呼叫

2.3 第一個函式演練

需求

    1. 編寫一個打招呼 say_hello 的函式,封裝三行打招呼的程式碼
    1. 在函式下方呼叫打招呼的程式碼
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. 函式的引數

演練需求

  1. 開發一個 sum_2_num 的函式
  2. 函式能夠實現 兩個數字的求和 功能

演練程式碼如下:

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 引數的作用

  • 函式,把 具有獨立功能的程式碼塊 組織為一個小模組,在需要的時候 呼叫
  • 函式的引數,增加函式的 通用性,針對 相同的資料處理邏輯,能夠 適應更多的資料
    1. 在函式 內部,把引數當做 變數 使用,進行需要的資料處理
    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 檔案(瞭解)

Ccompiled 編譯過 的意思

操作步驟

  1. 瀏覽程式目錄會發現一個 __pycache__ 的目錄
  2. 目錄下會有一個 hm_10_分隔線模組.cpython-35.pyc 檔案,cpython-35 表示 Python 直譯器的版本
  3. 這個 pyc 檔案是由 Python 直譯器將 模組的原始碼 轉換為 位元組碼
    • Python 這樣儲存 位元組碼 是作為一種啟動 速度的優化

位元組碼

  • Python 在解釋源程式時是分成兩個步驟的

    1. 首先處理原始碼,編譯 生成一個二進位制 位元組碼
    2. 再對 位元組碼 進行處理,才會生成 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 只能使用不可變型別的資料

注意

  1. 可變型別的資料變化,是通過 方法 來實現的
  2. 如果給一個可變型別的變數,賦值了一個新的資料,引用會修改
    • 變數 不再 對之前的資料引用
    • 變數 改為 對新賦值的資料引用

雜湊 (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. 首先 查詢 函式內部 是否存在 指定名稱 的區域性變數如果有,直接使用
  2. 如果沒有,查詢 函式外部 是否存在 指定名稱 的全域性變數如果有,直接使用
  3. 如果還沒有,程式報錯!

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. 無引數,無返回值
  2. 無引數,有返回值
  3. 有引數,無返回值
  4. 有引數,有返回值

定義函式時,是否接收引數,或者是否返回結果,是根據 實際的功能需求 來決定的!

  1. 如果函式 內部處理的資料不確定,就可以將外界的資料以引數傳遞到函式內部
  2. 如果希望一個函式 執行完成後,向外界彙報執行結果,就可以增加函式的返回值

1.1 無引數,無返回值

此類函式,不接收引數,也沒有返回值,應用場景如下:

  1. 只是單純地做一件事情,例如 顯示選單
  2. 在函式內部 針對全域性變數進行操作,例如:新建名片,最終結果 記錄在全域性變數

注意:

  • 如果全域性變數的資料型別是一個 可變型別,在函式內部可以使用 方法 修改全域性變數的內容 —— 變數的引用不會改變
  • 在函式內部,使用賦值語句 才會 修改變數的引用

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()

面試題 —— 交換兩個數字

題目要求

  1. 有兩個整數變數 a = 6, b = 100
  2. 不使用其他變數,交換兩個變數的值

解法 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. 預設引數,需要使用 最常見的值 作為預設值!
  2. 如果一個引數的值 不能確定,則不應該設定預設值,具體的數值在呼叫函式時,由外界傳遞!

預設引數的注意事項

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 —— 存放 字典 引數,前面有兩個 *
  • argsarguments 的縮寫,有變數的含義

  • kwkeyword 的縮寫,kwargs 可以記憶 鍵值對引數

def demo(num, *args, **kwargs):

    print(num)
    print(args)
    print(kwargs)


demo(1, 2, 3, 4, 5, name="小明", age=18, gender=True)

提示:多值引數 的應用會經常出現在網路上一些大牛開發的框架中,知道多值引數,有利於我們能夠讀懂大牛的程式碼

多值引數案例 —— 計算任意多個數字的和

需求

  1. 定義一個函式 sum_numbers,可以接收的 任意多個整數
  2. 功能要求:將傳遞的 所有數字累加 並且返回累加結果
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 遞迴函式的特點

特點

  • 一個函式 內部 呼叫自己
    • 函式內部可以呼叫其他函式,當然在函式內部也可以呼叫自己

程式碼特點

  1. 函式內部的 程式碼 是相同的,只是針對 引數 不同,處理的結果不同
  2. 引數滿足一個條件 時,函式不再執行
    • 這個非常重要,通常被稱為遞迴的出口,否則 會出現死迴圈

示例程式碼

def sum_numbers(num):

    print(num)
    
    # 遞迴的出口很重要,否則會出現死迴圈
    if num == 1:
        return

    sum_numbers(num - 1)
    
sum_numbers(3)

4.2 遞迴案例 —— 計算數字累加

需求

  1. 定義一個函式 sum_numbers
  2. 能夠接收一個 num 的整數引數
  3. 計算 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))

提示:遞迴是一個 程式設計技巧,初次接觸遞迴會感覺有些吃力!在處理 不確定的迴圈條件時,格外的有用,例如:遍歷整個檔案目錄的結構