1. 程式人生 > >函式物件和裝飾器

函式物件和裝飾器

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()
認證與計時裝飾器

 當你想新增的功能需要接收一個引數時,需要寫成三層的有參裝飾器。