python 學習筆記——python 裝飾器(一)


裝飾器的作用:常見的用法可以用來擴展一個方法(這個方法是其他的庫裏面的,你沒辦法修改)也可以用來方便調試(你不想修改原來的方法,只是想暫時看一下調試信息,之後就刪掉了)

裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較爲經典的有插入日誌、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能


def benchmark(func):  
    """ 
    A decorator that prints the time a function takes 
    to execute. 
    一個輸出函數運行時間的裝飾器 
    """  
    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):  
    """ 
    A decorator that logs the activity of the script. 
    一個輸出日誌信息的裝飾器 
    (it actually just prints it, but it could be logging!) 
    雖然這裏只是簡單得只用了print函數,但是你可以用其他日誌模塊代替 
    """  
    def wrapper(*args, **kwargs):  
        res = func(*args, **kwargs)  
        print func.__name__, args, kwargs  
        return res  
    return wrapper  
  
  
def counter(func):  
    """ 
    A decorator that counts and prints the number of times a function has been executed 
    一個記錄、打印函數調用次數的裝飾器 
    """  
    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   #注意當有多個裝飾器的時候執行的順序是從內到外,裝飾順序爲:@logging,@benchmark,@counter  先執行裝飾器,最後執行被裝飾函數體
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!")  
  
#outputs:  
#reverse_string ('Able was I ere I saw Elba',) {}      此時裝飾器中的func.__name__ 是被裝飾的函數名
#wrapper 0.0                                          此時的裝飾器的func.__name__ 是上層裝飾器返回的wrapper
#wrapper has been used: 1x                             此時的裝飾器的func.__name__ 是上層裝飾器返回的wrapper
#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 ,)


對帶參數的函數進行裝飾


# It's not black magic, you just have to let the wrapper   
# pass the argument:  
# 這一點都不神奇,只要讓包裝器(wrapper)傳遞參數就可以了  
  
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  
  
# Since when you are calling the function returned by the decorator, you are  
# calling the wrapper, passing arguments to the wrapper will let it pass them to   
# the decorated function  
# 當你調用通過裝飾器包裝過後返回的函數時,   
# 相當於調用包裝器,並且將參數傳遞給包裝器,由包裝器將參數傳遞給原始函數。  
 
@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  


對參數數量不確定的函數進行裝飾

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 sayYourAge(self, lie=-3): # You can now add a default value  
        print "I am %s, what did you think ?" % (self.age + lie)  
  
m = Mary()  
m.sayYourAge()  
#outputs  
# Do I have args?:  
#(<__main__.Mary object at 0xb7d303ac>,)  
#{}  
#I am 28, what did you think?  


裝飾方法          #######  這裏要注意的地方

What's great with Python is that methods and functions are really the same, except methods expect their first parameter to be a reference to the current object (self). It means you can build a decorator for methods the same way, just remember to take self in consideration:
在Python裏面,方法(method)和函數(function)基本上是一樣的,除了一點:方法的第一個參數必須是當前對象(self)的引用。也就是說你可以用同樣的方法來裝飾方法(method),只要記得處理self參數就可以了。 

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 sayYourAge(self, lie):  
        print "I am %s, what did you think?" % (self.age + lie)  
  
l = Lucy()  
l.sayYourAge(-3)  
#outputs: I am 26, what did you think?  


讓裝飾器帶參數


def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):    
    
#  一層封裝,帶參數是給裝飾器的參數

    print "I make decorators! And I accept arguments:", decorator_arg1, decorator_arg2  
  
    def my_decorator(func):                      #  二層封裝,帶的參數是被裝飾的函數
        # The ability to pass arguments here is a gift from closures.  
        # If you are not comfortable with closures, you can assume it's ok,  
        # or read: http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python  
        print "I am the decorator. Somehow you passed me arguments:", decorator_arg1, decorator_arg2  
  
        # Don't confuse decorator arguments and function arguments!  
        # 三層封裝,對被封裝的函數進行處理,此處的參數是被裝飾函數的參數
        # 二三層的封裝就是裝飾器沒有參數的時候(也就是外層裝飾器的參數是被裝飾的函數的那種情況)
        def wrapped(function_arg1, function_arg2) :                     
            print ("I am the wrapper around the decorated function.\n"  
                  "I can access all the variables\n"  
                  "\t- from the decorator: {0} {1}\n"  
                  "\t- from the function call: {2} {3}\n"  
                  "Then I can pass them to the decorated function"  
                  .format(decorator_arg1, decorator_arg2,  
                          function_arg1, function_arg2))  
            return func(function_arg1, function_arg2)  
  
        return wrapped  
  
    return 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")  
#outputs:  
#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  

注意:此時需要裝飾器要進行三層封裝,比沒有帶參數的裝飾器多了外層的一層封裝functools 的使用# For debugging, the stacktrace prints you the function __name__  
def foo():  
    print "foo"  
  
print foo.__name__  
#outputs: foo  
  
# With a decorator, it gets messy      
def bar(func):  
    def wrapper():  
        print "bar"  
        return func()  
    return wrapper  
 
@bar  
def foo():  
    print "foo"  
  
print foo.__name__  
#outputs: wrapper  
  
# "functools" can help for that  
  
import functools  
  
def bar(func):  
    # We say that "wrapper", is wrapping "func"  
    # and the magic begins  
    @functools.wraps(func)  
    def wrapper():  
        print "bar"  
        return func()  
    return wrapper  
 
@bar  
def foo():  
    print "foo"  
  
print foo.__name__  
#outputs: foo  
1、They are new as of Python 2.4, so be sure that's what your code is running on.
2、Decorators slow down the function call. Keep that in mind.
3、You can not un-decorate a function. There are hacks to create decorators that can be removed but nobody uses them. So once a function is decorated, it's done. For all the code.
4、Decorators wrap functions, which can make them hard to debug.

1、裝飾器是在Python 2.4之後纔有的特性,所以請檢查你的版本。
2、請記住:裝飾器將會帶來性能問題。
3、裝飾是不可逆的。雖然已經有hacks設計出了可逆的裝飾器,但是基本沒人這麼做。所以一旦一個函數被裝飾過了,就無法還原了。
4、裝飾器包裝了函數,使得調試更加困難。
Python 2.5 solves this last issue by providing the functools module including functools.wraps that copies the name, module and docstring of any wrapped function to it's wrapper. Fun fact, functools.wraps is a decorator :-)
Python 2.5 解決上面提到的第四個問題。Python 2.5 之後,包含了一個functools模塊,這個模塊提供一個functools.wraps方法,這個方法將被包裝的函數的name, module 和 docstring 都複製到包裝好的函數上去。顯然,functools.wraps也是一個裝飾器 


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