Python中的裝飾器


裝飾器

  • 裝飾器定義;
    • 本質是函數 函數的目的是完成特定的功能
  • 裝飾器功能:一個裝飾其他函數功能的函數(為其他函數添加特定的功能)

拋出問題:

  假如我們現在有10個函數,每個函數都有自己獨特的功能,但是,現在我們需要給這10個函數添加一個記錄日志的功能

# 定義日志的函數,然后將日志函數添加到test的十個函數中
def logger():
    print("...logger...")

def test1():
    pass
    logger()

def test2():
    pass
    logger()

def test3():
    pass
    logger()

test1()
test2()
test3()
使用添加函數的方法

  特定場景:假如,這10個函數已經再線上運行了,比如說,現在需要再用戶已經使用的軟件中,給這10個函數添加新的功能,那么該怎么做?

    • 如果我們直接修改函數的源代碼,可能會導致軟件崩潰,所以原則上,我們不能在已經上線的程序中修改源代碼。

裝飾器原則:

  • 原則1:不能修改被裝飾的函數的源代碼
  • 原則2:不能修改被裝飾的函數的調用方式
  • 裝飾器對被裝飾的函數是完全透明的,函數感知不到裝飾器的存在,因為函數的源代碼和調用方式都沒有改變

初識裝飾器:

  • 計算函數運行時間的裝飾器
    import time
    # 創建獲取函數運行時間的裝飾器
    def timmer(func):
        def warpper(*args, **kwargs):
            start_time = time.time()
            func()
            stop_time = time.time()
            print("the func run time is %s" % (stop_time-start_time))
        return warpper
    
    @timmer     #為test函數添加獲取test函數運行時間的功能
    def test():
        time.sleep(3)
        print("in the test")
    
    test()
    """
    運行效果:
    in the test
    the func run time is 3.004085063934326
    """
  • 由上代碼可以得知:
    • 裝飾器本身就是一個函數
    • 裝飾器不修改被裝飾函數的源代碼、同時也不修改被裝飾函數的調用方式
    • 對於test函數來說,裝飾器timmer就和不存在一樣

實現裝飾器的知識儲備:

  • 函數即”變量“
  • 高階函數
  • 嵌套函數

  高階函數 + 嵌套函數 =》 裝飾器

函數即“變量”:

# 第一種
def foo():
    print("in the foo")
    bar()
foo()

第二種
def foo():
    print("in the foo")
    bar()
def bar():
    print("in the bar")
foo()

# 第三種
def bar():
    print("in the bar")
def foo():
    print("in the foo")
    bar()
foo()

# 第四種
def foo():
    print("in the foo")
    bar()
foo()
def bar():
    print("in the bar")
分析內存地址

高階函數:

  • 把一個函數名當作實參傳遞給另一個函數:

    import time
    def bar():
        time.sleep(3)
        print("in the bar")
    # print(bar) # bar記錄了bar函數在內存中的地址
    
    def foo(func):
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func run time is %s" % (stop_time - start_time))
    
    # 滿足了裝飾器的不修改被裝飾器的源代碼的條件,但是調用方式被修改了
    foo(bar) 
    """
    運行結果:
    in the bar
    the func run time is 3.0134708881378174
    """
    符合裝飾器的條件之一
    • 在不修改被裝飾函數源代碼的情況下為其添加功能
  • 返回值中包含函數名
    import time
    def bar():
        time.sleep(3)
        print("in the bar")
    
    def foo(func):
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func run time is %s" % (stop_time - start_time))
        return func
    
    # foo(bar())
    
    #print(foo(bar))
    
    # t = foo(bar)
    # print(t)
    
    # t = foo(bar)
    # t()
    # 不修改函數的調用方式,為其添加功能
    bar = foo(bar)
    bar()
    符合裝飾器的條件之二
    • 不修改被裝飾函數的調用方式

嵌套函數:

  • 在函數體內使用def關鍵字定義一個新的函數
    # 嵌套函數
    def foo():
        print("in the foo")
        def bar():
            print("in the bar")
        bar()
    # bar() #報錯,因為bar的作用域僅在foo()函數體內,當foo函數運行結束,那么bar就會釋放空間
    foo()
    嵌套函數
  • 變量作用域的訪問順序
    x = 0
    def grandfather():
        x = 1
        def father():
            x = 2
            def son():
                x = 3
                print(x)
            son()
        father()
    grandfather()
    代碼演示

閉包:

  當局部變量脫離了函數體后,依然可以使用:

def foo():
    sum_1 = 100
    def deco():
        print(sum_1)
    return deco
a = foo()
a()     #100
# print(sum_1) # 報錯,局部變量不能在局部以外的地方使用
演示閉包

  解釋代碼:

