# Python 常見問題及函式
## 找出問題需求
在開始動手寫程式之前,需要釐清以下項目並回答。
1. 程式要解決的問題具體的內容?
2. 處理的對象有哪些?
3. 要處理的資料是什麼形式?
4. 這些資料放哪裡?
5. 要如何輸入到電腦中?
6. 要做什麼樣的處理?
7. 處理完畢之後要以何種方式呈現?
8. 處理過的資料需要儲存嗎?
9. 要儲存在哪裡?
### 範例
我們要整理電腦某個資料夾中的所有圖形檔
* 待解決的問題: 對某個指定的資料夾中所有圖形檔重新編碼
* 程式名稱: resort.py
* 對象: 某一指定的資料夾內所有的圖形檔(.png、.gif、.jpg)
* 處理: 把所有圖形檔的檔名全部依照編號重新命名,從001開始編號,不同的圖形格式不另外編號
* 執行結果: 所有的圖形檔案都在重新編號之後,存放在原有圖形檔所在資料夾下output資料夾
* 注意事項(防錯或防呆): 檢查資料夾是否存在
**glob**可以在指定特定的資料夾之後,傳回該資料夾中所有指定的檔案型態的檔名列表,並以List的方式傳回。
```python=
import glob
gif_files = glob.glob("*.gif")
jpg_files = glob.glob("*.jpg")
png_files = glob.glob("*.png")
image_files = gif_files + jpg_files + png_files
```
設定以下變數及型態
* 指定的輸入資料夾: source_dir(字串)
* 輸出用的資料夾(放在source_dir之下): target_dir(字串,預設值是output)
* 存放所有圖形檔名的變數: image_files(串列)
### 設計演算法與繪製流程圖
## Python常見問題和小知識
### Tuple
因為Tuple的內容不能改,所以內部結構簡單,執行的效能會比較好。而且,一些不想要被修改的資料放在Tuple中就會比較放心。
### 檢視兩個變數是否為同一個記憶體位址
== 是用於檢測其值是否相等
is 是用於檢測兩者是否為同一個物件(使用同一個記憶體位址)
```python=
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True
a is b # False
b = a # a指定給b
a == b # True
a is b # True
b = a.copy() # 複製一份
a == b # True
a is b # False
```
**List、Set、Dict用 = 是指定記憶體位址**,但是基本的型態如整數、浮點數以及字串則不會有此種情形發生。
```python=
a = "str 1"
b = a
a == b # True
a is b # True
b = b.upper() # 此時a is b為False
a # "str 1"
b # "STR 1"
a = a.upper()
a is b # False
# 若為int,則變回原來相同的數值,就會變為True
a = 5
b = a
a == b # True
a is b # True
b = 3
a is b # False
a = 3
a is b # True
```
:::warning
#### a += 1 和 a = a + 1的差異
- a += 1 原本的a位址的值會改變
- a = a + 1 原本的a位址的值不會改變。其視為不同的a,所以會將相加後的值存入新的位址。
```python=
a = [1, 2]
print(hex(id(a))) # 0x20863e79a08
b = a
print(hex(id(b))) # 0x20863e79a08
a = a + [1]
print(hex(id(a))) # 0x20863e79ac8
---
a = [1, 2, 1]
b = [1, 2]
```
:::
### list去重覆且保持原順序不變
```python=
l1 = ['b','c','d','b','c','a','a']
l2 = list(set(l1))
# l2 >>> ['a', 'b', 'c', 'd']
l2.sort(key=l1.index) # 重新排序
# l2 >>> ['b', 'c', 'd', 'a']
```
## 自訂函數
建議定義函數內容之前註解說明此函數的用法,包含參數個數、型態、目的及傳回值內容等等。
### 不限制參數個數
在定義函數的時候,可以讓函數接受沒有預先設定的參數個數,定義方法是在參數的前面加上"*"。
```python=
# 函數proc中設定一個叫做args的參數,在前面加上"*",此時呼叫此函數時,Python會以tuple的方式來接收所有呼叫的引數,也就是當作tuple型態的變數處理就對了!
def proc(*args):
    for arg in args:
        print("arg:", arg)
        
proc(1, 2, 3)
proc(1, 2)
proc("a", "b")
---output---
arg: 1
arg: 2
arg: 3
===
arg: 1
arg: 2
===
arg: a
arg: b
===
```
### 區域變數 vs 全域變數
在函數中定義的變數稱區域變數,它的效用僅於此函數被呼叫時的範圍中,在函數結束執行之後就消失了。而如果在函數中才定義的變數名稱和函數外的變數名稱相同的話,使用上會以區域變數為優先。
```python=
def add2number(a, b):
    # c是區域變數;d是全域變數
    global d # 使用global告知要使用外面的d來儲放計算的結果
    c = a+b
    d = a+b
    print("在函數中,(c={}, d={})".format(c, d))
    
    return c
    
c = 10
d = 99
print("呼叫函數前,(c={}, d={})".format(c, d))
print("{}+{}={}".format(2, 2, add2number(2, 2)))
print("呼叫函數後,(c={}, d={})".format(c, d))
---output---
呼叫函數前,(c=10, d=99)
在函數中,(c=4, d=4)
2+2=4
呼叫函數後,(c=10, d=4)
```
### lambda 參數1, 參數2, ... : 敘述內容
一行敘述用完即丟的自訂函數做法。
lambda 參數1, 參數2, ... : 敘述內容
(等同於)
def fun(參數1, 參數2, ...):
    return 敘述內容
