# 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, ...): &nbsp;&nbsp;&nbsp;&nbsp;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、調用者等有用,知道你的程序發生了什麼,並相應地繼續。