溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python裝飾器怎么應(yīng)用

發(fā)布時間:2023-05-08 11:14:19 來源:億速云 閱讀:100 作者:zzz 欄目:編程語言

這篇文章主要介紹“Python裝飾器怎么應(yīng)用”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Python裝飾器怎么應(yīng)用”文章能幫助大家解決問題。

Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名前面加一個@XXX注解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很讓人望而卻步,在使用它之前你需要了解一堆Annotation的類庫文檔,讓人感覺就是在學(xué)另外一門語言。而Python使用了一種相對于Decorator Pattern和Annotation來說非常優(yōu)雅的方法,這種方法不需要你去掌握什么復(fù)雜的OO模型或是Annotation的各種類庫規(guī)定,完全就是語言層面的玩法:一種函數(shù)式編程的技巧。

裝飾器背后的原理

在Python中,裝飾器實現(xiàn)是十分方便。原因是:函數(shù)可以被扔來扔去。

Python的函數(shù)就是對象

要理解裝飾器,就必須先知道,在Python里,函數(shù)也是對象(functions are objects)。明白這一點非常重要,讓我們通過一個例子來看看為什么。

def shout(word="yes"):

**return** word.capitalize() + "!"

print(shout())

# outputs : 'Yes!'

# 作為一個對象,你可以像其他對象一樣把函數(shù)賦值給其他變量

scream = shout

# 注意我們沒有用括號:我們不是在調(diào)用函數(shù),

# 而是把函數(shù)'shout'的值綁定到'scream'這個變量上

# 這也意味著你可以通過'scream'這個變量來調(diào)用'shout'函數(shù)

print(scream())

# outputs : 'Yes!'

# 不僅如此,這也還意味著你可以把原來的名字'shout'刪掉,

# 而這個函數(shù)仍然可以通過'scream'來訪問

del shout

**try**:

print(shout())

**except** NameError as e:

print(e)

# outputs: "name 'shout' is not defined"

print(scream())

# outputs: 'Yes!'

Python 函數(shù)的另一個有趣的特性是,它們可以在另一個函數(shù)體內(nèi)定義。

def talk():

# 你可以在 'talk' 里動態(tài)的(on the fly)定義一個函數(shù)...

**def** whisper(word="yes"):

**return** word.lower() + "..."

# ... 然后馬上調(diào)用它!

print(whisper())

# 每當調(diào)用'talk',都會定義一次'whisper',然后'whisper'在'talk'里被調(diào)用

talk()

# outputs:

# "yes..."

# 但是"whisper" 在 "talk"外并不存在:

**try**:

print(whisper())

**except** NameError as e:

print(e)

# outputs : "name 'whisper' is not defined"
函數(shù)引用(Functions references)

你剛剛已經(jīng)知道了,Python的函數(shù)也是對象,因此:

  • 可以被賦值給變量

  • 可以在另一個函數(shù)體內(nèi)定義

那么,這樣就意味著一個函數(shù)可以返回另一個函數(shù):

def get_talk(type="shout"):

# 我們先動態(tài)定義一些函數(shù)

**def** shout(word="yes"):

**return** word.capitalize() + "!"

**def** whisper(word="yes"):

**return** word.lower() + "..."

# 然后返回其中一個

**if** type == "shout":

# 注意:我們是在返回函數(shù)對象,而不是調(diào)用函數(shù),所以不要用到括號 "()"

**return** shout

**else**:

**return** whisper

# 那你改如何使用d呢?

# 先把函數(shù)賦值給一個變量

talk = get_talk()

# 你可以發(fā)現(xiàn) "talk" 其實是一個函數(shù)對象:

print(talk)

# outputs :# 這個對象就是 get_talk 函數(shù)返回的:

print(talk())

# outputs : Yes!

# 你甚至還可以直接這樣使用:

print(get_talk("whisper")())

# outputs : yes...

既然可以返回一個函數(shù),那么也就可以像參數(shù)一樣傳遞:

def shout(word="yes"):

**return** word.capitalize() + "!"

scream = shout

