Pyton裝飾器基礎

原鏈接 https://www.jianshu.com/p/1ae551fb17cd/

Pyton裝飾器基礎

在Python中,函數也是對象

爲了理解裝飾器,你必須首先理解,在Python中函數也是對象。
理解這個知識點很重要。讓我們使用一個簡單的例子來說明一下:

def shout(word="yes"):
    return word.capitalize()+"!"

print shout()
# 輸出爲: ‘Yes!’

# 函數作爲一個對象,你可以像其他對象一樣,把它賦值給其他的變量
scream = shout

# 注意我們沒有使用圓括號:我們不是調用這個函數,我們把"shout"這個函數賦值給變量"scream"
# 那意味着你之後可以使用"scream"來調用"shout"這個函數
print scream()
# 輸出爲: ‘Yes!’

# 不僅如此,那意味着你可以移除’shout’這個老的名稱,但這個函數仍然可以通過’scream’訪問
del shout
try:
print shout()
except NameError, e:
print e
# 輸出爲: “name ‘shout’ is not defined”

print scream()
# 輸出爲: ‘Yes!’

好了,在心裏記住這個知識點。我們之後很快要用到它。

Python中函數還有另一個有趣的特性,那就是它可以在其他函數裏面定義!

def talk():
<span class="hljs-comment"># 你可以在"talk"函數中定義一個函數...</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">whisper</span><span class="hljs-params">(word=<span class="hljs-string">"yes"</span>)</span>:</span>
    <span class="hljs-keyword">return</span> word.lower()+<span class="hljs-string">"..."</span>

 <span class="hljs-comment"># ...並且你可以馬上使用這個函數</span>
<span class="hljs-keyword">print</span> whisper()

# 你每次調用"talk"這個函數的時候,它會定義一個"whisper"函數,之後這個"whisper"將在"talk"裏面被調用
talk()
# 輸出爲:“yes…”

# 但是在"talk"這個函數的作用域之外,"whisper"這個函數是不存在的
try:
print whisper()
except NameError, e:
print e
# 輸出爲: “name ‘whisper’ is not defined”*

函數的引用

Okay,就這些東西嗎?有趣的部分該上場了...
你已經看見,函數是對象。因此,函數:

  • 可以賦值給其他變量
  • 可以在其它函數裏面定義

那意味着一個函數可以被另一個函數return。我們來看個例子! ☺

def getTalk(kind="shout"):
<span class="hljs-comment"># 我們定義了一些函數</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">shout</span><span class="hljs-params">(word=<span class="hljs-string">"yes"</span>)</span>:</span>
    <span class="hljs-keyword">return</span> word.capitalize()+<span class="hljs-string">"!"</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">whisper</span><span class="hljs-params">(word=<span class="hljs-string">"yes"</span>)</span> :</span>
    <span class="hljs-keyword">return</span> word.lower()+<span class="hljs-string">"..."</span>;

<span class="hljs-comment"># 然後我們返回他們中的一個</span>
<span class="hljs-keyword">if</span> kind == <span class="hljs-string">"shout"</span>:
    <span class="hljs-comment"># 我們沒有用"()", 我們不是要調用這個函數</span>
    <span class="hljs-comment"># 我們返回了這個函數對象</span>
    <span class="hljs-keyword">return</span> shout  
<span class="hljs-keyword">else</span>:
    <span class="hljs-keyword">return</span> whisper

# 我們怎麼使用它呢?
# 獲取函數,並將它賦值給一個變量
talk = getTalk()

# 你可以看到在這裏"talk"是一個函數對象:
print talk
# 輸出爲: <function shout at 0xb7ea817c>

# 這個就是被函數返回的對象
print talk()
# 輸出爲: Yes!

# 你甚至可以直接使用它:
print getTalk(“whisper”)()
# 輸出爲: yes…

等等...這裏有我們沒有注意到的地方!

既然你可以return一個函數,你就可以把一個函數當參數傳遞:

def doSomethingBefore(func): 
    print "I do something before then I call the function you gave me"
    print func()

doSomethingBefore(scream)
# 輸出爲:
#I do something before then I call the function you gave me
#Yes!

好了,你已經具備了理解裝飾器的所有知識點。你知道,裝飾器就是 "封裝", 這意味着它可以讓你在被它裝飾的函數前面和後面執行一些代碼,而不必改動被裝飾的函數本身。

