python函數 | 裝飾器詳解


裝飾器(Decorators)是 Python 的一個重要部分。簡單地說:他們是修改其他函數的功能的函數。他們有助於讓我們的代碼更簡短,也更Pythonic(Python范兒)。在程序開發中經常使用到的功能,合理使用裝飾器,能讓我們的程序如虎添翼。

一、 函數名應用

函數名是什么?函數名是函數的名字,本質:變量,特殊的變量。

(1)函數名就是函數的內存地址,直接打印函數名,就是打印內存地址

def func1():
    print(123)
print(func1)         # <function func1 at 0x0000029042E02E18>

(2)函數名可以作為變量

def func1():
    print(111)

f = func1
f()                 # f() 就是func1()  

(3)函數名可以作為函數的參數

def func1():
    print(111)

def func2(x):
    x()

func2(func1)         #func1作為func2的參數 

(4)函數名可以作為函數的返回值

def wrapper():
    def inner():
        print('inner')
    return inner
f = wrapper()
f()

(5)函數名可以作為容器類類型的元素

使用for循環批量執行函數
def func1():
    print('func1')
def func2():
    print('func2')
def func3():
    print('func3')

l1 = [func1,func2,func3]
for i in l1:
    i()

像上面函數名這種,叫做第一類對象。

 

第一類對象( first-class object)指:

  • 1.可在運行期創建
  • 2.可用作函數參數或返回值
  • 3.可存入變量的實體

*不明白?那就記住一句話,就當普通變量用

 

二、閉包

1、閉包函數內部函數包含對外部作用域而非全局作用域變量的引用,該內部函數稱為閉包函數

2、閉包的作用:爬蟲、裝飾器

  當程序執行遇到函數執行時,會在內存空間開辟局部命名空間,當函數執行完畢,該命名空間會被銷毀。但是如果這個函數內部形成閉包,則該內存空間不會隨着函數執行完而消失。

3、如何判斷是否是閉包:print(函數名.__closure__) 結果是cell說明是閉包,結果是None說明不是閉包。

 

閉包舉例

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()

wrapper()     # summer

如何判斷它是否是一個閉包函數呢? 內層函數名.__closure__  cell 就是=閉包

1.

def wrapper():
    name = 'summer'
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper()   
執行輸出:
summer
(<cell at 0x0000017FC9C90B58: str object at 0x0000017FCA349AD0>,)

2.

name = 'summer'
def wrapper():
    def inner():
        print(name)
    inner()
    print(inner.__closure__)

wrapper() 
結果輸出:
summer
None

返回值為None 表示它不是閉包,因為name是一個全局變量,如果函數調用了外層變量而非全局變量,那么它就是閉包。

3.

name = 'summer'
def wrapper2():
    name1 = 'spring'
    def inner():
        print(name)
        print(name1)
    inner()
    print(inner.__closure__)

wrapper2()
結果輸出:
summer
spring
(<cell at 0x030B7310: str object at 0x03043680>,)

只要引用了外層變量至少一次,非全局的,它就是閉包

4:判斷下面的函數,是一個閉包嗎?******

name = 'summer'
def wraaper2(n):        #相當於n = 'summer' 
  def inner():
        print(n)
    inner()
    print(inner.__closure__)  

wraaper2(name)
結果輸出:
summer
(<cell at 0x03867350: str object at 0x037F3680>,)

它也是一個閉包. 雖然wraaper2傳了一個全局變量,但是在函數wraaper2內部,inner引用了外層變量,相當於在函數inner外層定義了 n = 'summer',所以inner是一個閉包函數

 

閉包的好處當函數開始執行時,如果遇到了閉包,他有一個機制,他會永遠開辟一個內存空間,將閉包中的變量等值放入其中,不會隨着函數的執行完畢而消失。

 舉一個例子:爬3次,內存開了3次,很占用內存

from urllib.request import urlopen
content1 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content2 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')
content3 = urlopen('https://www.cnblogs.com/').read().decode('utf-8')

把它封裝成閉包

from urllib.request import urlopen

