# 資料結構
###### tags: `python`
## 函式
已經寫程式也有一段時間了,可能會發現有些程式碼可以重複利用,因此常常會「複製」、「貼上」,常常有些算式只是變數的不同,但算式是一樣的,這時候我們就可以封裝成函式來重複使用,好處有:
1. 減少程式碼,讓程式更容易維護。
2. 增加程式碼的閱讀性。
3. 更有效率的的程式開發。
例如:
```
a = 1
b = 2
c = 3
d = 4
if a > b:
print(a, '比', b, '大')
else:
print(b, '比', a, '大')
if c > d:
print(c, '比', d, '大')
else:
print(d, '比', c, '大')
```
改用函式會變成:
```
a = 1
b = 2
c = 3
d = 4
def compare(n1, n2):
if n1 > n2:
print(n1, '比', n2, '大')
else:
print(n2, '比', n1, '大')
compare(a, b)
compare(c, d)
```
九九乘法表:
```
# 非函式版
for j in range(2, 10):
for i in range(1, 10):
print(j, ' x', i , ' = ', j * i)
# 函式版
def ninenine(x):
for i in range(1, 10):
print(x, ' x', i , ' = ', x * i)
for i in range(2, 10):
ninenine(i)
```
函式是一種流程上的抽象,封裝了實作細節,例如上面範例,以後如果要進行更多比較,只要呼叫`compre()`函式就可以了,而不需要管函式內是怎麼實作的。
### 定義函式
使用關鍵字`def`來定義函式,格式如下:
```
def 函式名稱(參數1, ..., 參數n):
程式碼區塊
...
```
注意:
1. 函式內程式碼一定要縮排。
2. 函式名稱第一個字只能是英文或底線。
3. 保留字不能當成函式名稱。
4. 函式名稱內的大小寫是不一樣的。
避免使用的命名:
1. 除了計數器及迭代器內之外,不要用單一字母為變數命名,請使用有意義的名稱。
2. 變數名稱前後不要加上兩個雙底線,雙底線的變數有特殊含義。
3. package 及 module 的名稱中不要包含破折號 "-"
### 參數與引數
在呼叫函式的時候,可以從外部傳入資料讓函式處理,例如:
```python
def say_hello(text):
print('Hello', text)
```
##### 參數預設值
在參數中可以設定預設值,如果沒有傳入該參數,就會自動以預設值來帶入,例如:
```
def call(name, num = '錯誤,沒有輸入號碼'):
print('撥電話給: ', name, '號碼:', num)
call('Aaron', '0968123456')
call('Amber')
```
會輸出:
```
撥電話給: Aaron 號碼: 0968123456
撥電話給: Amber 號碼: 錯誤,沒有輸入號碼
```
> 有參數預設值的參數不可以在沒有參數預設值的參數前面。
##### 關鍵字引數
在傳入參數時,可以使用關鍵字引數,就可以不需要照參數的順序傳入,例如:
```
def call(name, num = '錯誤,沒有輸入號碼'):
print('撥電話給: ', name, '號碼:', num)
call(num = '0968123456', name = 'Aaron')
```
### 回傳值
當函式執行完後,如果會產生結果,可以透過`return`關鍵字來將結果值回傳給呼叫者,例如:
```
def plus(a, b):
c = a + b
return c
result = plus(3, 4)
print('總合: ', result)
```
會輸出:
```
總合: 7
```
> 函式內如果遇到`return`陳述句,就會馬上結束函式執行並跳離函式。
### 一級函式
##### 函式也是物件
在Python裡函式也是物件,也就是說可以被放到變數裡傳遞,例如:
```
def call(name, num = '錯誤,沒有輸入號碼'):
print('撥電話給: ', name, '號碼:', num)
make_call = call
make_call('Aaron', '0999000123')
```
會輸出:
```
撥電話給: Aaron 號碼: 0999000123
```
Python的函式因為可以像變數ㄧ樣被傳遞,因此稱為==一級函式==,例如:
```
def swap(a, b):
return b, a
print(swap(1, 2))
s = swap
print(s(5,7))
```
會輸出:
```
(2, 1)
(7, 5)
```
swap函式播指派給s變數,因此s變數可以當成函式一樣被呼叫,其功用則和直接呼叫swap()函式一樣。
Python函式也可以被當成參數一樣傳遞:
```
def show():
print("我是show函式")
def hide():
print("我是hide函式")
def test():
print("我是test函式")
def a(a1, a2, a3):
a1()
a2()
a3()
a(show, hide, test)
print(type(show))
```
會輸出:
```
我是show函式
我是hide函式
我是test函式
<class 'function'>
```
透過type()函式可以發現show函式為一個`function`型態。
#### callable()函式
使用callable()函式可以用來測試一個變數是否可以被呼叫,例如:
```
a = 0
def f():
pass
print(callable(a))
print(callable(f))
```
會輸出:
```
False
True
```
### lambda
Lambda也是函式,但是沒有函式名稱,也就是匿名函式,它只有一行運算式,可以用簡潔的語法完成原本需要較多行才能完成的程式碼,許多現代程式語言(例如:Java和C#等等)也都支援Lambda函式來處理小型的運算。
##### 語法
```
lambda 參數清單: 運算式
```
例如:
```
showMe = lambda name: print('This is ' + name)
showMe('Aaron')
```
會輸出:
```
This is Aaron
```
在很多情況下,函式內可能只是很簡單判斷式,也可以用更簡潔的語法`lambda`來表示,例如:
```
def max(n1, n2):
if n1 > n2:
return n1
return n2
```
使用`lambda`可改寫為:
```
max = lambda n1, n2: n1 if n1 > n2 else n2
```
就可以透過像一般函式呼叫的方式來做這樣的呼叫:
```
print(max(3, 4))
```
甚至可以直接這樣呼叫:
```
(lambda n1, n2: n1 if n1 > n2 else n2)(3, 4)
```
> **補充**
>
> Lambda函式支援IIFE(Immediately invoked function expression)語法,意思就是說定義完後就可以直接被呼叫;如果沒有直接被呼叫,則是回傳一個function物件。
### yield
##### 說明
yield函式是一個產生器 (generator),用於迭代中。
使用方式則是類似於 return,不一樣的地方是,函式裡可以有多個yield,每次遇到yield時,函式就會返回,並回傳yield後面的值,然後記下目前的位置,等待下一次呼叫時,會從該yield位置的下一行程式碼開始執行。
其好處就是可以不用一次回傳全部得值,而是一個一個回傳,可以節省記憶體的使用。
#### `next()`和`send()`
直接呼叫yield函式會拿到一個`generator`物件,接著透過next()或send()函式來呼叫該`generator`物件。
> **補充**
>
> `for`迴圈會以捕捉`StopIteration`來做為結束,所以如果`yield`函式`return None`,就會發生`StopIteration`例外;在呼叫`next()`函式的時候如果提供預設值的參數,就不會引發`StopIteration`例外,例如:
>
> ```
> next(yieldFunc, 'No more data')
> ```
### 變數作用域
**變數可以在三個不同的地方分配**
1. 如果一個變數在def內賦值,它被定位在這個函式之內
2. 如果一個變數在一個巢狀的def中賦值,對於巢狀函式來說,他是非本地的
3. 如果在def之外賦值,他就是整個檔案全域性的
作用域法則
1. 內嵌模組是全域性作用域
2. 全域性作用域的作用範圍僅限於單個檔案
3. 每次對函式的呼叫都建立了一個新的本地作用域
4. 函式中賦值的變數名除非宣告為全域性變數或非本地變數,否則均為本地變數
5. 所有其他的變數名都可以歸納為本地,全域性或者內建的
變數名解析
1. 變數名引用分為三個作用域進行查詢,首先是本地,然後是上層函式的本地作用域,之後全域性,最後是內建
2. 預設情況下,變數名賦值會建立或者改變本地變數
3. 全域性宣告和非本地宣告將賦值的變數名對映到模組檔案內部的作用域
### 其它
##### `print()`函示
為了可以輸出結果到終端機視窗,可以使用`print()`函示,如果有多個結果同時輸出,可以使用逗號`,`分隔。
```
>> print(234)
>> print('Hello!', 456) # 同時有多個結果要一次輸出
234
Hello! 456
```
如果想要輸出多的字串時,每個字串用逗號「`,`」做分隔,可以加上`sep=', '`,例如:
```
>> print('Hello!', '456', sep=', ')
Hello!, 456
```
`print()`函式預設輸出後一定會做換行,如果不想換行,可以指定`end=''`,例如:
```
>> print("Hello, ", end='')
>> print("World!")
Hello, World!
```
##### `type()`函式
無論是十進位、二進位、八進位、十六進位整數,都是int類別,如果想知道某種資料的型態,可以使用`type()`函式來取得,並搭配`print()函式`來顯示到終端機視窗上。
例如:
```
>> print(type(10))
>> print(type(0b1001))
>> print(type(0o123))
>> print(type(0x9C))
<class 'int'>
<class 'int'>
<class 'int'>
<class 'int'>
```
##### sum()函式
計算指定序列內數字的加總,例如:
```
a = sum([1, 2, 3])
print(a)
```
輸出為:
```
6
```