這種定義方式,可以和map這一類的函數一起使用
```python=
x = range(1, 10) # 1~9
# 函數lambda i: i**3
y = map(lambda i: i**3, x) # 把x丟入函數中的第一個參數
for i, value in enumerate(y):
    print("{}^3 = {}".format(i+1, value))
    
---output---
1^3 = 1
2^3 = 8
3^3 = 27
4^3 = 64
5^3 = 125
6^3 = 216
7^3 = 343
8^3 = 512
9^3 = 729
```
### map(function, iterable, ...)
map()會根據提供的函數對指定序列做映射。
第一個參數 function 以參數序列中的每一個元素調用 function 函數,返回包含每次 function 函數返回值的新列表。
```python=
def square(x) :  
    return x ** 2
map(square, [1,2,3,4,5])
# [1, 4, 9, 16, 25]
map(lambda x: x ** 2, [1, 2, 3, 4, 5])
# [1, 4, 9, 16, 25]
 
# 提供了兩個列表,對相同位置的列表數據進行相加
map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
# [3, 7, 11, 15, 19]
```
### filter(function, iterable)
filter() 函數用於過濾序列,過濾掉不符合條件的元素,返回由符合條件元素組成的新列表。
該接收兩個參數,第一個為函數,第二個為序列,序列的每個元素作為參數傳遞給函數進行判,然後返回 True 或 False,最後將返回 True 的元素放到新列表中。
```python=
def is_odd(n):
    return n % 2 == 1
 
newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(newlist)) # Pyhton2.7 返回列表,Python3.x 返回迭代器对象
# [1, 3, 5, 7, 9]
```
### enumerate(sequence, [start=0])
enumerate() 函數用於將一個可遍歷的數據對象(如列表、元組或字符串)組合為一個索引序列(index, value),同時列出數據和數據下標,一般用在 for 循環當中。
* sequence: 一個序列、迭代器或其他支持迭代對象。
* start: 下標起始位置。
在使用for迴圈的時候,如果需要在迴圈中使用目前的索引值,以了解目前執行中運算是處於第幾次迴圈,使用enumerate()函數便可以達成。
```python=
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons))
# [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
list(enumerate(seasons, start=1)) # 下標從1開始
# [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
chars = ['A', 'B', 'C']
for i, char in enumerate(chars):
    print("No.{}: {}".format(i, char))
    
---output---
No.0: A
No.1: B
No.2: C
```
:::info
要做成模組供日後使用,和自訂函數方法類似,只不過是要放在另外一個程式中,同時為了方便管理,避免主程式和模組程式產生混淆,一般都會為自訂模組單獨建立一個資料夾,並在該資料夾中除了自訂模組的程式之外,再加上一個__init__.py這個檔案,此檔案可以放一些初始化的變數,或是不放任何東西也可以。
* from指的是資料夾名稱,import則是實際的檔案名稱
:::
## 例外處理
如果在except後面沒有指定任何參數的話,則是捕捉所有的例外。所以可透過以下方事先知道有哪些type,再去細分except。
```python=
import os, sys
try:
    os.remove('hello.txt')
except Exception as e:
    print(e)
    
    e_type, e_value, e_tb = sys.exc_info()
    print("種類: {}\n訊息: {}\n資訊: {}".format(e_type, e_value, e_tb))
---output---
[WinError 2] 系統找不到指定的檔案。: 'hello.txt'
種類: <class 'FileNotFoundError'>
訊息: [WinError 2] 系統找不到指定的檔案。: 'hello.txt'
資訊: <traceback object at 0x00000239934897C8>
```
### eval和ast.literal_eval
兩者皆能做到型態轉譯的動作,可以將字串型的list,tuple,dict轉變成原有的型別。
:::danger
eval還可以做計算器使用,甚至可以對能解析的字串都做處理,所以是巨大的安全隱患!比如說,使用者惡意輸入下面的字串
open(r'D://filename.txt', 'r').read()
__import__('os').system('dir')
__import__('os').system('rm -rf /etc/*')
那麼eval就會不管三七二十一,顯示你電腦目錄結構,讀取檔案,刪除檔案…..如果是格盤等更嚴重的操作,也會照做不誤!!!
:::
:::success
安全處理方式ast.literal_eval。literal_eval()函式會判斷需要計算的內容是不是合法的python型別,如果是則進行運算,否則就不進行運算。
:::
```python=
import sys, ast
if len(sys.argv) < 2:
    print("使用方法...")
    exit(1)
    
scores = dict()
with open(sys.argv[1], 'r') as fp:
    filedata = fp.read()
    scores = ast.literal_eval(filedata)
print(type(scores)) # <class 'dict'>
print(scores) # {1: 87, 2: 67}
```
exit(0):無錯誤退出
exit(1):有錯誤退出
退出代碼是告訴直譯器或作業系統。這對其他程序、shell、調用者等有用,知道你的程序發生了什麼,並相應地繼續。