def foo(x):
    def deco(y):
        print(x+y)
    return deco

a = foo(10)
print(a)    #<function foo.<locals>.deco at 0x00000275D6421040>
a(5)        # 15
b = foo(20)
print(b)    #<function foo.<locals>.deco at 0x0000017D992D10D0>
b(5)        #25

"""
所有函數都有一個__closure__屬性,如果這個函數是一個閉包的話,那么它返回的是一個由cell對象組成
的元組對象。cell對象的cell_contents屬性就是閉包中的自由變量
"""
print(foo.__closure__)  #None
print(a.__closure__)    #(<cell at 0x000002B1F02C1370: int object at 0x000002B1F0116A50>,)
print(b.__closure__)    #(<cell at 0x000002B1F018A0D0: int object at 0x000002B1F0116B90>,)
print(a.__closure__[0].cell_contents)   #10
print(b.__closure__[0].cell_contents)   #20
"""
這解釋了為什么局部變量脫離函數之后,還可以在函數之外被訪問的原因,因為它存儲在了閉包的cell_contens中了
"""
演示閉包原理

實現裝飾器:

import time
def timer(func):
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func run time is %s" % (stop_time - start_time))
    return deco

@timer
def test1():
    time.sleep(3)
    print("in the test1")
@timer
def test2():
    time.sleep(3)
    print("in the test2")

# test1 = timer(test1)
# test2 = timer(test2)
test1()
test2()

改進后的裝飾器:

import time
def timer(func):
    def deco(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        stop_time = time.time()
        print("the func run time is %s" % (stop_time - start_time))
    return deco

@timer
def test1():
    time.sleep(3)
    print("in the test1")
@timer
def test2(name):
    time.sleep(3)
    print("in the test2 %s"% name)

test1()
test2("python")

 優化后的裝飾器:

import time
def timer(func):
    def deco(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print("the func run time is %s" % (stop_time - start_time))
        return res
    return deco

@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(2)
    print("in the test2 %s"% name)

@timer
def test3(name, age):
    time.sleep(3)
    print("in the test3 %s"% name)
    return age+1

test1()
test2("python")
#test3("某人飛",999)
print(test3("某人飛",999))

加強版裝飾器:

# 加強版裝飾器
usern = "fjf"
passwd = "123456"
def dl(func,*args, **kwargs):
    username = input("請輸入您的賬號")
    password = input("請輸入您的密碼")
    if username == usern and password == passwd:
        print("登錄成功")
        res = func(*args, **kwargs)
        return res
    else:
        print("您的賬號或者密碼錯誤")

def login(auth_type):
    def wrapper_inout(func):
        def wrapper(*args, **kwargs):
            if auth_type == "loca":
                print("通過loca驗證")
                dl(func, *args, **kwargs)

            elif auth_type == "loca1":
                print("通過loca1驗證")
                dl(func, *args, **kwargs)

            elif auth_type == "ip":
                print("通過ip驗證")
                dl(func, *args, **kwargs)
            else:
                print("暫不支持其他登錄方式")
        return wrapper
    return wrapper_inout

@login(auth_type="loca")
def qq_user():
    print("歡迎使用qq")

@login(auth_type="loca1")
def wx_user(name):
    print("歡迎%s使用微信"%name)

@login(auth_type="ip")
def wb_user(name, age):
    print("歡迎%s使用微博"%name)
    return age >= 18

@login(auth_type="smdx")
def ys():
    pass

ys()
qq_user()
wx_user("某人飛")
print(wb_user("某人飛", 17))

 

"""
閉包中的拓展知識點:
導入functools模塊:
1.@functools.wraps(func):在閉包中將原函數func的文件名、注釋文檔等,傳遞給inner函數。
2.func.__name__:獲取函數的名字(字符串), 比如:func 這是一個字符串,並不是一個func函數的地址
3.func.__doc__:獲取函數的注釋文檔
"""
import functools


def auth(func):
    @functools.wraps(func)  # inner.__name=func.__name   inner.__doc__=func.__doc__
    def inner(*args, **kwargs): 
        """這個是inner函數的注釋文檔"""
        # 在原函數func執行之前添加的功能
        res = func(*args, **kwargs)  # 對接收到的參數進行拆包,傳遞給原函數func()
        # 在原函數func執行之之后添加的功能
        return res  # 將原函數func的返回值返回給調用者
    return inner


@auth
def func():
    """這個是func函數的注釋文檔"""
    print("this is func")
    return "func"


print(func.__name__)    # 原結果:inner  加上代碼 @functools.wraps(func) 后結果為:func
print(func.__doc__)     # 原結果:這個是inner函數的注釋文檔   加上代碼 @functools.wraps(func) 后結果為:這個是func函數的注釋文檔

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM