python 進階:修飾器的介紹


  參考鏈接:Python 函數裝飾器

  我認為python中的裝飾器是一個很厲害的功能,他能瞬間提升代碼的逼格,但對於我這樣的小白來說,別說為所欲為的使用了,就連簡單的嘗試一下,卻也是難於登天。經過長達半年的努力,我終於找到了大部分裝飾器的介紹信息,魯迅曾經說過,良好的開始就代表了成功的一半,在我看來,魯迅分明還是太保守,良好的開端無疑代表你已經成功了。比如接下來我們只需要Ctrl+C+V,就可以完成裝飾器的學習了,親愛的小朋友們,你們學會了嗎?

給大人看的分割線


 

裝飾器是用來給已經定義好的函數增加功能用的,能在方法運行期間動態增加方法的功能

1)不會改變原來的代碼結構。

為什么這么說呢,因為相比於另一種方法:只將函數作為變量傳入另一個函數從而來減少重合,使用裝飾器並不需要將函數調用改寫

#定義這個函數來為類似foo()的需要去增加loging.warn()的全部函數增添這個loging.warn()的功能
#假如有許多函數和foo()一樣,都需要增加loging.warn()這行代碼,我們就可以定義一個函數,然后將每個需要增加功能的函數作為參數傳入
#但是這樣必須在用use_logging(foo)去替換原有的foo()這就改變了函數的調用,破壞了原有的代碼結構
def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()

def foo():
    print('i am foo')

use_logging(foo)

 

2)裝飾器在不修改函數代碼的前提下影響代碼的功能;調用python中的函數可以作為變量去傳遞,基於這一特性,我們可以構建出自己的裝飾器;

 

先來了解一些基礎知識:

  1)我們可以將函數作為變量傳遞。要注意括號,加了括號就會運行函數。

  2)在函數的內部還可以嵌套函數

def hi(name="yasoob"):
    print("現在在hi()里面運行")
 
    def greet():
        return "現在在greet()里面運行"
 
    def welcome():
        return "現在在welcom()里面運行"
    print(greet())
    print(welcome())
    print("現在你返回到了hi()里面")
 
hi()
# 上面展示了無論何時你調用hi(), greet()和welcome()將會同時被調用。
# 然后greet()和welcome()函數在hi()函數之外是不能訪問的,比如

  結果:

現在在hi()里面運行
現在在greet()里面運行
現在在welcom()里面運行
現在你返回到了hi()里面

  

 3)從函數中返回函數

def hi(name="yasoob"):
    def greet():
        return "now you are in the greet() function"
 
    def welcome():
        return "now you are in the welcome() function"
 
    if name == "yasoob":
        return greet
    else:
        return welcome
 
a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>
 
#上面清晰地展示了`a`現在指向到hi()函數中的greet()函數
#現在試試這個
 
print(a())
#outputs: now you are in the greet() function

  注意下面的 hi()返回的值是greet,加上后面的括號時,就成為了greet()隨即開始運行

 hi()() #這會輸出 now you are in the greet() function。

  

 4)將函數作為參數傳遞給另外一個函數

def hi():
    return "hi yasoob!"
 
def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())
 
doSomethingBeforeHi(hi)    #注意不要加括號
#outputs:I am doing some boring work before executing hi()
#        hi yasoob!

  

第一個修飾器

def a_new_decorator(a_func):
    def wrapTheFunction():
        print('我在a_func執行前做一些事情')
        a_func()
        print('我在a_func執行后做一些事情')
    return wrapTheFunction  #記得把這個內定義的函數返回

def a_function_requiring_decoration():  #定義一個函數,等會用修飾器來修飾
    print('我是需要用修飾器修飾的函數')

#使用修飾器
a_function_requiring_decoration=a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()

  結果:

我在a_func執行前做一些事情
我是需要用修飾器修飾的函數
我在a_func執行后做一些事情

 

真正的裝飾器:decorator

   另一種生成被裝飾函數的方法:用@

補充----------------

  裝飾器,只能在函數運行前做一些事情,也能在函數運行后執行,不過要是有返回結果的就不能在其后執行了,因為有return語句但是可以將裝飾器寫成這樣

def log(parameter1):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args,**kw):
                s=func(*args,**kw)
                print('函數%s()運行消耗時間%.11f'%(func.__name__,parameter1))
                return s#前面執行,這里返回
            return wrapper
        return decorator

  

補充結束------------------

先了解一下@語句

最簡單的裝飾器

  如果被裝飾的函數運行時不需要參數,我們可以這樣定義裝飾器

