---
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)
```
> 
* **指定參數名**:當函數的參數數量較多,或是同類型的參數不異區分時,就可以使用指定參數名,這會加強程式的可讀性 :+1:
```python=
def add(value1, value2):
return value1 + value2
if __name__ == '__main__':
result = add(value2=10, value1=1)
print(f"specific params name: {result}")
```
> 
:::warning
* 當開始使用指定參數名之後,**後續的參數都必須指定名稱**(`Positional argument after keyword argument`)
> 
:::
* **參數預設值**:每個參數都可以有預設值!
```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")
```
> 
:::warning
* 定義函數的預設值時,**必須把有預設值的參數放置到最後**,否則無法通過編譯(`non-default parameter follows default parameter`)
> 
:::
### 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()
```
> 
* 當然,如果你想取外部的全域變數 Python 也是做的到的!只要在函數內 **使用 `global` 關鍵字** 宣告變數即可,範例如下
```python=
var = 555
print(f"outer function: {var}")
def use_global_keyword():
global var
var = 100
print(f"inner function: {var}")
```
> 
:::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}")
```
> 
:::
## 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 取餘數
```
> 
* **`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]}")
```
> 
* **`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)}")
```
> 
* **`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])}")
```
> 
* **`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))}")
```
> 
* **`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)}")
```
> 
### 字串函數
* 常見有關「字串」操作的內建函數,範例如下
* **字符串連接(`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(', '))
```
> 
* **字串開頭(`startswith`)、字串結尾(`endswith`)**
```python=
def string_starts_ends_usage():
print("https://www.python.org".startswith("https"))
print("https://www.python.org".endswith("org"))
```
> 
* **字符串「排版」**
* **`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, "~"))
```
> 
* **`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, "~"))
```
> 
* **字串搜尋(`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` 函數並不會改變原來的字串,它會返回新的字串
:::
> 
## 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)}")
```
> 
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)}")
```
> 
* 產生「**浮點數亂數**」
1. **使用 `random` 函數**:**`0 ~ 1` 之間的浮點數亂數**
```python=
import random
def use_random():
for i in range(5):
print(f"Random({i}): {random.random()}")
```
> 
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)}")
```
> 
### 亂數模組:隨機串列中的元素
> 亂數模組為 `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)}")
```
> 
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)}")
```
> 
### 時間模組:取得時間
> 時間模組為 `import time`
1. **`time` 函數的使用** (time stamp)
Python 的時間是以 `tick` 為單位(百萬分之一秒,也就是 10^-7^),計時是從 1970/1/1 開始,直到當下的時間共幾秒
```python=
def use_time():
print(f"Current time({time.time()})")
```
> 
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)})")
```
> 
3. **`ctime` 函數的使用**
`ctime` 函數與 `localtime` 差不多,但是它返回的是「字串」的格式,其格式如下
```shell=
# ctime 格式
星期 月份 日 時:分:秒 西元年
```
範例如下
```python=
def use_ctime():
print(f"ctime({time.ctime()})")
_time = time.time()
print(f"ctime({time.ctime(_time)})")
```
> 
### 時間模組:程式暫停
> 時間模組為 `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 秒鐘後才繼續運行
> 
## 自定義模組
以下使用 [jetbrains](https://www.jetbrains.com/pycharm/) 開發的 PyCharm 2023.3.5 IDE 作為編譯器,在其之上開發 Python 模組(`module`)
### 建立模組
下圖是尚未有模組的專案,只會 pythonProject,接著要在這個專案上建立其他的模組
> 
1. **開啟 `Python Package` 視窗**:
使用快捷鍵 `Alt + Insert` 開啟 New Widget(或是透過 `File` 點擊 `New...` 也可以),並點擊 `Python Package`
> 
2. **為模組命名**:`__init__.py` 檔
點擊 `Python Package` 後,就會彈出 `New Python Package` 視窗(如下),並在視窗內輸入模組名稱
> 
之後按下 `Enter`,IDE 就會直接幫我們建立模組,並且該模組內會內建一個 `__init__.py` 檔
> 
:::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
```
> 
:::
### 使用模組
* 這裡我們簡單的在模組內建立一個頂層函數,來示範如何使用、呼叫模組,函數如下…
```python=
# Hello.py
def say_hello(name: str):
print(f"Hello {name}")
```
> 
* 使用 `import`、`from` 來引進模組並使用
```python=
from hello_module.Hello import say_hello
if __name__ == '__main__':
say_hello("Apple")
```
運行結果如下所示
> 
## Appendix & FAQ
:::info
:::
###### tags: `Python`