--- title: '函數(function)、模組(module)、自訂模組' disqus: kyleAlien --- 函數(function)、模組(module)、自訂模組 === ## Overview of Content 如有引用參考請詳註出處,感謝 :cat: [TOC] ## Python 函數 在 Python 中要定義函數是使用 `def` 關鍵字(跟 [**groovy**](https://hackmd.io/aybb17GVRbK-Tm49MuSWYw?view#Groovy-%E8%AA%9E%E8%A8%80%E5%9F%BA%E7%A4%8E%E3%80%81%E9%96%89%E5%8C%85) 語言相同)建立函數,格式如下 ```python= def 函數名([參數1, 參數2...]): 程式區塊 [return 回傳值1, 回傳值2...] ``` ### Python 參數、返回值特點 * Python 函數格式與其他程式語言差異不大,較為不同的點在於…(跟 Java 比較) * **返回值可以是多個**:如果有多個回傳值須使用逗號 `,` 隔開,而接收方(呼叫函數方)也需要使用相對應的變數數量接收 ```python= def say_hello_python(): return "Hello", "Python~" if __name__ == '__main__': s1, s2 = say_hello_python() print(s1 + " " + s2) ``` > ![image](https://hackmd.io/_uploads/Hy2snstjp.png) * **指定參數名**:當函數的參數數量較多,或是同類型的參數不異區分時,就可以使用指定參數名,這會加強程式的可讀性 :+1: ```python= def add(value1, value2): return value1 + value2 if __name__ == '__main__': result = add(value2=10, value1=1) print(f"specific params name: {result}") ``` > ![image](https://hackmd.io/_uploads/SJ_86iYjp.png) :::warning * 當開始使用指定參數名之後,**後續的參數都必須指定名稱**(`Positional argument after keyword argument`) > ![image](https://hackmd.io/_uploads/r1DBk3Yop.png) ::: * **參數預設值**:每個參數都可以有預設值! ```python= def use_default_params(msg, print_count=3): for i in range(print_count): print(f"({i}) {msg}") print() if __name__ == '__main__': use_default_params("Hello", print_count=5) # 使用預設參數 use_default_params("Hello") ``` > ![image](https://hackmd.io/_uploads/SJ89AoFoa.png) :::warning * 定義函數的預設值時,**必須把有預設值的參數放置到最後**,否則無法通過編譯(`non-default parameter follows default parameter`) > ![image](https://hackmd.io/_uploads/Hke212Ksa.png) ::: ### Python 函數範疇、取 global 變數 * Python 函數(或是說 Python 這門語言)的基礎規則在於「縮排」,縮排會影響到函數的變數範疇;當函數的縮排結束時,也就代表該函數的影響範疇結束,範例如下 ```python= def function_scope(): # 雖然與外部變數名稱相同,但是進入函數後,就會覆蓋外部宣告的變數,使用內部變數! var = 10 print(f"inner function: {var}") var = 555 print(f"outer function: {var}") if __name__ == '__main__': function_scope() ``` > ![image](https://hackmd.io/_uploads/BJfYZnFop.png) * 當然,如果你想取外部的全域變數 Python 也是做的到的!只要在函數內 **使用 `global` 關鍵字** 宣告變數即可,範例如下 ```python= var = 555 print(f"outer function: {var}") def use_global_keyword(): global var var = 100 print(f"inner function: {var}") ``` > ![image](https://hackmd.io/_uploads/ryaOXhtoa.png) :::danger * 在函數中使用 `global` 關鍵字,其實就是 **取得全域變數的引用,這時如果在函數內修改該變數,就會導致全域變數的值也跟著一起改變**(請注意這點!) > 這種 `global` 用法與 `PHP` 相同 :accept: ```python= var = 555 print(f"outer function: {var}") def use_global_keyword(): global var var = 100 print(f"inner function: {var}") if __name__ == '__main__': use_global_keyword() print(f"After function change global variable: {var}") ``` > ![image](https://hackmd.io/_uploads/BJeV4hKja.png) ::: ## Python 內建函數 Python 中有內建一些常用、強大的函數,這些函數並非是屬於某個類,而是頂層函數,可以直接呼叫使用 ### 數值函數 * 常見有關「數值」操作的內建函數,範例如下 * **`pow` 函數:次方操作**,如果最後一個參數有填寫,就會用來除以該參數的數值,並回傳餘數 ```python= def pow_usage(): print(f"Pow(2, 4): {pow(2, 4)}") # 2 的 4 次方 print(f"Pow(7, 3): {pow(7, 3, 3)}") # 7 的 3 次方,除以 3 取餘數 ``` > ![image](https://hackmd.io/_uploads/Bk4Ujzs0a.png) * **`divmod` 函數:同時回傳「商」、「餘數」**,可以透過中括號 `[]` 取得,商放置 `[0]`,餘數放置 `[1]` > 回傳的類型是 `tuple`(特性是不可更改) ```python= def divmod_usage(): print(f"divmod(10, 2): {divmod(7, 3)})") res = divmod(5, 2) print(f"divmod(5, 2): {type(res), res[0], res[1]}") ``` > ![image](https://hackmd.io/_uploads/ryADnMoAp.png) * **`round` 函數:四捨六入,取得進 `x` 的值** :::info * 那 5 呢?怎麽計算 5 的話要視前一位數字的「奇偶性」而定,若前一位數是「偶數」就「捨棄」,「奇數」就「進位」 ::: ```python= def round_usage(): print(f"round(3.14): {round(3.14)}") print(f"round(3.16): {round(3.6)}") print(f"round(3.5): {round(3.5)}") print(f"round(4.5): {round(4.5)}") print(f"round(3.14159): {round(3.14159, 4)}") ``` > ![image](https://hackmd.io/_uploads/HkoB6zj0T.png) * **`max`、`min` 函數**:**多個數值中取得最大值、最小值** ```python= def max_min_usage(): print(f"max([2, 1, 99, -100]): {max([2, 1, 99, -100])}") print(f"min([2, 1, 99, -100]): {min([2, 1, 99, -100])}") ``` > ![image](https://hackmd.io/_uploads/SkevqlhAT.png) * **`sum` 函數**:**計算串列中所有數值的總和** ```python= def sum_usage(): print(f"sum([0, 2, 4, 6, 8, 10]): {sum([0, 2, 4, 6, 8, 10])}") print(f"sum(range(0, 11, 2)): {sum(range(0, 11, 2))}") ``` > ![image](https://hackmd.io/_uploads/rkImox2CT.png) * **`sorted` 函數**:**將串列種所有的數值做排序**,預設是「小至大」排序(也可以透過設置 `reverse` 參數設置反向「大至小」) ```python= def sorted_usage(): print(f"sorted([2, 1, 99, -100]): {sorted([2, 1, 99, -100])}") print(f"sorted([2, 1, 99, -100], True): {sorted([2, 1, 99, -100], reverse=True)}") ``` > ![image](https://hackmd.io/_uploads/B1_z2l3Ca.png) ### 字串函數 * 常見有關「字串」操作的內建函數,範例如下 * **字符串連接(`Join`)、切割(`Split`)** ```python= def string_join_split_usage(): sting_list = ["Hello", "World", "~"] print(f"{' '.join(sting_list)}") print(f"{'ABC'.join(sting_list)}") string = "1 2 3" print(string.split()) # 預設使用「空格」最為切割 string = "1, 2, 3" print(string.split(', ')) ``` > ![image](https://hackmd.io/_uploads/SytLRg30p.png) * **字串開頭(`startswith`)、字串結尾(`endswith`)** ```python= def string_starts_ends_usage(): print("https://www.python.org".startswith("https")) print("https://www.python.org".endswith("org")) ``` > ![image](https://hackmd.io/_uploads/rkAayWn0p.png) * **字符串「排版」** * **`ljust`、`rjust`、`center` 函數**: 填充方式的排版 ```python= def just_usage(): # 保持左邊,填充右邊 print("Hello".ljust(12)) print("Hello".ljust(12, "~")) # 保持右邊,填充左邊 print("Hello".rjust(12)) print("Hello".rjust(12, "~")) # 左右填充 print("Hello".center(10, "~")) print("Hello".center(11, "~")) ``` > ![image](https://hackmd.io/_uploads/rkuifb2AT.png) * **`lstrip`、`rstrip`、`strip` 函數**: 移除空白字元或指定字元的排版 ```python= def just_usage(): # 保持左邊,填充右邊 print("Hello".ljust(12)) print("Hello".ljust(12, "~")) # 保持右邊,填充左邊 print("Hello".rjust(12)) print("Hello".rjust(12, "~")) # 左右填充 print("Hello".center(10, "~")) print("Hello".center(11, "~")) ``` > ![image](https://hackmd.io/_uploads/ry83Xbn0a.png) * **字串搜尋(`find`)、取代(`replace`)**: ```python= def find_replace_usage(): demo_string = "Hello World!" print(demo_string.find("World")) print(demo_string.find("?")) print(demo_string.replace("World", "Python")) print(demo_string) ``` :::warning * `find` 函數找字串時,並找到字串時會返回字串的 Index… 這個 Index 是從 `0` 開始計算 > 若是沒找到,則返回 `-1` * `replace` 函數並不會改變原來的字串,它會返回新的字串 ::: > ![image](https://hackmd.io/_uploads/Byb0E-30a.png) ## Python 模組 Python 開發之所以廣為人知,有一部分的原因是因為它提供了各種不同的模組,讓應用程式可以站在應用的角度不斷覆用已有的功能 :::success * **「套件, `Package`」與「模組, `Module`」概念**: * 模組(`Module`)在 Python 中是一個包含了函數和變數定義以及執行語句的檔案。一個模組可以被另一個 Python 程式引入,以便在不同的程式中重複使用相同的程式碼 > 模組的名稱通常是檔案名稱去掉 `.py` 擴展名。模組提供了一種組織程式碼的方式,**使得程式碼更加結構化、模組化和易於維護** * 套件(`Package`):套件是一個包含了多個模組的目錄,並且在該目錄下必須包含一個名為 `__init__.py` 的檔案。透過使用套件,開發者可以更好地組織自己的程式碼,並將相關的模組放在一個目錄下,從而方便引用和管理。 ::: ### 模組的使用方式:import、from、as * 使用 `import` 關鍵字就可以將模組匯入應用中,而匯入的方式也有不同種 1. **單使用「`import`」關鍵字** 這種使用引入模組的方式,在使用時要同時呼叫模組名稱、函數名 ```python= # import 模組名稱 import random random.randint(10) # 模組名.函數名 ``` 2. **使用「`from`、`import`」關鍵字** 使用 `from`、`import` 方式匯入模組後,使用模組函數就 **不用輸入模組名稱,可以直接使用函數** ```python= # from 模組名稱 import * from random import * randint(10) # 函數名 (不須模組名) ``` :::danger * 這種方式雖然簡單,但其實也不夠清晰 如果發生有多個模組都提供相同函數名,那使用時就可能導致錯誤… ::: * **函數別名 `as`**: **`import` 可以在配合 `as` 關鍵字一起使用**,幫模組另外起名稱(這個取名並不會真正改變模組明,只會在這個檔案中作為模組的「別名」) ```python= # import 模組名稱 as 別名 import random as rd rd.randint(10) ``` ### 亂數模組:隨機整數、浮點數 > 亂數模組為 `import random` * 產生「**整數亂數**」 1. **使用 `randint` 函數**:設定初始值、結束值後,隨機值就會在這範圍內隨機產生 ```python= import random def use_random_int(): for i in range(10): start = 0 end = 10 print(f"Random({i}): {random.randint(start, end)}") ``` > ![image](https://hackmd.io/_uploads/By4oREHy0.png) 2. **使用 `randrange` 函數**: 以下範例,會在起始跟結束的範圍內(0 ~ 10, 但不包含 10),每次遞增 2… 也就是說,隨機數被限定在 [0、2、4、6、8] 這些數字之中,不會有奇數 ```python= import random def use_random_range(): for i in range(10): start = 0 end = 10 print(f"Random range({i}): {random.randrange(start, end, 2)}") ``` > ![image](https://hackmd.io/_uploads/Skju1rBk0.png) * 產生「**浮點數亂數**」 1. **使用 `random` 函數**:**`0 ~ 1` 之間的浮點數亂數** ```python= import random def use_random(): for i in range(5): print(f"Random({i}): {random.random()}") ``` > ![image](https://hackmd.io/_uploads/SJP-brBk0.png) 2. **使用 `uniform` 函數**:指定浮點數亂數範圍 ```python= import random def use_uniform(): for i in range(5): start = 3.14 end = 5.566 print(f"uniform({i}): {random.uniform(start, end)}") ``` > ![image](https://hackmd.io/_uploads/HJ3XGBrkR.png) ### 亂數模組:隨機串列中的元素 > 亂數模組為 `import random` * 從串列中「**隨機取得元素**」 1. **使用 `choice` 函數**: ```python= import random def use_choice(): for i in range(5): msg = "Hello world" print(f"choice({i}): {random.choice(msg)}") for i in range(5): tmp = [1, 5, 77, 2, 4] print(f"choice({i}): {random.choice(tmp)}") ``` > ![image](https://hackmd.io/_uploads/HyONNrHJ0.png) 2. **使用 `sample` 函數**: 與 `choice` 不同的點在於,它取回的是串列類型,也就是說可以隨機取回一個串列的隨機元素… 範例如下 ```python= import random def use_sample(): for i in range(5): msg = "Hello world" print(f"choice({i}): {random.sample(msg, i + 1)}") ``` > ![image](https://hackmd.io/_uploads/HJdvrSBJA.png) ### 時間模組:取得時間 > 時間模組為 `import time` 1. **`time` 函數的使用** (time stamp) Python 的時間是以 `tick` 為單位(百萬分之一秒,也就是 10^-7^),計時是從 1970/1/1 開始,直到當下的時間共幾秒 ```python= def use_time(): print(f"Current time({time.time()})") ``` > ![image](https://hackmd.io/_uploads/rJ1qurByC.png) 2. **`localtime` 函數的使用**:返回 `struct_time` 類型 以應用端的角度來說,取得 time stamp 的價值可能不大,畢竟仍然需要解析 time stamp 的數值,才能取得年、月、日、時、分、秒… 等等資訊 透過 `localtime` 函數,就可以自動取得已經分析好的時間結構、而且也可以透過它去轉換 time stamp ```python= def use_localtime(): local_time = time.localtime() print(f"Local time({local_time})") _time = time.time() print(f"Local time convert time({time.localtime(_time)})") ``` > ![image](https://hackmd.io/_uploads/SkF5qrHyC.png) 3. **`ctime` 函數的使用** `ctime` 函數與 `localtime` 差不多,但是它返回的是「字串」的格式,其格式如下 ```shell= # ctime 格式 星期 月份 日 時:分:秒 西元年 ``` 範例如下 ```python= def use_ctime(): print(f"ctime({time.ctime()})") _time = time.time() print(f"ctime({time.ctime(_time)})") ``` > ![image](https://hackmd.io/_uploads/HJUajHS1A.png) ### 時間模組:程式暫停 > 時間模組為 `import time` * **讓程式暫停** `sleep` 函數的使用 `sleep` 函數可以將程式暫停(應用會讓出 CPU 時間),暫停的時機單位為「**秒**」 ```python= import time def use_sleep(): print(f"Start time({time.time()})") time.sleep(3) print(f"End time({time.time()})") ``` 從下圖中,我們可以看到確實程式休眠 3 秒鐘後才繼續運行 > ![image](https://hackmd.io/_uploads/Hy2MwHBJ0.png) ## 自定義模組 以下使用 [jetbrains](https://www.jetbrains.com/pycharm/) 開發的 PyCharm 2023.3.5 IDE 作為編譯器,在其之上開發 Python 模組(`module`) ### 建立模組 下圖是尚未有模組的專案,只會 pythonProject,接著要在這個專案上建立其他的模組 > ![image](https://hackmd.io/_uploads/r1asfNAkC.png) 1. **開啟 `Python Package` 視窗**: 使用快捷鍵 `Alt + Insert` 開啟 New Widget(或是透過 `File` 點擊 `New...` 也可以),並點擊 `Python Package` > ![image](https://hackmd.io/_uploads/ryuXm4R10.png) 2. **為模組命名**:`__init__.py` 檔 點擊 `Python Package` 後,就會彈出 `New Python Package` 視窗(如下),並在視窗內輸入模組名稱 > ![image](https://hackmd.io/_uploads/HJ-R7NCJR.png) 之後按下 `Enter`,IDE 就會直接幫我們建立模組,並且該模組內會內建一個 `__init__.py` 檔 > ![image](https://hackmd.io/_uploads/SJnh4NA10.png) :::success * **`__init__.py` 檔是做甚麽的**? 建立一個 `__init__.py` 檔的目的是告訴 Python 編譯器,將這個目錄當作「模組」來看代,並且管理該目錄下所有的 Py 檔案… 概念範例如下 ```shell= ├── app │ ├── __init__.py │ ├── main.py │ ├── dependencies.py │ └── routers # routers 模組,管理 item.py, users.py │ │ ├── __init__.py │ │ ├── items.py │ │ └── users.py │ └── internal # internal 模組 │ ├── __init__.py │ └── admin.py ``` > ![image](https://hackmd.io/_uploads/B14ol4y7R.png) ::: ### 使用模組 * 這裡我們簡單的在模組內建立一個頂層函數,來示範如何使用、呼叫模組,函數如下… ```python= # Hello.py def say_hello(name: str): print(f"Hello {name}") ``` > ![image](https://hackmd.io/_uploads/ryCpL4Ak0.png) * 使用 `import`、`from` 來引進模組並使用 ```python= from hello_module.Hello import say_hello if __name__ == '__main__': say_hello("Apple") ``` 運行結果如下所示 > ![image](https://hackmd.io/_uploads/Hkc5vNRJR.png) ## Appendix & FAQ :::info ::: ###### tags: `Python`