<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何理解 Python 裝飾器?

          共 13572字,需瀏覽 28分鐘

           ·

          2021-12-02 11:11


          大家好,裝飾器是一個經(jīng)常被同學(xué)問起的概念。今天就給大家分享一篇對于裝飾器的講解文章。文章較長,涉及裝飾器的細(xì)節(jié)較多,值得收藏細(xì)讀。


          1.裝飾器簡介


          裝飾器(decorator)是一種高級Python語法。可以對一個函數(shù)、方法或者類進(jìn)行加工。


          在Python中,我們有多種方法對函數(shù)和類進(jìn)行加工,相對于其它方式,裝飾器語法簡單,代碼可讀性高。因此,裝飾器在Python項(xiàng)目中有廣泛的應(yīng)用。修飾器經(jīng)常被用于有切面需求的場景,較為經(jīng)典的有插入日志、性能測試、事務(wù)處理, Web權(quán)限校驗(yàn), Cache等。



          裝飾器的優(yōu)點(diǎn)是能夠抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。即,可以將函數(shù)“修飾”為完全不同的行為,可以有效的將業(yè)務(wù)邏輯正交分解。概括的講,裝飾器的作用就是為已經(jīng)存在的對象添加額外的功能。


          例如記錄日志,需要對某些函數(shù)進(jìn)行記錄。笨的辦法,每個函數(shù)加入代碼,如果代碼變了,就悲催了。裝飾器的辦法,定義一個專門日志記錄的裝飾器,對需要的函數(shù)進(jìn)行裝飾。



          Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名前面加一個@XXX注解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很讓人望而卻步,在使用它之前你需要了解一堆Annotation的類庫文檔,讓人感覺就是在學(xué)另外一門語言。


          而Python使用了一種相對于Decorator Pattern和Annotation來說非常優(yōu)雅的方法,這種方法不需要你去掌握什么復(fù)雜的OO模型或是Annotation的各種類庫規(guī)定,完全就是語言層面的玩法:一種函數(shù)式編程的技巧。


          2.裝飾器背后的原理


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


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


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

          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 shouttry:????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())
          #?每當(dāng)調(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
          #?那你該如何使用呢?#?先把函數(shù)賦值給一個變量talk = get_talk()
          #?你可以發(fā)現(xiàn)?"talk"?其實(shí)是一個函數(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!

          3.裝飾器實(shí)戰(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ù)前運(yùn)行的代碼放這里        print("Before the function runs")
          # 調(diào)用原始函數(shù)(需要帶括號) a_function_to_decorate()
          # 把想要在調(diào)用原始函數(shù)后運(yùn)行的代碼放這里 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ù)前運(yùn)行的代碼放這里        print("Before the function runs")
          # 調(diào)用原始函數(shù)(需要帶括號) a_function_to_decorate()
          # 把想要在調(diào)用原始函數(shù)后運(yùn)行的代碼放這里 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_decoratordef 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)
          裝飾器語法糖其實(shí)就是裝飾器模式的一個Python化的變體。為了方便開發(fā),
          Python已經(jīng)內(nèi)置了好幾種經(jīng)典的設(shè)計(jì)模式,比如迭代器(iterators)。當(dāng)然,你還
          可以堆積使用裝飾器:
          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@ingredientsdef 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@breaddef 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

          # 當(dāng)你調(diào)用裝飾器返回的函數(shù)式,你就在調(diào)用wrapper,而給wrapper的# 參數(shù)傳遞將會讓它把參數(shù)傳遞給要裝飾的函數(shù)@a_decorator_passing_argumentsdef 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,該裝飾器默認(rèn)它后面的函數(shù)是唯一的參數(shù)。裝飾器的語法允許我們調(diào)用decorator時,提供其它參數(shù),比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。

          # a new wrapper layerdef 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í)際上是對原有裝飾器的一個函數(shù)封裝,并返回一個裝飾器。我們可以將它理解為一個含有環(huán)境參量的閉包。當(dāng)我們使用@pre_str(‘^_^’)調(diào)用的時候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。該調(diào)用相當(dāng)于:

          square_sum = pre_str('^_^') (square_sum)

          裝飾“類中的方法”


          Python的一個偉大之處在于:方法和函數(shù)幾乎是一樣的(methods and functions are really the same),除了方法的第一個參數(shù)應(yīng)該是當(dā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?

          當(dāng)然,如果你想編寫一個非常通用的裝飾器,可以用來裝飾任意函數(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_argumentsdef 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_argumentsdef 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_argumentsdef 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?:# (<__main__.Mary object at 0xb7d303ac>,)# {}# 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

          @decoratorclass 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ù)了。


          4.內(nèi)置裝飾器


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


          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)用無需實(shí)例化,但同樣意味著它沒有 self 參數(shù),也無法訪問實(shí)例化后的對象。

          class XiaoMing:    @staticmethod    def say_hello():        print('同學(xué)你好')

          XiaoMing.say_hello()
          # 實(shí)例化調(diào)用也是同樣的效果# 有點(diǎn)多此一舉xiaoming = XiaoMing()xiaoming.say_hello()

          classmethod 裝飾器


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

          class XiaoMing:    name = '小明'
          @classmethod def say_hello(cls): print('同學(xué)你好, 我是' + cls.name) print(cls)
          XiaoMing.say_hello()

          5.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
          @decoratordef 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
          @decoratordef say_hello(): """doc of say hello""" print('同學(xué)你好')
          print(say_hello.__name__)print(say_hello.__doc__)

          6.裝飾器總結(jié)


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


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


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


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


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


          “裝飾器模式”是一個完全基于“面向?qū)ο蟆毖苌龅木幊淌址āK鼡碛袔讉€關(guān)鍵組成:一個統(tǒng)一的接口定義、若干個遵循該接口的類、類與類之間一層一層的包裝。

          最終由它們共同形成一種“裝飾”的效果。


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

          @log_time@cache_resultdef foo(): pass

          基本完全等同于:

          def?foo():?passfoo = log_time(cache_result(foo))

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


          參考鏈接:

          • Primer on Python Decorators
            https://realpython.com/primer-on-python-decorators/#more-real-world-examples
          • Decorator Basics:Python’s functions are objects
            https://stackoverflow.com/questions/739654/how-to-make-function-decorators-and-chain-them-together#answer-1594484


          兄弟倆都是院士!哥哥在南大,弟弟在復(fù)旦


          要在數(shù)據(jù)可視化中脫穎而出,你必須知道的 8 個圖表用法!


          深度盤點(diǎn):詳細(xì)介紹 Python 中的 7 種交叉驗(yàn)證方法!


          長按或掃描下方二維碼,后臺回復(fù):加群,可申請入群。一定要備注:入群+地點(diǎn)+學(xué)習(xí)/公司。例如:入群+上海+復(fù)旦。


          您的“點(diǎn)贊/在看/分享/互動”是就是最大支持!

          分享

          收藏

          點(diǎn)贊

          在看


          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色一级看 | 中文无码播放 | 大陆三级视频在线观看 | 看黄在线免费观看 | 91探花在线 |