# 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 <!-- .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/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)
{"type":"slide","tags":"presentation","title":"Modules and import - 資訊之芽 2022 Python 語法班","description":"Sean 韋詠祥 / 2022-04-17 14:00 / 這堂課你會學到什麼是 Module、各種 import 用法的差異、常用工具 / 為何需要 Module、如何使用 Module、自己寫 Module / 模組中的 __all__ 特殊變數、何謂 __init__.py / 常見雷點 NameError ModuleNotFoundError AttributeError ImportError / 小彩蛋 import this __future__.braces antigravity"}
    889 views