最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python函數(shù)裝飾器原理與用法詳解

 更新時間:2019年08月16日 08:48:10   作者:寧生信  
這篇文章主要介紹了Python函數(shù)裝飾器原理與用法,結(jié)合實例形式詳細分析了Python裝飾器的原理、功能、分類、常見操作技巧與使用注意事項,需要的朋友可以參考下

本文實例講述了Python函數(shù)裝飾器原理與用法。分享給大家供大家參考,具體如下:

裝飾器本質(zhì)上是一個函數(shù),該函數(shù)用來處理其他函數(shù),它可以讓其他函數(shù)在不需要修改代碼的前提下增加額外的功能,裝飾器的返回值也是一個函數(shù)對象。它經(jīng)常用于有切面需求的場景,比如:插入日志、性能測試、事務(wù)處理、緩存、權(quán)限校驗等應(yīng)用場景。裝飾器是解決這類問題的絕佳設(shè)計,有了裝飾器,我們就可以抽離出大量與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。

嚴格來說,裝飾器只是語法糖,裝飾器是可調(diào)用的對象,可以像常規(guī)的可調(diào)用對象那樣調(diào)用,特殊的地方是裝飾器的參數(shù)是一個函數(shù)

現(xiàn)在有一個新的需求,希望可以記錄下函數(shù)的執(zhí)行時間,于是在代碼中添加日志代碼:

import time
#遵守開放封閉原則
def foo():
  start = time.time()
  # print(start) # 1504698634.0291758從1970年1月1號到現(xiàn)在的秒數(shù),那年Unix誕生
  time.sleep(3)
  end = time.time()
  print('spend %s'%(end - start))
foo()

bar()、bar2()也有類似的需求,怎么做?再在bar函數(shù)里調(diào)用時間函數(shù)?這樣就造成大量雷同的代碼,為了減少重復(fù)寫代碼,我們可以這樣做,重新定義一個函數(shù):專門設(shè)定時間:

import time
def show_time(func):
  start_time=time.time()
  func()
  end_time=time.time()
  print('spend %s'%(end_time-start_time))
def foo():
  print('hello foo')
  time.sleep(3)
show_time(foo)

但是這樣的話,你基礎(chǔ)平臺的函數(shù)修改了名字,容易被業(yè)務(wù)線的人投訴的,因為我們每次都要將一個函數(shù)作為參數(shù)傳遞給show_time函數(shù)。而且這種方式已經(jīng)破壞了原有的代碼邏輯結(jié)構(gòu),之前執(zhí)行業(yè)務(wù)邏輯時,執(zhí)行運行foo(),但是現(xiàn)在不得不改成show_time(foo)。那么有沒有更好的方式的呢?當然有,答案就是裝飾器。

def show_time(f):
  def inner():
    start = time.time()
    f()
    end = time.time()
    print('spend %s'%(end - start))
  return inner
@show_time #foo=show_time(f)
def foo():
  print('foo...')
  time.sleep(1)
foo()
def bar():
  print('bar...')
  time.sleep(2)
bar()

輸出結(jié)果:

foo...
spend 1.0005607604980469
bar...

函數(shù)show_time就是裝飾器,它把真正的業(yè)務(wù)方法f包裹在函數(shù)里面,看起來像foo被上下時間函數(shù)裝飾了。在這個例子中,函數(shù)進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。

@符號是裝飾器的語法糖,在定義函數(shù)的時候使用,避免再一次賦值操作

裝飾器在Python使用如此方便都要歸因于Python的函數(shù)能像普通的對象一樣能作為參數(shù)傳遞給其他函數(shù),可以被賦值給其他變量,可以作為返回值,可以被定義在另外一個函數(shù)內(nèi)。

裝飾器有2個特性,一是可以把被裝飾的函數(shù)替換成其他函數(shù), 二是可以在加載模塊時候立即執(zhí)行

def decorate(func):
  print('running decorate', func)
  def decorate_inner():
    print('running decorate_inner function')
    return func()
  return decorate_inner
@decorate
def func_1():
  print('running func_1')
if __name__ == '__main__':
  print(func_1)
  #running decorate <function func_1 at 0x000001904743DEA0>
  # <function decorate.<locals>.decorate_inner at 0x000001904743DF28>
  func_1()
  #running decorate_inner function
  # running func_1

通過args 和 *kwargs 傳遞被修飾函數(shù)中的參數(shù)

def decorate(func):
  def decorate_inner(*args, **kwargs):
    print(type(args), type(kwargs))
    print('args', args, 'kwargs', kwargs)
    return func(*args, **kwargs)
  return decorate_inner
@decorate
def func_1(*args, **kwargs):
  print(args, kwargs)
if __name__ == '__main__':
  func_1('1', '2', '3', para_1='1', para_2='2', para_3='3')
#返回結(jié)果
#<class 'tuple'> <class 'dict'>
# args ('1', '2', '3') kwargs {'para_1': '1', 'para_2': '2', 'para_3': '3'}
# ('1', '2', '3') {'para_1': '1', 'para_2': '2', 'para_3': '3'}