**def** do_something_before(func):

print("I do something before then I call the function you gave me")

print(func())

do_something_before(scream)

# outputs:

# I do something before then I call the function you gave me

# Yes!

裝飾器實戰(zhàn)

現(xiàn)在已經(jīng)具備了理解裝飾器的所有基礎(chǔ)知識了。裝飾器也就是一種包裝材料,它們可以讓你在執(zhí)行被裝飾的函數(shù)之前或之后執(zhí)行其他代碼,而且不需要修改函數(shù)本身。

手工制作的裝飾器
# 一個裝飾器是一個需要另一個函數(shù)作為參數(shù)的函數(shù)

**def** my_shiny_new_decorator(a_function_to_decorate):

# 在裝飾器內(nèi)部動態(tài)定義一個函數(shù):wrapper(原意:包裝紙).

# 這個函數(shù)將被包裝在原始函數(shù)的四周

# 因此就可以在原始函數(shù)之前和之后執(zhí)行一些代碼.

**def** the_wrapper_around_the_original_function():

# 把想要在調(diào)用原始函數(shù)前運行的代碼放這里

print("Before the function runs")

# 調(diào)用原始函數(shù)(需要帶括號)

a_function_to_decorate()

# 把想要在調(diào)用原始函數(shù)后運行的代碼放這里

print("After the function runs")

# 直到現(xiàn)在,"a_function_to_decorate"還沒有執(zhí)行過 (HAS NEVER BEEN EXECUTED).

# 我們把剛剛創(chuàng)建的 wrapper 函數(shù)返回.

# wrapper 函數(shù)包含了這個函數(shù),還有一些需要提前后之后執(zhí)行的代碼,

# 可以直接使用了(It's ready to use!)

**return** the_wrapper_around_the_original_function

# Now imagine you create a function you don't want to ever touch again.

**def** a_stand_alone_function():

print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function()

# outputs: I am a stand alone function, don't you dare modify me

# 現(xiàn)在,你可以裝飾一下來修改它的行為.

# 只要簡單的把它傳遞給裝飾器,后者能用任何你想要的代碼動態(tài)的包裝

# 而且返回一個可以直接使用的新函數(shù):

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)

a_stand_alone_function_decorated()

# outputs:

# Before the function runs

# I am a stand alone function, don't you dare modify me

# After the function runs
裝飾器的語法糖

我們用裝飾器的語法來重寫一下前面的例子:

# 一個裝飾器是一個需要另一個函數(shù)作為參數(shù)的函數(shù)

**def** my_shiny_new_decorator(a_function_to_decorate):

# 在裝飾器內(nèi)部動態(tài)定義一個函數(shù):wrapper(原意:包裝紙).

# 這個函數(shù)將被包裝在原始函數(shù)的四周

# 因此就可以在原始函數(shù)之前和之后執(zhí)行一些代碼.

**def** the_wrapper_around_the_original_function():

# 把想要在調(diào)用原始函數(shù)前運行的代碼放這里

print("Before the function runs")

# 調(diào)用原始函數(shù)(需要帶括號)

a_function_to_decorate()

# 把想要在調(diào)用原始函數(shù)后運行的代碼放這里

print("After the function runs")

# 直到現(xiàn)在,"a_function_to_decorate"還沒有執(zhí)行過 (HAS NEVER BEEN EXECUTED).

# 我們把剛剛創(chuàng)建的 wrapper 函數(shù)返回.

# wrapper 函數(shù)包含了這個函數(shù),還有一些需要提前后之后執(zhí)行的代碼,

# 可以直接使用了(It's ready to use!)

**return** the_wrapper_around_the_original_function

@my_shiny_new_decorator

**def** another_stand_alone_function():

print("Leave me alone")

another_stand_alone_function()

# outputs:

# Before the function runs

# Leave me alone

# After the function runs

是的,這就完了,就這么簡單。@decorator 只是下面這條語句的簡寫(shortcut):

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