手動創建裝飾器

你如何手動構建一個裝飾器:

# 裝飾是一個函數,該函數需要另一個函數作爲它的參數
def my_shiny_new_decorator(a_function_to_decorate):
<span class="hljs-comment"># 在裝飾器的函數實現裏面它定義了另一個函數: 他就是封裝函數(wrapper)</span>
<span class="hljs-comment"># 這個函數將原來的函數封裝到裏面</span>
<span class="hljs-comment"># 因此你可以在原來函數的前面和後面執行一些附加代碼</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">the_wrapper_around_the_original_function</span><span class="hljs-params">()</span>:</span>

    <span class="hljs-comment"># 在這裏放置你想在原來函數執行前執行的代碼</span>
    <span class="hljs-keyword">print</span> <span class="hljs-string">"Before the function runs"</span>

    <span class="hljs-comment"># 調用原來的函數(使用圓括號)</span>
    a_function_to_decorate()

    <span class="hljs-comment"># 在這裏放置你想在原來函數執行後執行的代碼</span>
    <span class="hljs-keyword">print</span> <span class="hljs-string">"After the function runs"</span>

<span class="hljs-comment"># 這個時候,"a_function_to_decorate"並沒有執行</span>
<span class="hljs-comment"># 我們返回剛纔創建的封裝函數</span>
<span class="hljs-comment"># 這個封裝函數包含了原來的函數,和將在原來函數前面和後面執行的代碼。我們就可以使用它了!</span>
<span class="hljs-keyword">return</span> the_wrapper_around_the_original_function

# 想象你創建了一個你再也不想修改的函數
def a_stand_alone_function():
print “I am a stand alone function, don’t you dare modify me”

a_stand_alone_function()
# 輸出爲: I am a stand alone function, don’t you dare modify me

# 現在你可以裝飾這個函數來擴展它的行爲
# 只需要將這個函數傳入裝飾器,那它將被動態的包在任何你想執行的代碼間,並且返回一個可被使用的新函數:
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#輸出爲:
#Before the function runs
#I am a stand alone function, don’t you dare modify me
#After the function runs

現在,你可能想在每次調用 a_stand_alone_function的時候,真正被執行的函數是 a_stand_alone_function_decorated。那很容易,只需要使用 my_shiny_new_decorator返回的函數賦值給原來的 a_stand_alone_function這個函數名(其實是個變量):

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#輸出爲:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs

# 你猜怎麼着?這就是裝飾器做的事情。

裝飾器揭祕

前面的例子,使用Python的裝飾器語法糖來重寫就是下面的樣子:

@my_shiny_new_decorator
def another_stand_alone_function():
    print "Leave me alone"

another_stand_alone_function()
# 輸出爲:
#Before the function runs
#Leave me alone
#After the function runs

是的,這就是全部,就是這麼簡單。@decorator 只是下面表達式的簡寫:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

這裏的裝飾器只是裝飾器設計模式的一種Python化變體。Python嵌入了多種經典的設計模式來簡化開發(比如迭代器(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()
# 輸出爲: --ham–
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</’’’’’’&gt;
# #tomatoes#
# --ham–
# salad
#<______/>

使用Python的裝飾器語法糖:

@bread
@ingredients
def sandwich(food="--ham--"):
    print food

sandwich()
#outputs:
#</’’’’’’&gt;
# #tomatoes#
# --ham–
# salad
#<______/>

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

@ingredients
@bread
def strange_sandwich(food="--ham--"):
    print food

strange_sandwich()
#outputs:
##tomatoes#
#</’’’’’’&gt;
# --ham–
#<______/>
# salad


現在: 回答問題(請參考stack overflorw上的相關問題)

作爲結論,你可以很容易看出如何回答問題:

# 使其變bold的裝飾器
def makebold(fn):
    # 裝飾器將要返回的函數
    def wrapper():
        # 在原函數前面和後面插入一些代碼
        return "<b>" + fn() + "</b>"
    return wrapper

# 使其變italic的裝飾器
def makeitalic(fn):
# 裝飾器將要返回的函數
def wrapper():
# 在原函數前面和後面插入一些代碼
return “<i>” + fn() + “</i>”
return wrapper

@makebold
@makeitalic
def say():
return “hello”

print say()
# 輸出爲: <b><i>hello</i></b>

# 這和下面代碼效果相同
def say():
return “hello”
say = makebold(makeitalic(say))

print say()
# 輸出爲: <b><i>hello</i></b>

你可以高興的離開這裏了,或者再費點腦子來看看裝飾器的高級用法。


更深入的討論裝飾器

向被裝飾的函數傳參數

# 這不是黑魔法,你只需要讓封裝函數傳遞這些參數:

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

# 因爲當你調用被裝飾器返回的函數時,實際你是在調用封裝函數
# 所以向封裝函數傳遞參數可以讓封裝函數把參數傳遞給被裝飾的函數

@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”)
# 輸出爲:
# I got args! Look: Peter Venkman
# My name is Peter Venkman

裝飾方法

Python中方法和函數幾乎是一樣的,這個特性很nice。唯一的不同是方法期望它的第一個參數是對當前對象的引用(self)。

那意味着你可以使用相同的方式來給方法添加裝飾器!只是需要將self考慮在內:

def method_friendly_decorator(method_to_decorate):
    def wrapper(self, lie):
        lie = lie - 3 # 很友好吧,再次減少了年齡 :-)
        return method_to_decorate(self, lie)
    return wrapper

class Lucy(object):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
    <span class="hljs-keyword">self</span>.age = <span class="hljs-number">32</span>

@method_friendly_decorator
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sayYourAge</span><span class="hljs-params">(<span class="hljs-keyword">self</span>, lie)</span></span>:
    print <span class="hljs-string">"I am %s, what did you think?"</span> % (<span class="hljs-keyword">self</span>.age + lie)

l = Lucy()
l.sayYourAge(-3)
# 輸出爲: I am 26, what did you think?

如果你在寫一個通用的裝飾器--可以接收任何參數的函數或者方法--這時候只需要使用 *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    # 封裝函數可以接收任何的參數
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print "Do I have args?:"
        print args
        print kwargs
        # 然後你解包出參數,這裏是 *args, **kwargs 
        # 如果你不熟悉怎麼解包,可以查看:
        # 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()
#輸出爲:
#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)
# 輸出爲:
#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!”)
# 輸出爲:
#Do I have args ? :
#(‘Bill’, ‘Linus’, ‘Steve’)
#{‘platypus’: ‘Indeed!’}
#Do Bill, Linus and Steve like platypus? Indeed!

class Mary(object):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span>
    self.age = <span class="hljs-number">31</span>

@a_decorator_passing_arbitrary_arguments
def sayYourAge(self, lie=-3): # 這時候你可以添加一個默認參數值
print “I am %s, what did you think ?” % (self.age + lie)

m = Mary()
m.sayYourAge()
# 輸出爲:
# Do I have args?:
#(<main.Mary object at 0xb7d303ac>,)
#{}
#I am 28, what did you think?

給裝飾器傳遞參數

好了,現在你覺得給裝飾器本身傳遞參數該怎麼做呢?

這個可能有點繞,因爲裝飾器必須接收一個函數作爲參數。
因此,你不能把被裝飾函數的參數直接傳遞給裝飾器。

在我們說出解決辦法前,寫點代碼來找找靈感:

# 裝飾器只是普通的函數
def my_decorator(func):
    print "I am an ordinary function"
    def wrapper():
        print "I am function returned by the decorator"
        func()
    return wrapper

# 因此,你可以不使用任何的 “@” 就可以調用它

def lazy_function():
print “zzzzzzzz”

decorated_function = my_decorator(lazy_function)
# 輸出爲: I am an ordinary function

# 它輸出 “I am an ordinary function”,因爲那就是你在代碼裏面做的事情:
# 調用一個函數,沒有任何的魔法。

@my_decorator
def lazy_function():
print “zzzzzzzz”

# 輸出爲: I am an ordinary function

上面兩種方式幾乎一樣。"my_decorator"被調用。因此當你在代碼裏面添加 @my_decorato時,你就在告訴Python去調用'被"my_decorator"變量標示的函數'。

這很重要! 你給出的這個變量名可以直接指向裝飾器-也可以不直接指向

我們來乾點邪惡的事情。 ☺

def decorator_maker():
<span class="hljs-keyword">print</span> <span class="hljs-string">"I make decorators! I am executed only once: "</span>+\
      <span class="hljs-string">"when you make me create a decorator."</span>
        
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_decorator</span><span class="hljs-params">(func)</span>:</span>
    
    <span class="hljs-keyword">print</span> <span class="hljs-string">"I am a decorator! I am executed only when you decorate a function."</span>
           
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">wrapped</span><span class="hljs-params">()</span>:</span>
        <span class="hljs-keyword">print</span> (<span class="hljs-string">"I am the wrapper around the decorated function. "</span>
              <span class="hljs-string">"I am called when you call the decorated function. "</span>
              <span class="hljs-string">"As the wrapper, I return the RESULT of the decorated function."</span>)
        <span class="hljs-keyword">return</span> func()
    
    <span class="hljs-keyword">print</span> <span class="hljs-string">"As the decorator, I return the wrapped function."</span>
    
    <span class="hljs-keyword">return</span> wrapped

<span class="hljs-keyword">print</span> <span class="hljs-string">"As a decorator maker, I return a decorator"</span>
<span class="hljs-keyword">return</span> my_decorator

# 我們創建了一個裝飾器。它就只是一個新的函數。
new_decorator = decorator_maker()
# 輸出爲:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator

# 然後我們裝飾一個函數
def decorated_function():
print “I am the decorated function.”

decorated_function = new_decorator(decorated_function)
# 輸出爲:
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function

# 我們調用這個函數:
decorated_function()
# 輸出爲:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

這裏沒有任何驚奇的地方。

讓我們再次來做相同的事情,但是省略掉所有討厭的中間變量:

def decorated_function():
    print "I am the decorated function."
decorated_function = decorator_maker()(decorated_function)
# 輸出爲:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最後:
decorated_function()
# 輸出爲:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

讓我們使它更簡潔:

@decorator_maker()
def decorated_function():
    print "I am the decorated function."
# 輸出爲:
#I make decorators! I am executed only once: when you make me create a decorator.
#As a decorator maker, I return a decorator
#I am a decorator! I am executed only when you decorate a function.
#As the decorator, I return the wrapped function.

# 最終:
decorated_function()
# 輸出爲:
#I am the wrapper around the decorated function. I am called when you call the decorated function.
#As the wrapper, I return the RESULT of the decorated function.
#I am the decorated function.

Hey,你注意到了嗎?我們除了 "@"格式的語法糖外還使用了函數調用! :-)