帶參數(shù)的被裝飾函數(shù) 

import time
# 定長
def show_time(f):
  def inner(x,y):
    start = time.time()
    f(x,y)
    end = time.time()
    print('spend %s'%(end - start))
  return inner
@show_time
def add(a,b):
  print(a+b)
  time.sleep(1)
add(1,2)

不定長

import time
#不定長
def show_time(f):
  def inner(*x,**y):
    start = time.time()
    f(*x,**y)
    end = time.time()
    print('spend %s'%(end - start))
  return inner
@show_time
def add(*a,**b):
  sum=0
  for i in a:
    sum+=i
  print(sum)
  time.sleep(1)
add(1,2,3,4)

帶參數(shù)的裝飾器

在上面的裝飾器調(diào)用中,比如@show_time,該裝飾器唯一的參數(shù)就是執(zhí)行業(yè)務(wù)的函數(shù)。裝飾器的語法允許我們在調(diào)用時,提供其它參數(shù),比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。

import time
def time_logger(flag=0):
  def show_time(func):
    def wrapper(*args, **kwargs):
      start_time = time.time()
      func(*args, **kwargs)
      end_time = time.time()
      print('spend %s' % (end_time - start_time))
      if flag:
        print('將這個操作的時間記錄到日志中')
    return wrapper
  return show_time
@time_logger(flag=1)
def add(*args, **kwargs):
  time.sleep(1)
  sum = 0
  for i in args:
    sum += i
  print(sum)
add(1, 2, 5)

@time_logger(flag=1) 做了兩件事:

(1)time_logger(1):得到閉包函數(shù)show_time,里面保存環(huán)境變量flag

(2)@show_time   :add=show_time(add)

上面的time_logger是允許帶參數(shù)的裝飾器。它實際上是對原有裝飾器的一個函數(shù)封裝,并返回一個裝飾器(一個含有參數(shù)的閉包函數(shù))。當我 們使用@time_logger(1)調(diào)用的時候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。

疊放裝飾器

執(zhí)行順序是什么

如果一個函數(shù)被多個裝飾器修飾,其實應(yīng)該是該函數(shù)先被最里面的裝飾器修飾后(下面例子中函數(shù)main()先被inner裝飾,變成新的函數(shù)),變成另一個函數(shù)后,再次被裝飾器修飾

def outer(func):
  print('enter outer', func)
  def wrapper():
    print('running outer')
    func()
  return wrapper
def inner(func):
  print('enter inner', func)
  def wrapper():
    print('running inner')
    func()
  return wrapper
@outer
@inner
def main():
  print('running main')
if __name__ == '__main__':
  main()
#返回結(jié)果
# enter inner <function main at 0x000001A9F2BCDF28>
# enter outer <function inner.<locals>.wrapper at 0x000001A9F2BD5048>
# running outer
# running inner
# running main

類裝飾器

相比函數(shù)裝飾器,類裝飾器具有靈活度大、高內(nèi)聚、封裝性等優(yōu)點。使用類裝飾器還可以依靠類內(nèi)部的__call__方法,當使用 @ 形式將裝飾器附加到函數(shù)上時,就會調(diào)用此方法。

import time
class Foo(object):
  def __init__(self, func):
    self._func = func
  def __call__(self):
    start_time=time.time()
    self._func()
    end_time=time.time()
    print('spend %s'%(end_time-start_time))
@Foo #bar=Foo(bar)
def bar():
  print ('bar')
  time.sleep(2)
bar()  #bar=Foo(bar)()>>>>>>>沒有嵌套關(guān)系了,直接active Foo的 __call__方法

標準庫中有多種裝飾器

例如:裝飾方法的函數(shù)有property, classmethod, staticmethod; functools模塊中的lru_cache, singledispatch,  wraps 等等

from functools import lru_cache
from functools import singledispatch
from functools import wraps

functools.wraps使用裝飾器極大地復(fù)用了代碼,但是他有一個缺點就是原函數(shù)的元信息不見了,比如函數(shù)的docstring、__name__、參數(shù)列表,先看例子:

def foo():
  print("hello foo")
print(foo.__name__)# foo
def logged(func):
  def wrapper(*args, **kwargs):
    print (func.__name__ + " was called")
    return func(*args, **kwargs)
  return wrapper
@logged
def cal(x):
  resul=x + x * x
  print(resul)
cal(2)
#6
#cal was called
print(cal.__name__)# wrapper
print(cal.__doc__)#None
#函數(shù)f被wrapper取代了,當然它的docstring,__name__就是變成了wrapper函數(shù)的信息了。

好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數(shù)的元信息拷貝到裝飾器函數(shù)中,這使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息了。

from functools import wraps
def logged(func):
  @wraps(func)
  def wrapper(*args, **kwargs):
    print(func.__name__ + " was called")
    return func(*args, **kwargs)
  return wrapper
@logged
def cal(x):
  return x + x * x