裝飾器語法糖其實就是裝飾器模式的一個Python化的變體。為了方便開發(fā),Python已經(jīng)內(nèi)置了好幾種經(jīng)典的設(shè)計模式,比如迭代器(iterators)。當然,你還可以堆積使用裝飾器:

def bread(func):

**def** wrapper():

print("")

func()

print("")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

**def** sandwich(food="--ham--"):

print(food)

sandwich()

# outputs: --ham--

sandwich = bread(ingredients(sandwich))

sandwich()

# outputs:

# 

# #tomatoes#

# --ham--

# ~salad~

#

用Python的裝飾器語法表示:

def bread(func):

**def** wrapper():

print("")

func()

print("")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

@bread

@ingredients

**def** sandwich(food="--ham--"):

print(food)

sandwich()

# outputs:

# 

# #tomatoes#

# --ham--

# ~salad~

#

裝飾器放置的順序也很重要:

def bread(func):

**def** wrapper():

print("")

func()

print("")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

@ingredients

@bread

**def** strange_sandwich(food="--ham--"):

print(food)

strange_sandwich()

# outputs:

##tomatoes#

# 

# --ham--

## ~salad~
給裝飾器函數(shù)傳參
# 這不是什么黑色魔法(black magic),你只是必須讓wrapper傳遞參數(shù):

**def** a_decorator_passing_arguments(function_to_decorate):

**def** a_wrapper_accepting_arguments(arg1, arg2):

print("I got args! Look:", arg1, arg2)

function_to_decorate(arg1, arg2)

**return** a_wrapper_accepting_arguments

# 當你調(diào)用裝飾器返回的函數(shù)式,你就在調(diào)用wrapper,而給wrapper的

# 參數(shù)傳遞將會讓它把參數(shù)傳遞給要裝飾的函數(shù)

@a_decorator_passing_arguments

**def** print_full_name(first_name, last_name):

print("My name is", first_name, last_name)

print_full_name("Peter", "Venkman")

# outputs:

# I got args! Look: Peter Venkman

# My name is Peter Venkman
含參數(shù)的裝飾器

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

# a new wrapper layer

**def** pre_str(pre=''):

# old decorator

**def** decorator(F):

**def** new_F(a, b):

print(pre + " input", a, b)

**return** F(a, b)

**return** new_F

**return** decorator

# get square sum

@pre_str('^_^')

**def** square_sum(a, b):

**return** a ** 2 + b ** 2

# get square diff

@pre_str('T_T')

**def** square_diff(a, b):

**return** a ** 2 - b ** 2

print(square_sum(3, 4))

print(square_diff(3, 4))

# outputs:

# ('^_^ input', 3, 4)

# 25

# ('T_T input', 3, 4)

# -7

上面的pre_str是允許參數(shù)的裝飾器。它實際上是對原有裝飾器的一個函數(shù)封裝,并返回一個裝飾器。我們可以將它理解為一個含有環(huán)境參量的閉包。當我們使用@pre_str(‘^_^’)調(diào)用的時候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。該調(diào)用相當于:

square_sum = pre_str('^_^') (square_sum)
裝飾“類中的方法”

Python的一個偉大之處在于:方法和函數(shù)幾乎是一樣的(methods and functions are really the same),除了方法的第一個參數(shù)應(yīng)該是當前對象的引用(也就是 self)。這也就意味著只要記住把 self 考慮在內(nèi),你就可以用同樣的方法給方法創(chuàng)建裝飾器:

def method_friendly_decorator(method_to_decorate):

**def** wrapper(self, lie):

lie = lie - 3# very friendly, decrease age even more :-)

**return** method_to_decorate(self, lie)

**return** wrapper

**class** Lucy(object):

**def** __init__(self):

self.age = 32

@method_friendly_decorator

**def** say_your_age(self, lie):

print("I am %s, what did you think?" % (self.age + lie))

l = Lucy()

l.say_your_age(-3)

# outputs: I am 26, what did you think?

當然,如果你想編寫一個非常通用的裝飾器,可以用來裝飾任意函數(shù)和方法,你就可以無視具體參數(shù)了,直接使用 *args, **kwargs 就行:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):

