1. 程式人生 > >Python 入門:裝飾器(decorator)、@functools.wraps、@staticmethod、@classmethod

Python 入門:裝飾器(decorator)、@functools.wraps、@staticmethod、@classmethod

裝飾器

1、要了解裝飾器,就需要知道什麼是高階函式,高階函式就是將函式作為引數賦值給另一個函式
2、Python的 decorator 本質上就是一個高階函式,它接收一個函式作為引數,然後,返回一個新函式
3、decorator是在被裝飾的函式前加@函式名的函式來修飾下面的函式

#被裝飾函式
def now():
    print(‘2015-3-3’)

想要對被裝飾函式新增(修飾)什麼功能,就可以寫一個特定的函式,然後在被裝飾的函式前加@函式名

#需要裝飾其它函式時的新功能函式
def log(func):
    def wrapper(*args,**kw):
        print(‘call %s(): ’ % func.__name__)
        return
func(*args,**kw) return wrapper

完成裝飾

@log
def now():
    return print(‘2015-3-3’) 

呼叫now()函式,不僅會執行now()函式本身,還會在執行now()函式前列印一行日誌:

>>> now()
call now():
2015-3-3

把@log放到now()函式的定義處,相當於執行了語句:
now = log(now)
由於log()是一個decorator,返回一個函式,所以,原來的now()函式仍然存在,只是現在同名的now變數指向了新的函式,於是呼叫now()將執行新函式,即在log()函式中返回的wrapper()函式。
++++++++++++++++++++++++++++++++++++++
帶引數的裝飾器

def log(text):
    def decorator(func):
        def wrapper(*args,**kw):
            print(‘%s %s():’ % (text,fun.__name__))
            return func(*args,**kw)
        return wrapper
    return decorator

這個3層巢狀的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

執行結果如下:

>>> now
() execute now(): 2015-3-25

和兩層巢狀的decorator相比,3層巢狀的效果是這樣的:

>>> now = log('execute')(now)

我們來剖析上面的語句,首先執行log(‘execute’),返回的是decorator函式,再呼叫返回的函式,引數是now函式,返回值最終是wrapper函式。
以上兩種decorator的定義都沒有問題,但還差最後一步。因為我們講了函式也是物件,它有name等屬性,但你去看經過decorator裝飾之後的函式,它們的name已經從原來的’now’變成了’wrapper’:

>>> now.__name__
'wrapper'

因為返回的那個wrapper()函式名字就是’wrapper’,所以,需要把原始函式的name等屬性複製到wrapper()函式中,否則,有些依賴函式簽名的程式碼執行就會出錯。
不需要編寫wrapper.name = func.name這樣的程式碼,Python內建的functools.wraps就是幹這個事的,所以,一個完整的decorator的寫法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

或者針對帶引數的decorator:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

import functools是匯入functools模組。模組的概念稍候講解。現在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。

類裝飾器

#mylocker.py
class mylocker:
    def __init__(self):
        print("mylocker.__init__() called.")

    @staticmethod
    def acquire():
        print("mylocker.acquire() called.")

    @staticmethod
    def unlock():
        print("  mylocker.unlock() called.")

class lockerex(mylocker):
    @staticmethod
    def acquire():
        print("lockerex.acquire() called.")

    @staticmethod
    def unlock():
        print("  lockerex.unlock() called.")

def lockhelper(cls):
    '''cls 必須實現acquire和release靜態方法'''
    def _deco(func):
        def __deco(*args, **kwargs):
            print("before %s called." % func.__name__)
            cls.acquire()   #這裡需要不建立例項呼叫類的方法,所以需要在acquire方法的上方使用@staticmethod
            try:
                return func(*args, **kwargs)
            finally:
                cls.unlock()    #這裡需要不建立例項呼叫類的方法,所以需要在unlock方法的上方使用@staticmethod
        return __deco
    return _deco
#decorate.py
from mylocker import *


class example:
    @lockhelper(mylocker)
    def myfunc(self):
        print(" myfunc() called.")

    @lockhelper(mylocker)
    @lockhelper(lockerex)
    def myfunc2(self, a, b):
        print(" myfunc2() called.")
        return a + b

if __name__=="__main__":
    a = example()
    a.myfunc()
    print(a.myfunc())
    print(a.myfunc2(1, 2))
    print(a.myfunc2(3, 4))

@staticmethod與@classmethod的區別

一般來說,要使用某個類的方法,需要先例項化一個物件再呼叫方法。
而使用@staticmethod或@classmethod,就可以不需要例項化,直接類名.方法名()來呼叫。
這有利於組織程式碼,把某些應該屬於某個類的函式給放到那個類裡去,同時有利於名稱空間的整潔。

既然@staticmethod和@classmethod都可以直接類名.方法名()來呼叫,那他們有什麼區別呢
從它們的使用上來看,
@staticmethod不需要表示自身物件的self和自身類的cls引數,就跟使用函式一樣。
@classmethod也不需要self引數,但第一個引數需要是表示自身類的cls引數。
如果在@staticmethod中要呼叫到這個類的一些屬性方法,只能直接類名.屬性名或類名.方法名。
而@classmethod因為持有cls引數,可以來呼叫類的屬性,類的方法,例項化物件等,避免硬編碼。
下面上程式碼。

[python] view plain copy
class A(object):  
    bar = 1  
    def foo(self):  
        print 'foo'  

    @staticmethod  
    def static_foo():  
        print 'static_foo'  
        print A.bar  

    @classmethod  
    def class_foo(cls):     #這裡用了cls引數,即A這個類本身,後面要使用類.屬性或類.方法時就可以用cls.屬性或cls.方法,避免硬編碼
        print 'class_foo'  
        print cls.bar  
        cls().foo()     #類.方法的呼叫,沒有使用類的名字(A),避免硬編碼

A.static_foo()  
A.class_foo()  
輸出
static_foo
1
class_foo
1
foo