print(cal.__name__) # cal

使用裝飾器會產(chǎn)生我們可能不希望出現(xiàn)的副作用, 例如:改變被修飾函數(shù)名稱,對于調(diào)試器或者對象序列化器等需要使用內(nèi)省機制的那些工具,可能會無法正常運行;

其實調(diào)用裝飾器后,會將同一個作用域中原來函數(shù)同名的那個變量(例如下面的func_1),重新賦值為裝飾器返回的對象;使用@wraps后,會把與內(nèi)部函數(shù)(被修飾函數(shù),例如下面的func_1)相關(guān)的重要元數(shù)據(jù)全部復(fù)制到外圍函數(shù)(例如下面的decorate_inner)

from functools import wraps
def decorate(func):
  print('running decorate', func)
  @wraps(func)
  def decorate_inner():
    print('running decorate_inner function', decorate_inner)
    return func()
  return decorate_inner
@decorate
def func_1():
  print('running func_1', func_1)
if __name__ == '__main__':
  func_1()
#輸出結(jié)果
#running decorate <function func_1 at 0x0000023E8DBD78C8>
# running decorate_inner function <function func_1 at 0x0000023E8DBD7950>
# running func_1 <function func_1 at 0x0000023E8DBD7950>

關(guān)于Python相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Python函數(shù)使用技巧總結(jié)》、《Python面向?qū)ο蟪绦蛟O(shè)計入門與進階教程》、《Python數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Python字符串操作技巧匯總》、《Python編碼操作技巧總結(jié)》及《Python入門與進階經(jīng)典教程

希望本文所述對大家Python程序設(shè)計有所幫助。

相關(guān)文章

  • Python如何使用type()函數(shù)查看數(shù)據(jù)的類型

    Python如何使用type()函數(shù)查看數(shù)據(jù)的類型

    這篇文章主要介紹了Python如何使用type()函數(shù)查看數(shù)據(jù)的類型,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • django+echart數(shù)據(jù)動態(tài)顯示的例子

    django+echart數(shù)據(jù)動態(tài)顯示的例子

    今天小編就為大家分享一篇django+echart數(shù)據(jù)動態(tài)顯示的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Python日志syslog使用原理詳解

    Python日志syslog使用原理詳解

    這篇文章主要介紹了Python日志syslog使用原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友可以參考下
    2020-02-02
  • django從后臺返回html代碼的實例

    django從后臺返回html代碼的實例

    這篇文章主要介紹了django從后臺返回html代碼的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03
  • Python字符串處理的8招秘籍(小結(jié))

    Python字符串處理的8招秘籍(小結(jié))

    這篇文章主要介紹了Python字符串處理的8招秘籍,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2019-08-08
  • python 返回列表中某個值的索引方法

    python 返回列表中某個值的索引方法

    今天小編就為大家分享一篇python 返回列表中某個值的索引方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-11-11
  • Python隊列Queue實現(xiàn)詳解

    Python隊列Queue實現(xiàn)詳解

    這篇文章主要介紹了Python隊列Queue實現(xiàn)詳解,隊列是一種列表,隊列用于存儲按順序排列的數(shù)據(jù),隊列是一種先進先出的數(shù)據(jù)結(jié)構(gòu),不同的是隊列只能在隊尾插入元素,在隊首刪除元素,需要的朋友可以參考下
    2023-07-07
  • python二元表達式用法

    python二元表達式用法

    今天小編就為大家分享一篇python二元表達式用法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-12-12
  • Python 隨機生成測試數(shù)據(jù)的模塊:faker基本使用方法詳解

    Python 隨機生成測試數(shù)據(jù)的模塊:faker基本使用方法詳解

    這篇文章主要介紹了Python 隨機生成測試數(shù)據(jù)的模塊:faker基本使用方法,結(jié)合實例形式詳細分析了Python 隨機生成測試數(shù)據(jù)的模塊faker基本功能、原理、使用方法及操作注意事項,需要的朋友可以參考下
    2020-04-04
  • python內(nèi)置函數(shù)sorted()用法深入分析

    python內(nèi)置函數(shù)sorted()用法深入分析

    這篇文章主要介紹了python內(nèi)置函數(shù)sorted()用法,結(jié)合實例形式較為深入的分析了Python內(nèi)置函數(shù)sorted()功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下
    2019-10-10

最新評論

镇坪县| 内江市| 新宾| 泌阳县| 乐昌市| 简阳市| 高碑店市| 太仆寺旗| 宽城| 招远市| 屯留县| 黔江区| 开远市| 泽库县| 炉霍县| 茌平县| 广灵县| 浪卡子县| 定边县| 邓州市| 湄潭县| 福安市| 黄冈市| 西吉县| 鄂托克前旗| 霍林郭勒市| 桑植县| 栾城县| 惠来县| 连云港市| 施甸县| 湖州市| 苗栗市| 克东县| 东乌珠穆沁旗| 珲春市| 北碚区| 兴安县| 虞城县| 瑞金市| 德州市|