def metric(fn):
    print('%s executed in %s ms' % (fn.__name__, 10.24))
    return fn
@metric
def customize(a,b):
  print("我是被裝飾的函數,運行結果%d"%a+b)
customize(1,3)

#輸出
customize executed in 10.24 ms
我是被裝飾的函數,運行結果4

  把@metric放到customize()函數的定義處,相當於執行了語句:

customize=metric(customize)

  由於metric()是一個decorator,返回一個函數,所以,原來的customize()函數仍然存在,只是現在同名的customize變量指向了新的函數,於是調用customize()將執行新函數,即在metric()函數中返回的wrapper中返回的customize函數。

 

普遍使用的裝飾器的格式

  上面的只是最簡單的,我們通常使用裝飾器的時候都需要被裝飾的函數的運行結果,當被裝飾的函數需要參數時,使用上面的定義就不行了:

  

decorator實際上也是一個返回函數的函數,不過他返回的函數並不是我們要裝飾的函數。
裝飾器是在方法執行的過程中動態的為方法增加功能的一種方式,不知道我們能不能想到, 這種方式可以通過先運行要增加的功能,然后再運行要修飾的對象函數
而兩部過程應該放到一個方法體中,而裝飾器返回的就是包含這兩步過程的一個函數。
另外需要注意的是參數,裝飾器接受的參數是要修飾的方法名,其內部的函數接受的是要修飾的方法需要的參數(用*args,**kw)來表示就好
在這個函數中,先運行計划為函數增加功能的代碼,然后運行函數
最后,通過python的@語法將這個裝飾器“綁定”到要修飾的方法上
def a_new_decorator(a_func):
    def wrapTheFunction():
        print('我在a_func執行前做一些事情')
        a_func()
        print('我在a_func執行后做一些事情')
    return wrapTheFunction  #記得把這個內定義的函數返回

#不同之處在這里
@a_new_decorator
def a_function_requiring_decoration():  #定義一個函數,等會用修飾器來修飾
    print('我是需要用修飾器修飾的函數')


a_function_requiring_decoration()

  把@log放到now()函數的定義處,相當於執行了語句:

now = log(now)

  

由於log()是一個decorator,返回一個函數,所以,原來的now()函數仍然存在,只是現在同名的now變量指向了新的函數,於是調用now()將執行新函數,即在log()函數中返回的wrapper()函數。

wrapper()函數的參數定義是(*args, **kw),因此,wrapper()函數可以接受任意參數的調用。在wrapper()函數內,首先打印日志,再緊接着調用原始函數。

解決被修飾的函數會被重命名問題的wraps函數

  使用上面會出現一個問題,獲取_name_時會返回被修飾器內置函數的名字

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction  應該返回a_function_requiring_decoration
之所以返回的是修飾器里內置的名字,是因為它會重寫函數的名字和注釋文檔

而python提供了 functools.wraps() 來解決這個辦法,以如下代碼為例說明 functolls.wraps 使用方法

from functools import wraps
 
def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        ---snip---
    return a_func return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): ---snip-- print(a_function_requiring_decoration.__name__) # Output: a_function_requiring_decoration

@wraps 接受一個被修飾函數的名字,並加入了復制函數名稱、注釋文檔、參數列表等功能,這讓我們能在修飾器里訪問函數被修飾之前的屬性

 

怎樣為修飾器傳入參數:

  

假如裝飾器也需要一些參數怎么辦呢,比如我們想要輸出日志的同時還要輸出程序的運行時間.這需要三個函數來完成,綁定最外面的函數,其他的可以看作不變

  假如 a_function_requiring_decoration() 需要參數

def a_function_requiring_decoration(name):
    --snip--

  我們需要在定義 wrapTheFunction() 時指定參數即:

def wrapTheFunction(name):
        ---snip---
    return a_func(name)

  當裝飾器不知道要裝飾的函數有多少個參數時,可以用 *args 來代替

def wrapTheFunction(*args):
        ---snip---
    return a_func(*args)

  當需要關鍵字參數時

def a_function_requiring_decoration(name, age=None, height=None): 
  print("I am %s, age %s, height %s" % (name, age, height))

  可以這樣

def wrapTheFunction(*args, **kwargs): 
  # args是一個數組,kwargs一個字典 
  logging.warn("%s is running" % func.__name__) 
  return func(*args, **kwargs) 

  和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

  用法

@log('execute')
def now():
    print('2015-3-25')

  和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

>>> now = log('execute')(now)

  我們來剖析上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。

原文鏈接 >>>點擊<<<


免責聲明!

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



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