def index():
    url = "https://www.cnblogs.com/"
    def get():
        return urlopen(url).read()
    return get        #return的是get,就是一個函數名

cnblog = index()
print(cnblog)               # <function index.<locals>.get at 0x02F46978>
content = cnblog()
print(content)              # 頁面源碼

這個例子,只有第一遍,是從網站抓取的。之后的執行,直接從內存中加載,節省內存空間

 

三、裝飾器

1 裝飾器初識

裝飾器本質就是一個python函數,他可以讓其他函數在不需要做任何代碼變動的前提下,增加額外的功能,裝飾器的返回值也是一個函數對象。

 

裝飾器的應用場景:比如插入日志,性能測試,事務處理,緩存等等場景。

import time
def timmer(f):                           
    def inner():
        start_time = time.time()             
        f()                                                         
        end_time = time.time()             
        print('此函數的執行時間為{}'.format(end_time - start_time))         
    return inner                       

def func1():                           
    print('in func1')               
    time.sleep(1)                    

func1 = timmer(func1)               
print(func1)
func1()                           # 這里的func1是全新的func1,就是上面的賦值,此時相當於執行 inner函數
輸出結果:
<function timmer.<locals>.inner at 0x03822DF8>
in func1
此函數的執行時間為1.0003533363342285

代碼從上至下執行

 

語法糖:想測試誰,前面加@裝飾器函數,即可。寫裝飾器,約定俗成,函數名為wrapper

def wrapper(func):
    def inner(*args,**kwargs):
        '''被裝飾函數之前'''
        ret = func(*args,**kwargs)
        '''被裝飾函數之后'''
        return ret
    return inner

@wrapper
def func(*args,**kwargs):
    print(args,kwargs)
    return 666

print(func())
輸出結果:
() {}
666

裝飾器利用return制造了一個假象,func()執行,其實是執行inner()func()把原來的func()給覆蓋了

2. 裝飾器傳參

1:上面裝飾器的例子,func1,要傳2個參數a,b

import time
def timmer(f):
    def inner(a,b):
        start_time = time.time()
        f(a,b)
        end_time = time.time()
        print('此函數的執行時間為{}'.format(end_time - start_time))
    return inner

@timmer
def func1(a,b):
    print('in func1 {}{}'.format(a,b))
    time.sleep(1)  # 模擬程序邏輯

func1(1,2) 
執行輸出:
in func1 12
此函數的執行時間為1.0006024837493896

2:如果有多個參數呢?改成動態參數

import time
def timmer(f):
    def inner(*args,**kwargs):
        start_time = time.time()
        f(*args,**kwargs)
        end_time = time.time()
        print('此函數的執行時間為{}'.format(end_time - start_time))
    return inner

@timmer
def func1(*args,**kwargs):
    print('in func1 {}{}'.format(args,kwargs))
    time.sleep(1)  # 模擬程序邏輯

func1(1,2,a='3',b=4) 
執行輸出:
in func1 (1, 2){'b': 4, 'a': '3'}
此函數的執行時間為1.000101089477539

函數的執行時,*打散

函數的定義時,*聚合。

from functools import wraps
def wrapper(f):                  # f = func1
    def inner(*args,**kwargs):               #聚合,args (1,2,3)
        '''執行函數之前的相關操作'''
        ret = f(*args,**kwargs)               # 打散 1,2,3
        '''執行函數之后的相關操作'''
        return ret
    return inner

@wrapper  # func1 = wrapper(func1)  func1 = inner
def func1(*args):                           #args (1,2,3) 聚合
    print(666)
    return args

print(func1(*[1,2,3]))  
執行輸出:
666
(1, 2, 3)

3*****

import time                                 #1.加載模塊

def timmer(*args,**kwargs):                     #2.加載變量  5.接收參數True,2,3

    def wrapper(f):                             #6.加載變量  8.f = func1

        print(args, kwargs)                     #9.接收timmer函數的值True,2,3

        def inner(*args,**kwargs):                 #10.加載變量. 13.執行函數inner
            if flag:                         #14 flag = True
                start_time = time.time()             #15 獲取當前時間
                ret = f(*args,**kwargs)             #16 執行func1
                time.sleep(0.3)                 #19 等待0.3秒
                end_time = time.time()             #20 獲取當前時間
                print('此函數的執行效率%f' % (end_time-start_time)) #21 打印差值
            else:
                ret = f(*args, **kwargs)

            return ret                         #22 返回給函數調用者func1()
        return inner                         #11 返回給函數調用者wrapper
    return wrapper                         #7.返回給函數調用timmer(flag,2,3)

