# Modules / import
資訊之芽 2022 Python 語法班
Author: Sean 韋詠祥
Note:
日期:2022-04-17
課程時間:14:00 - 15:20
----
## 這堂課你會學到...
- 什麼是 Module(模組 / 套件)
- 各種 import 用法的差異
- 站在巨人的肩膀上:常用工具
Note:
簡報連結已放上課程網站
----
## 為何需要 Module
- 假設你寫出 5,000 行的大型程式
- 當你想重複利用同個函式
Note:
PyDoc 的 6. Modules 開頭就講了
- 重新打開 JupyterBook 會導致變數與函式消失,所以需要存成 script 以利持久化
- 既然寫成腳本了,程式漸漸變得愈來愈長,因此我們想切分檔案以利維護,也避免不必要的複製貼上
https://docs.python.org/3/tutorial/modules.html
----
## 什麼是 Module
- 獨立的 .py 檔案
- 可以被其他 Python 程式使用
---
# 如何使用 Module
- 在程式碼中使用 import(引入)語句
- 分為以下幾種方式
----
## 引入單個模組
```python
# 語法結構
import <模組名稱>
# 舉個例子 🌰
import math
x = math.floor(3.14)
print(x)
```
Note:
- 稱作 non-from import
- PEP 8 寫到,不該在一句話裡 import 多個模組
----
## 引入模組內的某些功能
- 被 import 的對象可以是
- 子模組、函式、常數、Class
- 可以 import 同模組內一個或多個物件
```python
# 語法結構
from <模組> import <模組內的東西>
from <模組> import <子模組>, <函式>, <常數>, <Class>
# 舉個例子 🌰
from string import hexdigits, capwords
print(hexdigits)
print(capwords('hello woRLD'))
```
Note:
- 稱作 from-import
----
## 引入模組內的全部功能
- 僅限早期開發時使用,要盡快改回指定名稱
- PEP 8 規範提到,在正式環境中應避免使用
```python
# 語法結構
from <模組> import *
# 舉個例子 🌰
from math import * # 裡面包含 floor, gcd, pi 等函式及變數
n = floor(9 * pi)
print('n =', n)
m = gcd(n, 35)
print('m =', m)
```
Note:
- 稱作 wildcard import
----
## 幫模組取別名
```python
# 語法結構
import <模組> as <別名>
from <模組> import <東西> as <別名>
# 舉個例子 🌰
import unicodedata as ud
from math import factorial as fact
print('Name 1:', ud.name('!')) # EXCLAMATION MARK
print('Name 2:', ud.name('😼')) # CAT FACE WITH WRY SMILE
print('5! = ', fact(5))
```
----
## 練習 1:三角函數
用 Python 計算以下數值
$$
3^5 \text{ = ?} \\
\sqrt{2} \text{ = ?} \\
\cos (\frac{\pi}{3}) \text{ = ?}
$$
Hint: 全部在 math 裡面
sqrt, cos, pow, pi
---
# 自己寫 Module
建立一個名為 `fibo.py` 的模組檔案
```python=0
# File: fibo.py
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
```
在 VS Code 中,請按 ^N (Ctrl-N) 新增檔案
寫完後用 ^S 存檔
Note:
來源:PyDoc Modules Tutorial
https://docs.python.org/3/tutorial/modules.html
----
## 使用方式
```python=
import fibo # 前面 fibo.py 檔名,拿掉 .py
fibo.fib(20)
```
### 或是這樣
```python=
from fibo import fib
fib(20)
```
在 VS Code 中,點右上角 ▷ 執行程式
----
## 練習 2:實作階乘函式
在 `sprout.py` 中,自己定義 `fact()` 函式
```python
fact(0) = 1
fact(1) = 1 # 1
fact(2) = 2 # 2 * 1
fact(3) = 6 # 3 * 2 * 1
fact(4) = 24 # 4 * 3 * 2 * 1
fact(5) = 120 # 5 * 4 * 3 * 2 * 1
fact(6) = 720 # 6 * 5 * 4 * 3 * 2 * 1
....
fact(15) = 1307674368000 # 15 * 14 * 13 * ... * 2 * 1
```
Hint: 可以在 `def fact()` 中遞迴呼叫 `fact()` 函式
----
## 模組中的 \_\_all__ 特殊變數
當有一些函式不希望被 `import *` 進來
```python=0
# File: sprout.py
__all__ = ['fact']
def is_negative(n):
if n < 0:
return True
return False
def fact(n):
if is_negative(n):
return -1 # Error!
if n > 1:
return n * fact(n-1)
return 1
```
在 `from sprout import *` 的時候,只有 `__all__` 裡面寫到的 `fact` 會被 import 進來
Note. 但可以被 `import sprout` 或 `from sprout import is_negative` 取用
<!-- .element: class="r-fit-text" -->
----
## 其他特殊變數
在模組中,還有可以加上....
```python
__author__ = 'Sean Wei'
__version__ = '1.19.2'
__license__ = 'MIT'
__status__ = 'Production'
__email__ = 'me@sean.taipei'
__copyright__ = 'Copyright 2022, Sprout Team'
```
---
# 常見的 Module
- 系統內建許多基本函式庫了
- 可以用 pip 安裝別人寫好的套件
- 自己開發完也可以放上 PyPI 給大家用
Note:
The Python Package Index (PyPI) is a repository of software for the Python programming language.
https://pypi.org/
請大家跟著做
可以用 Tab 或 \_\_all__ 找到有效函式、常數
----
## Math 數學
- 與 C 的 math.h 函式庫大致相同
```python
import math
print('gcd:', math.gcd(52, 91)) # 最大公因數
print('lcm:', math.lcm(15, 21)) # 最小公倍數
print('ceil:', math.ceil(4.2)) # 上高斯(無條件進位)
print('floor:', math.floor(4.2)) # 下高斯(無條件捨去)
# 前面是函式,這兩個沒有括號的是常數
print('Natural number:', math.e) # 自然數
print('Pi:', math.pi) # 圓周率 π
```
Note:
搜尋 "py math"
參考資料
https://docs.python.org/3/library/math.html
----
### String 字串
```python
import string
# 小寫字母:abcdefghijklmnopqrstuvwxyz
print('Lowercase:', string.ascii_lowercase)
# 標點符號:!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
print('Punctuation:', string.punctuation)
```
```python
# 可視字元:
print('Printable:', string.printable)
# 0123456789abcdefghijklmnopqrstuvwxyz
# ABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()
# *+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c
```
Note:
參考資料
https://docs.python.org/3/library/string.html
### ASCII Table
```
\t \x09 TAB (horizontal tab)
\n \x0a LF (LN line feed, new line)
\x0b VT (vertical tab)
\x0c FF (NP form feed, new page)
\r \x0d CR (carriage return)
```
Ref: https://www.asciitable.com/asciifull.gif
----
## Random 隨機
```python
import random
# 在 [0, 1) 之間的浮點數
print('Random number:', random.random())
# 在 [1, 5] 之間挑選一個整數
print('Lucky number:', random.randint(1, 5))
# 在 [20, 80) 之間挑選一個浮點數
print('Precipitation:', random.uniform(20, 80))
```
```python
# 產生內容為 [0, 1, 2, ... 48, 49] 的 list
A = [i for i in range(50)]
print('The list A:', A)
# 在 list 中隨機選一個值
print('Choice one:', random.choice(A))
random.shuffle(A) # 把 A 的順序打亂
print('After shuffle:', A) # 現在 A 被原地修改為新的順序了
```
- 用來模擬及建模用,但不適合拿來產生密碼
Note:
參考資料
https://docs.python.org/3/library/random.html
只要取得幾百個隨機數,就能精準預測後面的其他隨機輸出
https://github.com/tna0y/Python-random-module-cracker
----
## OS 作業系統介面
- 常見的檔案操作
```python
import os
# 取得目前目錄路徑
print('Current working directory:', os.getcwd())
# 列出目錄中檔案,以根目錄為例
print('Files:', os.listdir('/'))
```
```python
# 建立資料夾
os.mkdir('tmp-py2022-data')
# 移除檔案
os.remove('trash-file')
```
Note:
參考資料
https://docs.python.org/3/library/os.html
----
## sys 系統參數
- 取得系統執行環境資訊
```python
import sys
# 執行的參數列表
print('Arguments:', sys.argv)
# 套件路徑列表
print('Python Path:', sys.path)
# 版本資訊:3.9.12 (main, Mar 26 2022) [Clang 13.1.6]
print('Version:', sys.version)
```
Note:
參考資料
https://docs.python.org/3/library/sys.html
----
## Time 時間
- 時間相關的函式庫
```python
import time
# 目前秒數:1650177060
print('Time 1:', time.time())
# 中間暫停 1.5 秒
time.sleep(1.5)
# 會比上面的秒數多 1.5 秒
print('Time 2:', time.time())
```
```python
from datetime import datetime
# 結構化的目前時間:datetime(2022, 4, 17, 14, 55, 20, 123456)
t = datetime.now()
print('Datetime:', repr(t))
# 將時間格式化成指定格式:Sun, 17 Apr 2022
print('Format:', t.strftime('%a, %d %b %Y'))
```
Note:
參考資料
https://docs.python.org/3/library/time.html
https://docs.python.org/3/library/datetime.html
一班用 time
晚點有空再提 \_\_repr__()
----
## 慣用的套件縮寫
- 可以先有個概念就好,不需要記住
```python
# 矩陣運算
import numpy as np
```
```python
# 影像處理
import pillow as pil
```
```python
# 聊天機器人
import discord as dc
```
```python
# 網頁內容請求
import requests as req
```
```python
# 網頁爬蟲
import bs4.BeautifulSoup as bs
```
```python
# 資料視覺化
import matplotlib.pyplot as plt
```
```python
# Base64 編碼、解碼
import base64.b64encode as b64e
import base64.b64decode as b64d
```
----
## 練習 3:亂數產生器
#### 輸入格式
第 1 行輸入數字 N 代表長度(N ≥ 5)
第 2 行輸入 0 或 1,代表是否要包含大小寫字母
第 3 行輸入 0 或 1,代表是否要包含數字和標點符號
保證第 2 3 行不會同時為 0
#### 輸出要求
使用前面提到的 `strings` 及 `random` 函式庫
每次產生 5 個長度為 N 且符合要求的隨機字串
(就算輸入要求有數字,輸出也可以不出現數字,
只要正常隨機就好)
---
# Python 專案開發
拆成幾十個模組怎麼辦?
總不能全部丟在同個目錄吧
----
## 目錄架構
一個專案可能長得像這樣
```text
discord/ 專案頂層目錄
├── __init__.py ├── 初始化 discord 模組的腳本
├── commands/ ├── 指令相關模組
│ ├── __init__.py │
│ ├── options.py │
│ ├── context.py │
│ └── core.py │
├── utils.py │
```
```text
├── types/ ├── 專案內用到的自定義型別
│ ├── __init__.py │
│ ├── emoji.py │
│ ├── role.py │
│ └── user.py │
└── ui/ └── 介面相關程式碼
├── __init__.py
├── button.py
└── modal.py
```
----
## 何謂 \_\_init__.py
我們可以 import 一個 .py 檔案,那資料夾呢?
因此只有包含 `__init__.py` 的資料夾可以被引入
<br>
此檔案中會放初始化模組用的程式碼
並且用 `__all__` 特殊變數,指定同目錄下要引入哪些檔案或子資料夾
Note:
如果 import 一個套件,就把裡面所有子資料夾全部讀進來,那可能會遇到一些撞名的資料夾或檔案,導致不必要的麻煩
----
## 模組之間互相引用
- 可以用絕對名稱或相對名稱 import 其他模組
```python=0
# File: discord/commands/core.py
import request
from discord.ui import modal # /ui/modal.py
# 以下使用相對名稱 import
import ..utils # /utils.py
from . import options # /commands/options.py
from .context import response # /commands/context.py:response
from ..types import user # /types/user.py
from ..types.role import color # /types/role.py:color
```
- 不加點:使用絕對名稱引用模組
- 一個點:從此檔案所在目錄計算
- 兩個點:從套件的頂層目錄計算
---
# 常見雷點
- 找不到 / 未安裝 Module
- 檔案撞名
----
## NameError
找不到這個變數
```python
base64.b64decode('U3Byb3V0IDIwMjI=')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'base64' is not defined
```
### 解決方法
確定有 import 相關模組
```python
import <缺少的模組>
import base64
```
----
## ModuleNotFoundError
找不到這個模組
```python
import discord
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'discord'
```
### 解決方法
使用 pip 安裝相應套件
```shell
$ pip install <缺少的套件>
```
----
## pip not found
- 找不到 pip 指令
```text
C:\Users\sean> pip
'pip' is not recognized as an internal or external command,
operable program or batch file.
```
### 解決方法
到 Python 官網安裝
- 請確定有加入 PATH 環境變數
Note:
在這邊確保大家的環境是一致的
----
## AttributeError 檔案撞名
先上程式碼
```python=0
# File: math.py
import math
print(math.gcd(42, 18))
```
錯誤訊息
```python
Traceback (most recent call last):
File ".../math.py", line 1, in <module>
import math
File ".../math.py", line 3, in <module>
print(math.gcd(4, 6))
AttributeError: partially initialized module 'math' has no
attribute 'gcd' (most likely due to a circular import)
```
為什麼呢?
Note:
這裡的 import math 是自己 math.py 的意思
----
## ImportError 遞歸引用
```python=0
# File: main.py
from foo import func1
func1()
```
```python=0
# File: foo.py | # File: bar.py
from bar import func2 | from foo import func3
def func1(): | def func2():
func2() | print('Hey there!')
def func3(): | func3()
print('Goodbye~') |
```
```text
Traceback (most recent call last):
File ".../main.py", line 1, in <module>
from foo import func1
File ".../foo.py", line 1, in <module>
from bar import func2
File ".../bar.py", line 1, in <module>
from foo import func3
ImportError: cannot import name 'func3' from
partially initialized module 'foo' (most likely
due to a circular import) (.../foo.py)
```
Note:
即使此案例中 function 本身沒有造成迴圈也會出錯
---
# 補充資料
很重要但下週才會教的 Coding Style
----
## 每個 import 分開寫
```python
# ✅ 正確寫法
import os
import sys
# ❌ 錯誤示範
import sys, os
# ✅ 這樣也是正確的
from subprocess import Popen, PIPE
```
----
## 寫 import 的位置
- import 語句該放在檔案的最上面
- 可以置於(還沒教的)模組說明、docstrings 後
- 在全域變數、常數前
```python
#!/usr/bin/env python
"""Script for sprout demo
Meow meow meow meow meow
"""
import os
import math
_USAGE = 'Just run this script. :)'
```
----
## 排序方式
- 標準函式庫優先
- 用到的第三方套件其次
- 本地的程式、函式庫放最後
- 三大類別之間要有空行
- 先寫 non-from import 再 from-import
- 各類別內,照長度遞增排序
```python
import os
import string
from time import sleep
from datetime import datetime
import discord
import requests as req
from discord.ext import commands
import sproututils
```
----
## 特殊變數擺放位置
- 置於所有 import 前面
- 除了 `__future__` 等特殊 import
```python
"""This is the example module.
This module does stuff.
"""
from __future__ import annotations
__all__ = ['fact', 'exgcd', 'c']
__version__ = '0.1'
__author__ = 'Sprout 2022 Team'
import os
import sys
```
----
## 關於 import * 用法
- 會讓閱讀者不好分辨哪個函式、變數是誰的
- 讓程式碼檢查工具不好抓到未定義名稱
- 讓自動化工具不好抓到錯誤
- 雖然很方便,但因為種種缺點,應該盡量避免
----
## 補充用法:\_\_repr__()
```python
import datetime
t = datetime.datetime.now()
print('Case A:', t)
print('Case B:', str(t))
print('Case C:', repr(t))
s = repr(t) # 取得給直譯器看的 representation 格式
type(s) # 確認得到的是個字串 (str)
n = eval(s) # 把 str 轉換回 datetime
type(n) # 確認轉換成功
```
Note:
參考文章:repr 與 str 雜談
https://ithelp.ithome.com.tw/articles/10194593
---
# Thanks
投影片連結:https://hackmd.io/@Sean64/py-module-sprout2022
<!-- .element: class="r-fit-text" -->
<br>
[![CC-BY](https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by.png)](https://creativecommons.org/licenses/by/4.0/deed.zh_TW)
###### 這份投影片以 [創用 CC - 姓名標示](https://creativecommons.org/licenses/by/4.0/deed.zh_TW) 授權公眾使用,原始碼及講稿請見 [此連結](https://hackmd.io/@Sean64/py-module-sprout2022/edit)。
----
## 跟 import 有關的 Python 小彩蛋
```python
import __hello__
# Hello world!
```
```python
import this
# The Zen of Python, by Tim Peters
#
# Beautiful is better than ugly.
# Explicit is better than implicit.
# Simple is better than complex.
# Complex is better than complicated.
# [...]
```
```python
from __future__ import braces
# SyntaxError: not a chance
```
```python
# xkcd
import antigravity
# Geohashing: https://xkcd.com/426/
antigravity.geohash(24.787, 120.997, b'Meow')
```
----
## 參考資料 / 延伸閱讀
- PyDoc: [Modules tutorial](https://docs.python.org/3/tutorial/modules.html), [Import reference](https://docs.python.org/3/reference/import.html#submodules)
- PEP 8: [Imports](https://peps.python.org/pep-0008/#imports)
- 歷年資芽講義:[Py 2021](https://tw-csie-sprout.github.io/py2021/#!slides.md), [Py 2020](https://tw-csie-sprout.github.io/py2020/#!slides.md)
{"metaMigratedAt":"2023-06-16T22:35:20.695Z","metaMigratedFrom":"YAML","title":"Modules and import - 資訊之芽 2022 Python 語法班","breaks":true,"description":"Sean 韋詠祥 / 2022-04-17 14:00 / 這堂課你會學到什麼是 Module、各種 import 用法的差異、常用工具 / 為何需要 Module、如何使用 Module、自己寫 Module / 模組中的 __all__ 特殊變數、何謂 __init__.py / 常見雷點 NameError ModuleNotFoundError AttributeError ImportError / 小彩蛋 import this __future__.braces antigravity","contributors":"[{\"id\":\"8a6148ae-d280-4bfd-a5d9-250c22d4675c\",\"add\":24875,\"del\":11347}]"}