函式物件和裝飾器
1、函式物件
在面向物件程式設計中,一切皆物件。所以函式也可以當作物件來操作。
比如將函式func1作為引數傳給func2內部、或將func1在func2內返回(return)、將函式作為列表的元素,字典的vaule ......等
注意這些操作中,作為物件的func1是沒有加()的,也就是作為一個物件,被當作值使用,而不是直接呼叫執行func1內的程式碼。
這就是函式物件的概念。
運用這個概念,可以簡單的寫一個函式字典:
def register(): print("register......") def auth(): print("login....") def check(): print("checking....") func_dict = {"1": register, "2": auth, "3": check} while True: func_list = ["註冊", "登入", "檢視"] for i, item in enumerate(func_list, start=1): print(i, item) cmd = input(" inpput cmdmand>>") if func_dict.get(cmd): func_dict.get(cmd)() else: print("輸入有誤")
既然函式是物件,函式的操作都可以在其他函式內操作,比如定義和呼叫:
def open_door(): print("開啟冰箱門") def putin(): print("把大象放進冰箱") def close_door(): print("關上冰箱門") close_door() putin() open_door()巢狀定義
2、名稱空間和作用域
名稱空間(名稱空間)有如下幾種:
名稱空間:儲存 名稱與記憶體地址繫結體(名稱與地址的對映) 的空間,它有三種:
內建名稱空間(Built-in):存放python自帶的名稱與值的繫結體,len、 print、 sum等內建方法的名字,注意關鍵字比如if、while...不存放在其中
在python直譯器啟動時建立,一直保留直到直譯器退出。
全域性名稱空間(Global):當開啟一個檔案然後執行時,全域性名稱空間記錄了檔案中定義的變數,包括此檔案中定義的函式、類、其他匯入的模組、模組級的變數與常量。
在.py檔案被載入時建立,通常一直保留直到檔案執行結束,python直譯器退出銷燬
區域性名稱空間(Local):每個函式所擁有的名稱空間,記錄了函式中定義的所有變數,包括函式的引數、內部定義的區域性變數。
在函式被呼叫時才被建立,但函式返回結果或丟擲異常時被刪除。(每一個遞迴函式都擁有自己的名稱空間)。
此外,如果函式多層巢狀,介於全域性和區域性間,還有當前所在層函式的名稱空間,比如函式巢狀定義時,putin()函式有自己的名稱空間,也可以看作區域性名稱空間,只不過
它的子函式close_door()還有自己的區域性名稱空間(Local),即,區域性是相對的。
名稱空間的載入順序:內建======》全域性======》區域性
名稱空間的訪問順序:區域性===逐層往上===》全域性====》內建
作用域,即名稱空間的作用範圍,Global廣義化為—— 全域性+內建名稱空間、Local—— 區域性名稱空間
可以用 print(global()) 和在區域性用print(local()) 檢視當前位置的作用域內都有哪些"名稱”。
還可以用 global 變數名 的方式,將變數宣告為全域性變數。nonlocal 變數名,將會宣告:之後在當前作用域使用這個變數,將會引用外層(優先上一層)變數,但引用不到全域性。
函式的作用域在定義時就固定了,與之後呼叫函式的位置無關!!!!
函式的作用域在定義時就固定了,與之後呼叫函式的位置無關!!!!
函式的作用域在定義時就固定了,與之後呼叫函式的位置無關!!!!
3、閉包函式
定義在一個函式func1內的函式func2,在返回內部函式func2時,不是單純返回此函式的地址,還將函式中訪問到的本層和上一層(非全域性)名稱與值的對映一起返回了。
閉包就是函式物件的應用
4、裝飾器
現在你寫了一個函式,在你的程式裡多次呼叫,後期你需要給函式擴充套件功能,根據開閉原則:可以擴充套件功能,加入新的程式碼,但不能修改原有寫好的程式碼。所以你需要在不改變函式呼叫方式何不修改函式內程式碼的情況下,給函式增加功能。
以給shop()函式新增計算函式執行時間為例:
import time, random # shop()原來的程式碼如下 def shop(): print("shopping...") time.sleep(random.random())
你需要這樣寫才能完成你的功能:
start = time.time() shop() stop = time.time() print("this process run %.5f s" % (stop - start))
為了容易呼叫,你決定將它封裝為一個函式:
def inner(): start = time.time() shop() stop = time.time() print("this process run %.5f s" % (stop - start))inner函式用於計算shop時間
但是這樣來看,你修改了函式的呼叫方式,你現在需要呼叫inner 才能完成功能。如果強行將shop=inner(),shop(),會發生迴圈呼叫,然後報錯,怎樣才能讓shop()不會自己呼叫自己呢?
讓shop這個函式名與呼叫 () 不在同一層名稱空間,只有通過閉包的方式才能拿到一次,然後執行shop。同時在執行的那一層可以新增新功能。即:
def timer(): func=shop def inner(): start = time.time() func() stop = time.time() print("this process run %.5f s" % (stop - start)) return inner shop=timer() shop()
看到麼,函式裡,func = shop 就相當於最初的3中閉包語句裡的 x =1,我們通過閉包的方式完成了傳值!!記住,閉包也是一種傳值方式!
但是還不夠,這樣的功能被寫死了,只能計算shop()的時間,我們之前在函式引數那一節學過用第一種用變數傳值的方式,還記得麼?用到這裡就是:
def timer(func): def inner(): start = time.time() stop = time.time() print("this process run %.5f s" % (stop - start)) return inner shop=timer(shop) # 前一個shop是變數名,第二個shop是函式(一個地址,shop函式的程式碼地址)作為vaule傳給timmer裡的形參func shop()
這樣就完成了一個裝飾器
裝飾器思路
現在思考下,這個裝飾器有沒有不足?
1.用裝飾器實際上是“偷樑換柱”將被裝飾函式換成了inner,如果被裝飾函式有引數傳入,而inner現在不能接收引數,就會報錯了。
2.如果被裝飾函式有返回值,用目前的裝飾器接收不到。
所以進一步完善下:
def timer(func): def inner(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("this process run %.5f s" % (stop - start)) return res return inner
到這裡,想必你已經能總結出一個裝飾器的固定模板了,那就是:
def timer(func): def inner(*args, **kwargs): # 新增功能 res = func(*args, **kwargs) # 新增功能 return res return inner裝飾器模板
最後,給你一個快捷使用裝飾器的方式,在程式設計中,將某種程式碼簡化,固定成形的語法被稱為語法糖,裝飾器這裡就有一顆語法糖,無參裝飾器的完整使用方法如下:
import time, random def timer(func): def inner(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("this process run %.5f s" % (stop - start)) return res return inner # 語法糖 @timer def shop(): print("shopping...") time.sleep(random.random()) shop()
試試再給shop()和與之同樣的pay()加上認證的功能吧。
import time import random status_dict = {"user": None, "login": False} db = 'info.txt' def auth(auth_source="text"): def auth_status(func): def wrapper(*args, **kwargs): if status_dict["user"] and status_dict.get("login"): return func if auth_source == "text": uname = input("輸入使用者名稱》》") upwd = input("輸入密碼》》") with open(db, 'rt', encoding='utf8') as f: for line in f: info_dict = eval(line.strip()) if uname == info_dict.get("name") and upwd == info_dict.get("pwd"): print("login successful") status_dict["user"] = uname status_dict["login"] = True res = func(*args, **kwargs) return res else: print("username or pwd error!") elif auth_source == " mySQL": print("沒有資料") else: print("沒有資料") return wrapper return auth_status def timer(func): def inner(*args, **kwargs): start = time.time() res = func(*args, **kwargs) stop = time.time() print("this process run %.5f s" % (stop - start)) return res return inner @timer def shop(): print("shopping...") time.sleep(random.random()) shop() @auth(auth_source="text") @timer def pay(): print("paying...") pay()認證與計時裝飾器
當你想新增的功能需要接收一個引數時,需要寫成三層的有參裝飾器。