因此,回到帶參數裝飾器的討論。如果我們可以使用函數來創建裝飾器,我們就可以把參數傳遞給那個函數,對吧?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
<span class="hljs-keyword">print</span> <span class="hljs-string">"I make decorators! And I accept arguments:"</span>, decorator_arg1, decorator_arg2
        
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_decorator</span><span class="hljs-params">(func)</span>:</span>
    <span class="hljs-comment"># 這裏之所有可以傳遞參數,得益於closures的特性。  </span>
    <span class="hljs-comment"># 如果你不熟悉closures,你可以假設這是沒問題的,</span>
    <span class="hljs-comment"># 或者讀一下: http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python  </span>
    
    <span class="hljs-keyword">print</span> <span class="hljs-string">"I am the decorator. Somehow you passed me arguments:"</span>, decorator_arg1, decorator_arg2
           
    <span class="hljs-comment"># 不要把裝飾器的參數和函數的參數搞混</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">wrapped</span><span class="hljs-params">(function_arg1, function_arg2)</span> :</span>
        <span class="hljs-keyword">print</span> (<span class="hljs-string">"I am the wrapper around the decorated function.\n"</span>
              <span class="hljs-string">"I can access all the variables\n"</span>
              <span class="hljs-string">"\t- from the decorator: {0} {1}\n"</span>
              <span class="hljs-string">"\t- from the function call: {2} {3}\n"</span>
              <span class="hljs-string">"Then I can pass them to the decorated function"</span>
              .format(decorator_arg1, decorator_arg2,
                      function_arg1, function_arg2))
        <span class="hljs-keyword">return</span> func(function_arg1, function_arg2)
    
    <span class="hljs-keyword">return</span> wrapped

<span class="hljs-keyword">return</span> my_decorator

