(1)if __name__ == '__main__' (2)列表解析式 (3)裝飾器 ...
最近想整一整數據分析,在看一本關於數據分析的書中提到了(1)if __name__ == '__main__' (2)列表解析式 (3)裝飾器。
先簡單描述一下前兩點,再詳細解說Python初級的函數裝飾器。
進入正題:
一、if __name__ == '__main__'
- 首先,__name__是一個程式名變數,而這個變數的值是根據程式的運行方式決定的。如果程式是被當作主程式運行的,那__name__將會被賦值為__main__;當程式是作為模塊被其他文件調用的,那它會自動被賦值為模塊所在(程式)的文件名。
- 一般這句代碼以這種形式出現:
1 def printHello(): 2 print("Hello World!") 3 print(__name__) 4 if __name__ == '__main__': 5 printHello()
其輸出如下:
Hello World! __main__
因為此時這個文件是以主程式的方式運行的,故它的__name__為__main__。
- 這個 if 判斷語句存在的意義在於當程式被作為模塊引入其他文件時,一句import會使得這個模塊自動運行一次。
- 故當 printHello 函數被作為模塊調入其他程式文件時,即:
1 from name_main import printHello 2 printHello()
我們只會得到 printHello 運行一次的結果。這一次結果是 import 語句的效果,真正的 printHello 函數被 if 語句攔住了(因為它的__name__變成了自己的文件名而非__main__)。
二、列表解析式
- 列表解析式其實就是一種根據已有列表,高效創建新列表的方式。
- 通常形式如:a = [ i for i in list1 if i%2==0 ]
- 學會使用,可以簡潔化代碼。
三、裝飾器
- 裝飾器器如其名,起到了“裝飾”的作用。體現在代碼中就是給原程式添加“裝飾”,添加新的東西。裝飾器的本質是函數。
- 裝飾器一般用於裝飾函數和類。
- 有人問既然裝飾器起到的是裝飾的作用,那為什麼不在函數中就添加這些“裝飾”?當然可以,這確實可行。但設想我們需要每個函數列印自己的運行時間,那我們就需要在每一個函數里記錄開始與結束時間,再相減得到時間差(即運行時間)。這會使得函數非常的“臃腫”,而且如果有成百上千個函數都需要這個功能,那一個一個函數加代碼的措施就顯得十分“無意義”。
- 但通過裝飾器:我們簡單地定義一個“裝飾”函數去實現需要的功能,然後在執行函數的前面加上一句“@裝飾函數”,就實現了對函數們的裝飾和升級。
- 舉一個例子,我們現在有兩個函數分別實現列印 hello 和 goodbye,我們需要每次列印時同時把這個函數名列印出來。我們就可以用裝飾器來完成,上代碼。
#直接裝飾@ def printname(func): #裝飾器:實質是一個函數,參數是需要裝飾的函數名(非函數調用) def wrapper(*args, **kwargs): #可變參數*args和關鍵字參數**kwargs print(f"[DEBUG]: enter {func.__name__}()") return func(*args, **kwargs) return wrapper #裝飾器函數返回的是裝飾完的函數 @printname def say_hello(): print("hello!") @printname def say_goodbye(): print("goodbye!") if __name__ == '__main__': say_hello() say_goodbye()
程式運行結果為:
[DEBUG]: enter say_hello() hello! [DEBUG]: enter say_goodbye() goodbye!
成功實現每次列印hello時列印出函數名稱say_hello()。
- 接下來是對裝飾器的深層探討。裝飾器的本質是函數,那我們如果不用@的裝飾方式,看看怎麼樣實現同樣的功能。
#間接裝飾 def printname(func): #裝飾器:實質是一個函數,參數是需要裝飾的函數名(非函數調用) def wrapper(*args, **kwargs): #可變參數*args和關鍵字參數**kwargs print(f"[DEBUG]: enter {func.__name__}()") return func(*args, **kwargs) return wrapper #裝飾器函數返回的是裝飾完的函數 def say_hello(): print("hello!") #return 0 測試line:23 def say_goodbye(): print("goodbye!") if __name__ == '__main__': decorator = printname(say_hello) ''' 不能使用decoratot = printname(say_hello()); 可能是因為調用函數的名稱是用func.__name__,而非func().__name__ 用了func().name會報錯'NoneType'對象沒有'__name__'屬性。 因為say_hello()函數沒有return,故執行結果為NoneType無。 ''' decorator() ''' 接上綠色註釋: >>> type(say_hello()) hello! <class 'NoneType'> >>> type(say_hello) <class 'function'> ''' #func,函數不帶括弧時,調用的是這個函數本身。是一整個函數體,不須等函數執行完成。 #func(),函數帶括弧時,調用的是函數的執行結果,需要等待函數執行完成的結果。
和@方法的區別主要在於如何使用裝飾器函數。我們需要在調用時把執行函數傳入給裝飾函數,return 的結果返回給新函數;再運行新函數,就實行了這一效果。
- 最後對裝飾函數的代碼做一個解讀。(應該在前面解讀的)
- printname(func)是裝飾函數,它的參數是func(一個函數,即執行函數),它的return是wrapper函數。
- 在printname里定義一個wrapper函數(參數、關鍵字可變),把裝飾器的內容包裝在wrapper里。wrapper返回的是func(執行函數)。
- 相當於是這一段代碼執行了wrapper函數
1 def printname(func): 2 def wrapper(*args, **kwargs): 3 print(f"[DEBUG]: enter {func.__name__}()") 4 return func(*args, **kwargs) 5 return wrapper
實現了:print(裝飾)+ func(執行函數),即成功裝飾。
- 還有一個很有趣的例子,在https://www.cnblogs.com/cicaday/p/python-decorator.html#4027718046,感謝作者的點撥。
- 裝飾器總結:
實質: 是一個函數
參數:是你要裝飾的函數名(並非函數調用)
返回:是裝飾完的函數名(也非函數調用)
作用:為已經存在的對象添加額外的功能
特點:不需要對對象做任何的代碼上的變動