<!-- .slide: data-background="https://hackmd.io/_uploads/ryOcSesP2.png" -->
# Python Module
Sean 韋詠祥
Note:
日期:2023-07-21(五)Day 2
課程時間:20:05 - 21:30
18:30 Function
20:00 Module
21:30 End
20:05 開場
20:10 練習 1:三角函數
20:15 自己寫 Module
20:20 練習 2:實作階乘函式
20:30 \_\_all\_\_
20:35 常見 Module(一)
20:40 練習 3:亂數產生器
20:50 常見 Module(二)
20:55 練習 4:RegEx
21:05 專案開發
21:10 常見雷點
21:20 Coding Style
21:30 彩蛋
21:30 End
----
## 這堂課你會學到...
- 什麼是 Module(模組 / 套件)
- 各種 import 用法的差異
- 站在巨人的肩膀上:常用工具
Note:
前一堂:上午 Hugo 教基礎容器、jayinnn 講 Function
後一堂:Day 3 Vincent 講 Packages
簡報連結記得放 Camp 網站
----
## 為何需要 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)) # 5! = 120
```
----
## 練習 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:實作階乘函式
在 `sitcon.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: sitcon.py
__all__ = ['fact']
def fact(n):
pass
```
在 `from sitcon import *` 的時候
只有 `__all__` 裡面寫到的 `fact` 會被 import 進來
Note. 但可以被 `import sitcon` 或 `from sitcon import xxx` 取用
<!-- .element: class="r-fit-text" -->
----
## 模組中的 \_\_all__ 特殊變數
```python=0
# File: sitcon.py
__all__ = ['fact']
def is_negative(n):
if n < 0:
return True
return False
```
```python=8
def fact(n):
if is_negative(n):
return -1 # Error!
if n > 1:
return n * fact(n-1)
return 1
```
----
## 其他特殊變數
在模組中,還有可以加上....
```python
__author__ = 'Sean Wei'
__version__ = '1.19.2'
__license__ = 'MIT'
__status__ = 'Production'
__email__ = 'me@sean.taipei'
__copyright__ = 'Copyright 2023, SITCON Staffs'
```
---
# 常見的 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"
Ref: 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:
Ref: 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
----
## sys 系統參數
- 取得系統執行環境資訊
```python
import sys
# 執行的參數列表
print('Arguments:', sys.argv)
# 套件路徑列表
print('Python Path:', sys.path)
# 版本資訊:3.11.4 (main, Jun 20 2023) [Clang 14.0.3]
print('Version:', sys.version)
```
Note:
Ref: https://docs.python.org/3/library/sys.html
----
## 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))
```
- 用來模擬及建模用,但不適合拿來產生密碼
Note:
Ref: https://docs.python.org/3/library/random.html
只要取得幾百個隨機數,就能精準預測後面的其他隨機輸出
https://github.com/tna0y/Python-random-module-cracker
----
## Random 隨機
```python
import random
# 產生內容為 [0, 1, 2, ... 48, 49] 的 list
A = [i for i in range(50)]
print('The list A:', A)
```
```python
# 在 list 中隨機選一個值
print('Choice one:', random.choice(A))
```
```python
# 把 A 的順序打亂
random.shuffle(A)
print('After shuffle:', A) # 現在 A 被原地修改為新的順序了
```
----
## 練習 3:亂數產生器
#### 輸入格式
第 1 行輸入數字 N 代表長度(N ≥ 5)
第 2 行輸入 0 或 1,代表是否要包含大小寫字母
第 3 行輸入 0 或 1,代表是否要包含數字和標點符號
保證第 2 3 行不會同時為 0
#### 輸出要求
使用前面提到的 `strings` 及 `random` 函式庫
每次產生 5 個長度為 N 且符合要求的隨機字串
(就算輸入要求有數字,輸出也可以沒出現數字,只要正常隨機就好)
<!-- .element: class="r-fit-text" -->
---
## 常見 Module(二)
----
## OS 作業系統介面
- 常見的檔案操作
```python
import os
# 取得目前目錄路徑
print('Current working directory:', os.getcwd())
# 列出目錄中檔案,以根目錄為例
print('Files:', os.listdir('/'))
```
```python
# 建立資料夾
os.mkdir('tmp-camp2023-data')
# 移除檔案
os.remove('trash-file')
```
Note:
Ref: https://docs.python.org/3/library/os.html
----
## Time 時間
```python
import time
# 目前秒數:1689943200
print('Time 1:', time.time())
# 中間暫停 1.5 秒
time.sleep(1.5)
# 會比上面的秒數多 1.5 秒
print('Time 2:', time.time())
```
Note:
Ref: https://docs.python.org/3/library/time.html
----
## Datetime 日期與時間
```python
from datetime import datetime
# 結構化的目前時間:datetime(2023, 7, 21, 20, 55, 20, 123456)
t = datetime.now()
print('Datetime:', repr(t))
# 將時間格式化成指定格式:Sun, 21 Jul 2023
print('Format:', t.strftime('%a, %d %b %Y'))
# 將時間格式化成指定格式:2023-07-21 20:55:20.123456
print('Format:', t.strftime('%Y-%m-%d %H:%M:%S.%f'))
```
Note:
Ref: https://docs.python.org/3/library/datetime.html
datetime 底下有 date, time, datetime, timedelta, timezone 等
晚點有空再提 \_\_repr__()
----
## 慣用的套件縮寫
- 可以先有個概念就好,不需要記住
```python
# 矩陣運算
import numpy as np
```
```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
```
----
## re 正規表達式
- RegEx - Regular Expression
```python
import re
# 在字串中尋找
print(re.search('Camp \d+', 'SITCON Camp 2023 NCTU'))
# 需要從字串開頭就符合
print(re.match('Camp [0-9]*', 'Camp 2023 NCTU'))
```
```python
# 用 ^ $ 代表開頭結尾
print(re.search('^Camp \d+$', 'SITCON Camp 2023 NCTU'))
```
```python
# 尋找所有符合的結果
print(re.findall('[A-Za-z]*is', 'His name is Chris'))
```
Note:
https://docs.python.org/3/library/re.html
----
## 練習 4:正規表達式
在以下 `???` 部分填入正確函式
並撰寫 RegEx 語句,分別從三個 `txt` 提取答案
```python
import re
txt1 = 'Hello FLAG{Python} basic'
txt2 = 'xyzFLAG{an$w3R}123'
txt3 = 'owoFAAAGG{9o0D_j08!}}}()'
ans = re.???(???, txt?)
print(ans)
# Ans: FLAG{Python}, FLAG{an$w3R}, FAAAGG{9o0D_j08!}
```
---
# Python 專案開發
拆成幾十個模組怎麼辦?
總不能全部丟在同個目錄吧
----
## 目錄架構
一個專案可能長得像這樣
```text
telegram/ 專案頂層目錄
├── __init__.py ├── 初始化 telegram 模組的腳本
├── 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: telegram/commands/core.py
import request
from telegram.ui import modal # telegram/ui/modal.py
```
---
# 常見雷點
- 找不到 or 未安裝 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 telebot
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'telebot'
```
### 解決方法
使用 pip 安裝相應套件
```shell
$ pip install <缺少的套件>
```
----
## pip not found
- 找不到 pip 指令
```text
sitconcamp@SITCON-Camp$ pip
pip: command not found
```
### 解決方法
到 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
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": from foo import func1
File ".../foo.py": from bar import func2
File ".../bar.py": 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 SITCON Camp 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 telebot
import requests as req
from telebot.callback_data import CallbackData
import camputils
```
----
## 特殊變數擺放位置
- 置於所有 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__ = 'SITCON 2023 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
---
## 跟 import 有關的 Python 小彩蛋
```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.
# Flat is better than nested.
# Sparse is better than dense.
# Readability counts.
# [...]
```
----
## 跟 import 有關的 Python 小彩蛋
```python
import __hello__
# Hello world!
```
```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 2022](https://sprout.tw/py2022/slides), [Py 2021](https://sprout.tw/py2021/#!slides.md)
----
# Thanks
投影片連結:https://hackmd.io/@Sean64/py-module-sitcon2023
<!-- .element: class="r-fit-text" -->
<br>
[](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-sitcon2023/edit)。
{"title":"Python Module - SITCON Camp 2023 主線課程","breaks":true,"description":"Sean 韋詠祥 / 這堂課你會學到什麼是 Module、各種 import 用法的差異、常用工具 / 為何需要 Module、如何使用 Module、自己寫 Module / 模組中的 __all__ 特殊變數、何謂 __init__.py / 常見雷點 NameError ModuleNotFoundError AttributeError ImportError / 小彩蛋 import this __future__.braces antigravity / 2023-07-21 20:00","metaMigratedAt":"2023-06-17T10:25:18.246Z","metaMigratedFrom":"YAML","slideOptions":"{\"backgroundTransition\":\"none\",\"parallaxBackgroundSize\":\"cover\",\"parallaxBackgroundHorizontal\":0,\"parallaxBackgroundVertical\":0,\"parallaxBackgroundImage\":\"https://hackmd.io/_uploads/SkaithcP2.png\"}","contributors":"[{\"id\":\"8a6148ae-d280-4bfd-a5d9-250c22d4675c\",\"add\":19642,\"del\":4918}]"}