@decorator_maker_with_arguments(“Leonard”, “Sheldon”)
def decorated_function_with_arguments(function_arg1, function_arg2):
print (“I am the decorated function and only knows about my arguments: {0}”
" {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(“Rajesh”, “Howard”)
# 輸出爲:
#I make decorators! And I accept arguments: Leonard Sheldon
#I am the decorator. Somehow you passed me arguments: Leonard Sheldon
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Sheldon
# - from the function call: Rajesh Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Rajesh Howard

這就是它了:帶參數的裝飾器。參數可以使用變量來設定

c1 = "Penny"
c2 = "Leslie"

@decorator_maker_with_arguments(“Leonard”, c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
print (“I am the decorated function and only knows about my arguments:”
" {0} {1}".format(function_arg1, function_arg2))

decorated_function_with_arguments(c2, “Howard”)
# 輸出爲:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function.
#I can access all the variables
# - from the decorator: Leonard Penny
# - from the function call: Leslie Howard
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie Howard

正如你看到的那樣,你可以使用這個技巧像給函數傳遞參數一樣給裝飾器傳遞參數。如果你想,你甚至可以使用 *args, **kwargs。但是記住裝飾器只會被調用一次。僅僅當Python載入(imports)這個腳本的時候調用。之後你不可以動態的設定參數。當你 "import x"的時候, 這個函數已經被裝飾了,因此你不能再改變任何東西了。



讓我們來練習一下:裝飾一個裝飾器

好了,作爲福利,我將給你一個代碼片段,它可以讓裝飾器接收任何參數。
爲了能接收參數,我們使用另一個函數來創建我們的裝飾器。

我們封裝了裝飾器。

我們最近知道的能封裝函數的東西是什麼?
對,就是裝飾器

讓我們來寫一個裝飾裝飾器的裝飾器來玩玩(夠繞吧):

def decorator_with_args(decorator_to_enhance): 
    """  
    這個函數被當做裝飾器來使用。  
    它必須裝飾另一個函數,這個函數也被當做裝飾器使用
    感覺理解不過來,休息一下
    它允許任何的裝飾器接收任何參數。
    在你的腦子裏面記住每次應該怎樣構建這樣的裝飾器
    """
<span class="hljs-comment"># 我們使用相同的技巧來傳遞參數</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator_maker</span><span class="hljs-params">(*args, **kwargs)</span>:</span>
    
    <span class="hljs-comment"># 我們動態的創建一個接收一個函數作爲參數的裝飾器  </span>
    <span class="hljs-comment"># 保持住從decorator_maker傳遞過來的參數。</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator_wrapper</span><span class="hljs-params">(func)</span>:</span>
   
        <span class="hljs-comment"># 我們最後返回原始裝飾器的結果,  </span>
        <span class="hljs-comment"># 這個結果就是最原始被裝飾的函數 (就是返回一個函數)。</span>
        <span class="hljs-comment"># 這裏只有一個小缺陷:被封裝的裝飾器必須具有特定的簽名,不然它不會工作</span>
        <span class="hljs-keyword">return</span> decorator_to_enhance(func, *args, **kwargs)這個樣子
    
    <span class="hljs-keyword">return</span> decorator_wrapper

<span class="hljs-keyword">return</span> decorator_maker

它可以像下面這樣使用:

# 你創建一個將要被用作裝飾器的函數。並向它添加一個裝飾器(這裏是指decorator_with_args) :-)
# 不要忘了你創建的這個裝飾器簽名必須是 "decorator(func, *args, **kwargs)"
@decorator_with_args 
def decorated_decorator(func, *args, **kwargs): 
    def wrapper(function_arg1, function_arg2):
        print "Decorated with", args, kwargs
        return func(function_arg1, function_arg2)
    return wrapper

# 之後你使用上面定義的裝飾器(指decorated_decorator)來裝飾一個函數
@decorated_decorator(42, 404, 1024)
def decorated_function(function_arg1, function_arg2):
print “Hello”, function_arg1, function_arg2

decorated_function(“Universe and”, “everything”)
# 輸出爲:
#Decorated with (42, 404, 1024) {}
#Hello Universe and everything

# 喔!

我知道,你有了這樣一種感覺,它是在聽一一個傢伙說:"before understanding recursion, you must first understand recursion"產生的。但是,現在,你是否有更好的體會了?


最佳實踐: 裝飾器

  • 裝飾器是Python2.4引入的,因此保證你的代碼運行的版本 >= 2.4。
  • 裝飾器會拖慢函數的執行速度,記住這點。
  • 你不能反裝飾一個函數。因此一旦一個函數被裝飾了,它對所有其它代碼來說就都是被裝飾了的。
  • 裝飾器會封裝函數,這會讓它們變得更難調試。 (這點從Python >= 2.5版本變得好了起來; 具體參考下面。)

在Python 2.5中 functools模塊被引入。它包含了 functools.wraps()函數,這個函數會將被裝飾函數的名稱、模塊、文檔字符串拷貝到封裝函數。

(有趣的事實是: functools.wraps() 也是一個裝飾器! ☺)

# 爲了調試,在堆棧軌跡中打印了函數的名稱(__name__)
def foo():
    print "foo"

print foo.name
# 輸出爲 : foo

# 有了裝飾器,名稱就凌亂了
def bar(func):
def wrapper():
print “bar”
return func()
return wrapper

@bar
def foo():
print “foo”

print foo.name
# 輸出爲: wrapper

# "functools"對這個有幫助

import functools

def bar(func):
# 我們看到"wrapper"封裝了"func"
# 現在魔法開始了
@functools.wraps(func)
def wrapper():
print “bar”
return func()
return wrapper

@bar
def foo():
print “foo”

print foo.name
# 輸出爲: foo


裝飾器可以被用在什麼地方?

現在問題來了:我可以使用裝飾器來幹什麼?

儘管看起來很酷很強大,但是一個實際的應用事例能更好的說明問題。好了,這裏有1000種可能性。經典的使用是在庫以外的代碼中擴展一個函數的行爲 (你不能修改的函數),或者只是爲了調試 (因爲調試只是零時的,所有你不想修改這個函數)。

你可以以一種DRY的方式使用他們來擴展許多函數,像這樣:

def benchmark(func):
    """
    打印原函數調用時間的裝飾器
    """
    import time
    def wrapper(*args, **kwargs):
        t = time.clock()
        res = func(*args, **kwargs)
        print func.__name__, time.clock()-t
        return res
    return wrapper

def logging(func):
“”"
記錄腳本行爲日誌的裝飾器
(這裏只是打印它,但是也可以記錄到日誌裏面!)
“”"

def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print func.name, args, kwargs
return res
return wrapper

def counter(func):
“”"
記錄並打印一個函數執行次數的裝飾器
“”"

def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(*args, **kwargs)
print “{0} has been used: {1}x”.format(func.name, wrapper.count)
return res
wrapper.count = 0
return wrapper

@counter
@benchmark
@logging
def reverse_string(string):
return str(reversed(string))

print reverse_string(“Able was I ere I saw Elba”)
print reverse_string(“A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!”)

#輸出爲:
#reverse_string (‘Able was I ere I saw Elba’,) {}
#wrapper 0.0
#wrapper has been used: 1x
#ablE was I ere I saw elbA
#reverse_string (‘A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!’,) {}
#wrapper 0.0
#wrapper has been used: 2x
#!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A

當然,裝飾器最好的一點是你不需要重寫,就可以幾乎在任何東西上面使用它們。這就是我所說的,DRY:

@counter
@benchmark
@logging
def get_random_futurama_quote():
    from urllib import urlopen
    result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=futurama").read()
    try:
        value = result.split("<br><b><hr><br>")[1].split("<br><br><hr>")[0]
        return value.strip()
    except:
        return "No, I'm ... doesn't!"

print get_random_futurama_quote()
print get_random_futurama_quote()

#outputs:
#get_random_futurama_quote () {}
#wrapper 0.02
#wrapper has been used: 1x
#The laws of science be a harsh mistress.
#get_random_futurama_quote () {}
#wrapper 0.01
#wrapper has been used: 2x
#Curse you, merciful Poseidon!

Python語言本身提供了好多種裝飾器:property, staticmethod,等等。

  • Django使用裝飾器來管理緩存和視圖權限。
  • Twisted to fake inlining asynchronous functions calls.(對Twisted不熟,不知道具體講的是神馬!)。

這個真是一個巨大的遊樂場

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章