flag = True                                 #3 加載變量
@timmer(flag,2,3)      # 4.執行函數timmer(flag,2,3) 17.執行函數func1 兩步:1,timmer(flag,2,3) 相當於執行wrapper                                                     2.@wrapper 裝飾器 func1 = wrapper(func1)
def func1(*args,**kwargs):
    return 666                             #18 返回給函數調用者f(*args,**kwargs)

print(func1())                             #12 執行函數 

寫裝飾器,一般嵌套3層就可以了

3.多個裝飾器,裝飾一個函數

def wrapper1(func):                  # func ==  f函數名
    def inner1():
        print('wrapper1 ,before func')          # 2
        func()
        print('wrapper1 ,after func')          # 4
    return inner1

def wrapper2(func):  # func == inner1
    def inner2():
        print('wrapper2 ,before func')          # 1
        func()
        print('wrapper2 ,after func')          # 5
    return inner2

@wrapper2                      #  f = wrapper2(f)  里面的f==inner1  外面的f == inner2
@wrapper1                      #  f = wrapper1(f)   里面的f==函數名f  外面的f == inner1

def f():                          # 3
    print('in f')

f()                              # inner2() 
執行輸出:
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func

哪個離函數近,哪個先計算最底下的先執行

執行順序如下圖:

 

 多個裝飾器,都是按照上圖的順序來的

 

4. 裝飾器的__name____doc___

__name__:函數名

__doc___:函數的解釋  

普通函數

def func1():
    """
    此函數是完成登陸的功能,參數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)

func1()
print(func1.__name__)         #獲取函數名
print(func1.__doc__)         #獲取函數名注釋說明 

執行輸出:
666
func1
此函數是完成登陸的功能,參數分別是...作用。
return: 返回值是登陸成功與否(True,False)

這個有什么用呢?比如日志功能,需要打印出誰在什么時間,調用了什么函數,函數是干啥的,花費了多次時間,這個時候,就需要獲取函數的有用信息了

帶裝飾器的函數

def wrapper(f):      # f = func1

    def inner(*args,**kwargs):             #聚合, args (1,2,3)
        '''執行函數之前的相關操作'''
        ret = f(*args,**kwargs)              # 打散 1,2,3
        '''執行函數之后的相關操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函數是完成登陸的功能,參數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
執行輸出:
666
inner
執行函數之前的相關操作

函數裝飾之后,相當於執行了inner函數,所以輸出inner

 

為了解決這個問題,需要調用一個模塊wraps

wraps將 被修飾的函數(wrapped) 的一些屬性值賦值給修飾器函數(wrapper) ,最終讓屬性的顯示更符合我們的直覺

from functools import wraps

def wrapper(f):                  # f = func1
    @wraps(f)                 #f是被裝飾的函數
    def inner(*args,**kwargs):         #聚合args (1,2,3)
        '''執行函數之前的相關操作'''
        ret = f(*args,**kwargs)          # 打散 1,2,3
        '''執行函數之后的相關操作'''
        return ret
    return inner

@wrapper
def func1():
    """
    此函數是完成登陸的功能,參數分別是...作用。
    return: 返回值是登陸成功與否(True,False)
    """
    print(666)
    return True

func1()
print(func1.__name__)
print(func1.__doc__) 
執行輸出:
666
func1
此函數是完成登陸的功能,參數分別是...作用。
return: 返回值是登陸成功與否(True,False)


千心萬苦寫了的博客,如果您覺得本篇文章有幫助到您,請麻煩右下角點贊呦!算是給我的鼓勵!我會繼續加油, 爭取給大家分享到有用的知識!相互學習,一起進步!謝謝啦~


免責聲明!

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



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