# The wrapper accepts any arguments

**def** a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):

print("Do I have args?:")

print(args)

print(kwargs)

# Then you unpack the arguments, here *args, **kwargs

# If you are not familiar with unpacking, check:

# http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/

function_to_decorate(*args, **kwargs)

**return** a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments

**def** function_with_no_argument():

print("Python is cool, no argument here.")

function_with_no_argument()

# outputs

# Do I have args?:

# ()

# {}

# Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments

**def** function_with_arguments(a, b, c):

print(a, b, c)

function_with_arguments(1, 2, 3)

# outputs

# Do I have args?:

# (1, 2, 3)

# {}

# 1 2 3

@a_decorator_passing_arbitrary_arguments

**def** function_with_named_arguments(a, b, c, platypus="Why not ?"):

print("Do %s, %s and %s like platypus? %s" % (a, b, c, platypus))

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")

# outputs

# Do I have args ? :

# ('Bill', 'Linus', 'Steve')

# {'platypus': 'Indeed!'}

# Do Bill, Linus and Steve like platypus? Indeed!

**class** Mary(object):

**def** __init__(self):

self.age = 31

@a_decorator_passing_arbitrary_arguments

**def** say_your_age(self, lie=-3):# You can now add a default value

print("I am %s, what did you think ?" % (self.age + lie))

m = Mary()

m.say_your_age()

# outputs

# Do I have args?:

# (,)

# {}

# I am 28, what did you think?
裝飾類

在上面的例子中,裝飾器接收一個函數(shù),并返回一個函數(shù),從而起到加工函數(shù)的效果。在Python 2.6以后,裝飾器被拓展到類。一個裝飾器可以接收一個類,并返回一個類,從而起到加工類的效果。

def decorator(aClass):

**class** newClass:

**def** __init__(self, age):

self.total_display = 0

self.wrapped = aClass(age)

**def** display(self):

self.total_display += 1

print("total display", self.total_display)

self.wrapped.display()

**return** newClass

@decorator

**class** Bird:

**def** __init__(self, age):

self.age = age

**def** display(self):

print("My age is", self.age)

eagleLord = Bird(5)

**for** i **in** range(3):

eagleLord.display()

在decorator中,我們返回了一個新類newClass。在新類中,我們記錄了原來類生成的對象(self.wrapped),并附加了新的屬性total_display,用于記錄調(diào)用display的次數(shù)。我們也同時更改了display方法。通過修改,我們的Bird類可以顯示調(diào)用display的次數(shù)了。

內(nèi)置裝飾器

Python中有三種我們經(jīng)常會用到的裝飾器, property、 staticmethod、 classmethod,他們有個共同點,都是作用于類方法之上。

property 裝飾器

property 裝飾器用于類中的函數(shù),使得我們可以像訪問屬性一樣來獲取一個函數(shù)的返回值。

class XiaoMing:

first_name = '明'

last_name = '小'

@property

**def** full_name(self):

**return** self.last_name + self.first_name

xiaoming = XiaoMing()

print(xiaoming.full_name)

例子中我們像獲取屬性一樣獲取 full_name 方法的返回值,這就是用 property 裝飾器的意義,既能像屬性一樣獲取值,又可以在獲取值的時候做一些操作。

staticmethod 裝飾器

staticmethod 裝飾器同樣是用于類中的方法,這表示這個方法將會是一個靜態(tài)方法,意味著該方法可以直接被調(diào)用無需實例化,但同樣意味著它沒有 self 參數(shù),也無法訪問實例化后的對象。

class XiaoMing:

@staticmethod

**def** say_hello():

print('同學(xué)你好')

XiaoMing.say_hello()

# 實例化調(diào)用也是同樣的效果

# 有點多此一舉

xiaoming = XiaoMing()

xiaoming.say_hello()
classmethod 裝飾器

classmethod 依舊是用于類中的方法,這表示這個方法將會是一個類方法,意味著該方法可以直接被調(diào)用無需實例化,但同樣意味著它沒有 self 參數(shù),也無法訪問實例化后的對象。相對于 staticmethod 的區(qū)別在于它會接收一個指向類本身的 cls 參數(shù)。

class XiaoMing:

name = '小明'

@classmethod

**def** say_hello(cls):

print('同學(xué)你好, 我是' + cls.name)

print(cls)

XiaoMing.say_hello()

wraps 裝飾器

一個函數(shù)不止有他的執(zhí)行語句,還有著 name(函數(shù)名),doc (說明文檔)等屬性,我們之前的例子會導(dǎo)致這些屬性改變。

def decorator(func):

**def** wrapper(*args, **kwargs):

"""doc of wrapper"""

print('123')

**return** func(*args, **kwargs)

**return** wrapper

@decorator

**def** say_hello():

"""doc of say hello"""

print('同學(xué)你好')

print(say_hello.__name__)

print(say_hello.__doc__)

由于裝飾器返回了 wrapper 函數(shù)替換掉了之前的 say_hello 函數(shù),導(dǎo)致函數(shù)名,幫助文檔變成了 wrapper 函數(shù)的了。解決這一問題的辦法是通過 functools 模塊下的 wraps 裝飾器。

from functools import wraps

**def** decorator(func):

@wraps(func)

**def** wrapper(*args, **kwargs):

"""doc of wrapper"""

print('123')

**return** func(*args, **kwargs)

**return** wrapper

@decorator

**def** say_hello():

"""doc of say hello"""

print('同學(xué)你好')

print(say_hello.__name__)

print(say_hello.__doc__)

裝飾器總結(jié)

裝飾器的核心作用是name binding。這種語法是Python多編程范式的又一個體現(xiàn)。大部分Python用戶都不怎么需要定義裝飾器,但有可能會使用裝飾器。鑒于裝飾器在Python項目中的廣泛使用,了解這一語法是非常有益的。

常見錯誤:“裝飾器”=“裝飾器模式”

設(shè)計模式是一個在計算機世界里鼎鼎大名的詞。假如你是一名 Java 程序員,而你一點設(shè)計模式都不懂,那么我打賭你找工作的面試過程一定會度過的相當艱難。

但寫 Python 時,我們極少談起“設(shè)計模式”。雖然 Python 也是一門支持面向?qū)ο蟮木幊陶Z言,但它的鴨子類型設(shè)計以及出色的動態(tài)特性決定了,大部分設(shè)計模式對我們來說并不是必需品。所以,很多 Python 程序員在工作很長一段時間后,可能并沒有真正應(yīng)用過幾種設(shè)計模式。

不過裝飾器模式(Decorator Pattern)是個例外。因為 Python 的“裝飾器”和“裝飾器模式”有著一模一樣的名字,我不止一次聽到有人把它們倆當成一回事,認為使用“裝飾器”就是在實踐“裝飾器模式”。但事實上,它們是兩個完全不同的東西。

“裝飾器模式”是一個完全基于“面向?qū)ο蟆毖苌龅木幊淌址?。它擁有幾個關(guān)鍵組成:一個統(tǒng)一的接口定義、若干個遵循該接口的類、類與類之間一層一層的包裝。最終由它們共同形成一種“裝飾”的效果。

而 Python 里的“裝飾器”和“面向?qū)ο蟆睕]有任何直接聯(lián)系,**它完全可以只是發(fā)生在函數(shù)和函數(shù)間的把戲。事實上,“裝飾器”并沒有提供某種無法替代的功能,它僅僅就是一顆“語法糖”而已。下面這段使用了裝飾器的代碼:

@log_time

@cache_result

**def** foo(): pass

基本完全等同于:

def foo(): pass

foo = log_time(cache_result(foo))

裝飾器最大的功勞,在于讓我們在某些特定場景時,可以寫出更符合直覺、易于閱讀的代碼。它只是一顆“糖”,并不是某個面向?qū)ο箢I(lǐng)域的復(fù)雜編程模式。

關(guān)于“Python裝飾器怎么應(yīng)用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI