首頁 > 軟體

Python裝飾器詳細介紹

2022-03-25 19:01:02

裝飾器

一、介紹

  • :代表函數的意思。裝飾器本質就是是函數
  • 功能:裝飾其他函數,就是為其他函數新增附加功能 
  • 被裝飾函數感受不到裝飾器的存在
  • 原則: 

    不能修改被裝飾的函數的原始碼(比如線上環境)

    不能修改被裝飾的函數的呼叫方式 

  • 實現裝飾器知識儲備: 

    函數即是“變數”

    高階函數

    巢狀函數

高階函數+巢狀函數=>裝飾器

二、通過高階函數+巢狀函數==>實現裝飾器

先分析以下兩段程式碼能不能執行?

def foo():
    print("in the foo")
    bar()
def bar():
    print("in the bar")

foo()
def foo():
    print("in the foo")
    bar()
foo()
def bar():
    print("in the bar")

第二段程式碼報錯:

NameError: name 'bar' is not defined

1、變數知識回顧

定義變數: 
如:定義變數:x=1,會在記憶體中找塊記憶體空間把“1”存進去,把“1”的記憶體地址給x  

前面提到:函數即變數

# 定義函數
def test():
    pass
# 就相當於把函數體賦值給test變數
test = '函數體'  # 函數體就是一堆字串而已
# 只不過函數呼叫要加上小括號呼叫
test()

python記憶體回收機制,是直譯器做的。直譯器到底怎麼去回收這個變數? 
python直譯器當中有種概念叫做參照計數。什麼叫參照計數呢? 
比如:定義x=1,之後又定義了y=1或y=x,實際上又把記憶體空間“1”的記憶體地址賦值給y 
這裡x代表一次參照,y代表一次參照。加起來兩次參照。 
python什麼時候會把“1”這個記憶體空間清空呢?會回收記憶體呢? 
當x這個變數沒有了,y這個變數也沒有了,便會把“1”這個記憶體空間清掉

del x  # 刪的只是變數名,記憶體中的值是直譯器回收

匿名函數

lambda x:x*x

匿名函數沒有函數名,沒有參照,所以會被垃圾回收機制立馬回收掉。 
所以匿名函數要賦值給變數,把函數體賦值給變數名

calc = lambda x:x*x
print(calc(4))

現在可以再理解下最開始兩段程式碼能不能執行的原因。 

2、高階函數(裝飾器前奏)

什麼叫高階函數呢:

  • 把一個函數名當做形實傳給另外一個函數
  • 返回值中包含函數名
def f1():
    print("in the func1")
def test1(func):
    print(func)
test1(f1)

執行結果(列印記憶體地址)

<function func1 at 0x000002805DE12378>

如下程式碼,能不能執行:

def f1():
    print("in the func1")
def test1(func):
    print(func)
    func()
test1(f1)

函數即變數,像“x=1,y=x”,同樣f是一個是一個函數,可不可以像一個變數一樣來回賦值呢?

import time
def func1():
    print("in the func1")
    time.sleep(1)
def test1(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func run time is %s" %(stop_time-start_time))
test1(func1)

到這裡,貌似實現了裝飾函數的功能。 
看上面裝飾器的原則: 
這裡:沒有修改func1的原始碼,但是呼叫方式改變了。現在是test1(func1),之前是func1() 
現在能做到哪一點呢? 
把一個函數名當做實參傳給另外一個函數(不修改被裝飾的函數原始碼的情況下為其新增功能)

2) 下面用第二個條件(返回值中包含函數名),做另外一個高階函數

import time
def func2():
    time.sleep(1)
    print("in the func2")
def test2(func):
    print(func)
    return(func)
print(test2(func2))

執行結果:

<function func2 at 0x00000162F3672378>
<function func2 at 0x00000162F3672378>

把函數記憶體地址都列印出來了,看到這麼多記憶體地址,有什麼想法? 
加上小括號就能執行。 
上面程式碼“test2(func2())”和“test2(func2)”有什麼區別?加上小括號是函數返回結果,不加是函數記憶體地址。所以加上小括號就不符合高階函數定義了。 
既然以後有了函數的記憶體地址,是不是可以賦值給其他變數?下面

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)
t = test2(func2)
print(t)
t()

好像還沒什麼用,怎麼讓他有用呢? 
把test2(func2)賦值給func2

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)
func2 = (test2(func2))
func2()

這就是高階函數的第二個好處:返回值中包含函數名(不修改函數的呼叫方式)

3、巢狀函數(裝飾器前戲)

巢狀函數:在一個函數體內,用def去宣告一個函數

def foo():
    print("in the foo")
    def bar():
        print("in the bar")
    bar()
foo()

看一下下面的程式碼是不是巢狀:

def foo():
    print("in the foo")
def bar():
    foo()
bar()

注意函數巢狀和函數呼叫區別  

區域性作用域和全域性作用域的存取順序:

x = 0
def grandpa():
    # x = 1
    def dad():
        x = 2
        def son():
            x = 3
            print(x)
        son()
    dad()
grandpa()

三、裝飾器

1、裝飾器

前面鋪墊了那麼多,現在開講正題:裝飾器 
先用高階函數實現給函數不修改原始碼的情況下新增功能

import time
def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
deco(test1)
deco(test2)

按照上面說的,如何實現不改變呼叫方式?直接“test1 = deco(test1)”和“test2 = deco(test2)”嗎? 
別忘記了,第二種方式,高階函數要加上return,如下

import time
def deco(func):
    start_time = time.time()
    return func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
test1 = deco(test1)
test2 = deco(test2)
deco(test1)
deco(test2)

雖然沒有修改原始碼和呼叫方式,但是函數加上return,函數就結束了,然並卵。怎麼實現呢? 
前面一直在用高階函數,還沒有用巢狀函數,加上巢狀函數能不能實現呢?看一下

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的記憶體地址
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")
print(timer(test1))  # 可見:返回deco的記憶體地址
test1 = timer(test1)
test1()
timer(test2)()

到此,完成實現了裝飾器的功能。但是還是有點麻煩,如何能不要“test1 = timer(test1)”, 
python直譯器提供了語法糖“@”符合,給哪個函數新增功能,就加在哪個函數頭部

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的記憶體地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2():
    time.sleep(1)
    print("in the test2")

test1()
test2()

2、有參裝飾器

前面實現了裝飾器的功能,但是如果函數有引數,能不能也能執行呢

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的記憶體地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2()

報錯:丟失引數

TypeError: test2() missing 1 required positional argument: 'name'

@timer 相當於 test2=timer(test2) =deco 
test2() 相當於執行deco(),所以沒指定引數,報錯。 
如何傳引數呢?為了適應各種不同引數的函數

import time
def timer(func):  # timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的記憶體地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2("fgf")

3、終極裝飾器

注意,上面的例子中還沒有涉及返回值,看下面的例子可以體會一下 
假設:公司網站需要驗證登入,有不同的驗證方式:本地認證、LDAP認證等

#/usr/bin/env python
# -*- coding: UTF-8 -*-
import time
user,passwd = 'fgf','abc123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("33[32;1mUser has passed authentication33[0m")
                    res = func(*args, **kwargs)  # from home
                    print("---after authenticaion ")
                    return res
                else:
                    exit("33[31;1mInvalid username or password33[0m")
            elif auth_type == "ldap":
                print("搞毛線ldap,不會。。。。")

        return wrapper
    return outer_wrapper
def index():
    print("welcome to index page")
@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"

@auth(auth_type="ldap")
def bbs():
    print("welcome to bbs  page")
index()
print(home()) #wrapper()
bbs()

到此這篇關於Python裝飾器詳細講解的文章就介紹到這了,更多相關Python裝飾器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com