### Python 模組與封包 (Modules and Packages) 完整讀書筆記
[心智圖](https://drive.google.com/file/d/1m_f8VUdct1sPJSsESOM4CIZF6bETavDf/view?usp=sharing)
#### 1. 為何需要模組 (Modules) 與封包 (Packages)?
在 Python 中,若程式規模很小,所有程式碼可集中於單一 `.py` 文件中。然而,隨著程式規模擴大、功能複雜化、開發團隊壯大,將程式碼分散為多個檔案並以模組、封包的方式組織是必然趨勢。
- **模組 (Module)**:
一個 `.py` 文件即為一個模組。透過將程式碼分為多個模組,我們可以:
以下是四點優勢的具體說明和例子:
1. **可重用性**:
- **說明**:模塊中的代碼可以在不同的程序中重複使用,避免重複編寫相同的功能。
- **例子**:假設你有一個模塊 `math_operations.py`,其中定義了一些基本的數學運算函數。這些函數可以在未來的任何項目中通過 `import math_operations` 來使用,不需要再次編寫相同的邏輯。
2. **簡潔性**:
- **說明**:將大程序分割成多個模塊,使每個文件的代碼量減少,從而提升可讀性。
- **例子**:如果在一個大型項目中,你將用戶界面的代碼分成 `ui.py`,數據處理的代碼分成 `data_processing.py`,這樣每個文件的責任單一,代碼更清晰易懂。
3. **組織性**:
- **說明**:模塊使得文件結構更有條理,能夠按功能或邏輯分開管理代碼。
- **例子**:一個網站項目可以按功能分成多個模塊,例如 `authentication.py` 處理用戶認證,`database.py` 處理數據庫交互,這樣的結構使得項目更易於導航和管理。
4. **易維護性**:
- **說明**:模塊化的代碼更容易調試和更新,因為每個模塊的變動不會直接影響到其他模塊。
- **例子**:當需要更新或修復某個功能時,只需要修改對應的模塊,而不必擔心影響整個系統。例如,更新 `report_generator.py` 中的報告生成邏輯,不會影響到 `user_interface.py` 的代碼。
這些優勢展示了模塊化設計如何提升程序的可維護性和可擴展性。
- **封包 (Package)**:
一個目錄 (folder) 包含多個模組與一個 `__init__.py`(Python 3.3 後可選),即為一個封包。封包能有效地分門別類、將相關功能模組集中管理。
**範例**:
```python
# another_module.py
def one_function():
print("Hello from another module")
# try.py
from another_module import one_function
one_function() # 輸出: Hello from another module
```
---
#### 2. 如何自建模組和封包
- **建立自定義模組**:
創建一個新 `.py` 檔,定義函式與變數,即為模組。
之後在主程式中以 `import your_module` 或 `from your_module import ...` 使用。
- **建立自定義封包**:
建立一個資料夾 (如 `my_package`),在裡面放置多個模組檔案 (如 `some_code.py`、`some_more_code.py`),並在主程式中使用類似 `from my_package import some_code` 的語法即可匯入該封包內的模組。
---
#### 3. 模組導入 (Import) 的多種方式
Python 提供多種 `import` 語法:
1. `import module_name`:
- 全部匯入模組名稱空間。
- 缺點:若模組名長,程式碼可讀性略差。
2. `import module_name as mn`:
- 將模組重新命名,增加可讀性。
- 最常見範例:`import numpy as np`、`import pandas as pd`
- **推薦用法**:
3. `from module import func1, func2`:
- 只匯入特定函式或變數。
- 缺點:可能與其他模組函式重名 (Name Shadowing)。
4. `from module import *`:
- 匯入模組內所有內容,易造成名稱衝突,建議避免。
**範例**:
```python
import random as rd
print(rd.randint(0, 5))
```
---
#### 4. 模組的搜尋路徑與 sys.path
- **模組搜尋路徑**:
Python 在執行程式時會建立一個路徑列表 `sys.path`,用以搜尋要導入的模組。
- **查看 sys.path**:
```python
import sys
print(sys.path)
```
- 若要使用自訂模組,可將檔案放在 `sys.path` 指定的目錄中,或自行調整環境變數以讓 Python 找得到你的模組。
---
#### 5. __pycache__ 資料夾與 Bytecode (字節碼)
- **__pycache__**:
當 Python 導入模組時,會將其編譯成位元組碼(bytecode),並存放在 `__pycache__` 資料夾內。下次執行時就可更快載入,提升執行效率。
- **Bytecode**:
是 Python 將原始程式碼編譯後的中介形式,非人類可讀,但執行效率較高,並保有跨平台特性。
字節碼(bytecode)是介於高階語言(如 Python)和機器碼之間的一種中間表示形式。它是一組指令集,能夠被虛擬機(如 Python 虛擬機器,PVM)解釋和執行。以下是有關字節碼的詳細說明:
1. **生成過程:**
- 當你編寫的 Python 源代碼被 Python 編譯器處理後,首先被編譯成字節碼。
- 這個過程是自動的,當你執行或導入一個 Python 文件時,Python 會自動完成這個編譯過程。
2. **特點:**
- **平台獨立性**:字節碼設計為平台無關,這意味著一旦編譯,字節碼可以在任何支持該虛擬機的平臺上運行,而不需要重新編譯。
- **高效性**:相比直接解釋源代碼,執行字節碼能提高性能,因為字節碼省略了語法解析等過程。
3. **作用和優勢:**
- **加快執行速度**:通過避免每次運行時的語法解析,字節碼可以改善程序的啟動速度。
- **簡化不同環境的支持**:由於字節碼是平台無關的,中間層使得 Python 可以在不同的操作系統上運行而不需要改變代碼。
- **安全性**:字節碼相比於直接分發源代碼,更難以被逆向工程,因為它不包括開發者的原始代碼結構和註釋。
4. **運行過程:**
- 編譯後的字節碼文件通常以 `.pyc` 為擴展名,存儲在 `__pycache__` 資料夾中。
- 當 Python 程序運行時,Python 虛擬機器(PVM)會讀取這些字節碼文件並逐條執行,這類似於 Java 虛擬機(JVM)對 Java 字節碼的處理。
5. **限制:**
- 雖然字節碼提高了執行效率,但它仍然需要一個虛擬機來解釋,所以不如直接編譯成機器碼的程序執行得快。
- 字節碼並不是最終的可執行文件,需要 Python 虛擬機的支持才能運行。
總之,字節碼是 Python 程序的一個中間形式,提供了跨平台支持和性能上的優勢,使得 Python 的解釋和執行更加高效。
---
#### 6. `if __name__ == "__main__"` 的用法
`if __name__ == "__main__"` 是判斷程式執行方式的關鍵語句:
- 當該 `.py` 檔案被「直接執行」時,`__name__` 會是 `"__main__"`,該區塊內的程式碼會被執行。
- 當該 `.py` 檔案被「當作模組導入」時,`__name__` 會是模組名稱,此區塊內程式碼不執行。
這有助於在同一檔案內進行測試或確保特定程式片段只在獨立執行時運行。
**範例**:
```python
# main_test.py
def hello():
print("Hello!")
if __name__ == "__main__":
hello() # 只有直執行 main_test.py 時才會執行
```
---
#### 7. Namespace(命名空間)與 Scope(作用域)
- **命名空間 (Namespace)**:
將程式中已定義的名稱(函式、變數)存放於字典結構中,以區分不同範圍內的名稱。
- **三大命名空間**:
以下是 LEGB 規則中四個命名空間的詳細範例:
1. **Local (局部命名空間)**:
- **說明**:指的是函數內部定義的變量。每次調用函數時,這些變量都會被重新創建。
- **範例**:
```python
def my_function():
local_var = 10 # 這是局部變量
print("Local:", local_var)
my_function() # 輸出:Local: 10
```
2. **Enclosing (閉包函數命名空間)**:
- **說明**:指的是嵌套函數所在的外部函數的命名空間。
- **範例**:
```python
def outer_function():
enclosing_var = 20 # 這是閉包函數的變量
def inner_function():
print("Enclosing:", enclosing_var)
inner_function()
outer_function() # 輸出:Enclosing: 20
```
3. **Global (全局命名空間)**:
- **說明**:指的是模塊級別的變量,通常在整個模塊中可用。
- **範例**:
```python
global_var = 30 # 這是全局變量
def my_function():
print("Global:", global_var)
my_function() # 輸出:Global: 30
```
---
3.1 **Global_var 及 Glabal glabal_var 不同處**
在 Python 中,`global` 關鍵字用於聲明一個變量在全局命名空間中。這意味著該變量在模塊級別上定義,並且在模塊中的任何地方都可以訪問和修改。
當你在函數內部使用 `global` 關鍵字聲明一個變量時,Python 會知道你希望在全局命名空間中使用該變量,而不是創建一個新的局部變量。
範例:
```python
global_var = 100 # 這是全局變量
def modify_global():
global global_var # 聲明使用全局變量
global_var = 200 # 修改全局變量
def print_global():
print("Global variable:", global_var)
print_global() # 輸出:Global variable: 100
modify_global()
print_global() # 輸出:Global variable: 200
```
在這個範例中:
- `global_var` 是一個全局變量,定義在模塊的最外層。
- `modify_global` 函數中使用 `global global_var`,表示要在全局命名空間中引用 `global_var`,而不是創建一個新的局部變量。
- 當 `modify_global` 函數被調用時,它修改了全局變量的值。
- `print_global` 函數可以訪問並打印出這個全局變量的值。
---
4. **Built-in (內建命名空間)**:
- **說明**:指的是 Python 內建的函數和名稱。
- **範例**:
```python
def my_function():
print("Built-in:", len("Hello")) # 使用內建函數 len()
my_function() # 輸出:Built-in: 5
```
這些範例展示了 LEGB 規則下變量和函數的查找順序,從最內層的局部命名空間到最外層的內建命名空間。
雖然 LEGB 涉及四個層次的查找,但通常在教學或討論中,會將重點放在 Local、Global、和 Built-in 三種主要類型上,因為 Enclosing 命名空間僅在使用嵌套函數或閉包時才會出現,而在大多數普通場景下,這三個命名空間是最常用和最需要理解的。
- **LEGB 規則**:
名稱解析順序為 Local → Enclosing → Global → Built-in。若皆找不到,拋出 `NameError`。
---
#### 8. Packages vs. Methods
在 Python 中,「Packages」和「Methods」是兩個不同層次的概念,主要在於它們的用途和應用範圍上有所不同。以下是對這兩者的詳細比較:
Packages (封包)
**概念**:
- **定義**:一個 package 是一個包含多個模塊(modules)的目錄。它用於將相關的模塊組織在一起,以便於管理和使用。
- **結構**:通常包含一個 `__init__.py` 文件(可以是空的),這個文件表明該目錄是一個 Python package。
- **用途**:用來組織和封裝代碼,特別是當項目變得複雜時,有助於保持代碼結構的清晰和可維護性。
**特點**:
- 可以包含子包和模塊,形成一個層次結構。
- 用於分發和共享代碼。
- 支持命名空間,避免命名衝突。
**例子**:
```plaintext
my_project/
│
├── my_package/
│ ├── __init__.py
│ ├── module1.py
│ └── module2.py
```
Methods (方法)
**概念**:
- **定義**:方法是定義在類別(class)中的函數。它描述了類的行為,並且可以訪問和操作該類的屬性。
- **用途**:用來實現面向對象編程中的行為,可以在對象實例上調用以執行操作。
**特點**:
- 方法是特定於類的,並且通常需要通過對象的實例來調用。
- 可以訪問對象的屬性(使用 `self` 關鍵字)。
- 支持多態和繼承,允許子類覆蓋父類的方法。
**例子**:
```python
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says woof!"
# 使用方法
my_dog = Dog("Buddy")
print(my_dog.bark()) # Output: Buddy says woof!
```
### 小結
- **Packages** 是用於組織和管理代碼的結構,特別適合大型應用程序的模塊化。
- **Methods** 是類的一部分,描述對象的行為,用於實現面向對象的編程。
在開發中,packages 幫助你管理項目的代碼結構,而 methods 則讓你能夠在類中定義具體的行為和操作。
---
#### 9. PyPI 與 pip
- **PyPI (Python Package Index)**:
官方線上套件倉庫,收藏無數 Python 專案與套件。
- **pip**:
套件管理工具,可從 PyPI 安裝、升級或指定版本安裝套件。
**範例**:
```bash
pip install numpy
pip install --upgrade numpy
pip install numpy==1.20.0
pip freeze
```
---
### 總結
- 使用模組與封包能讓程式碼更易維護、重複利用、分工協作。
- `import` 有多種方式,建議使用 `import ... as ...` 取得簡潔清晰的程式碼風格。
- `__pycache__` 與 Bytecode 機制提高載入與執行效率。
- `if __name__ == "__main__"` 區分直執行與被導入的行為,有助測試與程式結構化。
- Namespace、LEGB 規則使名稱解析有序。
- 使用 `pip` 從 PyPI 安裝、升級各種第三方套件,快速擴充 Python 功能。
本讀書筆記,協助我從基礎概念、語法技巧,到開發、測試與套件管理,全面掌握 Python 模組與封包的重要知識。
### **以下統整成一個範例程式:**
範例涵蓋了 Python 模組和封包的基礎概念、語法技巧、開發和測試,以及使用套件管理工具的知識。
### 程式架構
```
my_project/
│
├── main.py
├── utils/
│ ├── __init__.py
│ ├── math_operations.py
│ └── string_operations.py
└── tests/
├── __init__.py
└── test_math_operations.py
```
### 檔案內容
#### utils/math_operations.py
```python
# 模組:數學運算功能
def add(a, b):
"""返回兩數相加的結果"""
return a + b
def subtract(a, b):
"""返回兩數相減的結果"""
return a - b
```
#### utils/string_operations.py
```python
# 模組:字符串運算功能
def to_uppercase(s):
"""將字符串轉換為大寫"""
return s.upper()
def to_lowercase(s):
"""將字符串轉換為小寫"""
return s.lower()
```
#### main.py
```python
# 主程序,使用 utils 模組
from utils.math_operations import add, subtract
from utils.string_operations import to_uppercase, to_lowercase
def main():
# 使用數學運算模組
print("Add:", add(10, 5))
print("Subtract:", subtract(10, 5))
# 使用字符串運算模組
print("Uppercase:", to_uppercase("hello"))
print("Lowercase:", to_lowercase("WORLD"))
if __name__ == "__main__":
main()
```
#### tests/test_math_operations.py
```python
# 測試模組
import unittest
from utils.math_operations import add, subtract
class TestMathOperations(unittest.TestCase):
def test_add(self):
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 1), 0)
def test_subtract(self):
self.assertEqual(subtract(10, 5), 5)
self.assertEqual(subtract(0, 0), 0)
if __name__ == '__main__':
unittest.main()
```
### 使用 pip 管理套件
假設我們需要使用第三方套件,例如 `numpy`,首先需要在命令行或終端中安裝:
```bash
pip install numpy
```
然後在代碼中使用:
```python
import numpy as np
def calculate_mean(numbers):
return np.mean(numbers)
```
### 說明
- **模組與封包**:`utils` 文件夾是封包,包含 `math_operations.py` 和 `string_operations.py` 兩個模組。
- **基礎概念**:使用 `import` 語句加載模組中的函數。
- **語法技巧**:定義函數並提供文檔字符串(docstring)。
- **開發與測試**:使用 `unittest` 標準庫進行單元測試。
- **套件管理**:使用 `pip` 安裝和管理第三方套件。
這個範例展示了如何組織 Python 程式碼,如何使用和管理模組,如何進行單元測試,以及如何使用 `pip` 來管理外部套件。
`pip freeze` 是一個命令行工具,用於列出當前 Python 環境中已安裝的所有包及其版本。這對於記錄和管理你的 Python 環境非常有用,特別是在需要重現或共享環境時。
### 使用 `pip freeze` 的場景
1. **生成依賴文件**:
- 通常用於創建一個 `requirements.txt` 文件,這個文件可以被其他開發者或部署環境用來安裝相同版本的包。
- 使用方法:
```bash
pip freeze > requirements.txt
```
- 這將把當前環境中所有已安裝包及其版本輸出到 `requirements.txt` 文件中。
2. **查看當前環境的包**:
- 你可以直接運行 `pip freeze` 來檢查當前環境中有哪些包及其版本。
- 使用方法:
```bash
pip freeze
```
- 這將在終端中列出所有已安裝的包及其版本號,例如:
```
numpy==1.21.2
pandas==1.3.3
requests==2.26.0
```
### `pip freeze` 的好處
- **環境一致性**:確保在不同環境中使用相同的包版本,避免版本不兼容問題。
- **版本控制**:方便地跟蹤和管理依賴包的版本。
- **輕鬆共享環境**:與他人協作時,可以輕鬆地將環境依賴分享給團隊成員。
### 重新創建環境
一旦擁有 `requirements.txt` 文件,其他人或你自己可以在新的環境中使用以下命令來安裝所有列出的依賴包:
```bash
pip install -r requirements.txt
```
這將確保新的開發環境與原始環境具有相同的包和版本,這對於開發、測試和部署非常有用。