常見裝飾器;內置裝飾器;類裝飾器、函數裝飾器、帶參數的函數裝飾器
裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能
一、函數裝飾器:
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) return wrapper def bar(): print('i am bar')
bar = use_logging(bar) bar()
輸出:
WARNING:root:bar is running
i am bar
二、帶參數的函數裝飾器:
def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) return func(*args) return wrapper return decorator @use_logging(level="warn") def foo(name='foo'): print("i am %s" % name) foo()
輸出:
WARNING:root:foo is running
i am foo
三、類裝飾器:
再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的 __call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def bar(): print ('bar') bar()
輸出:
class decorator runing
bar
class decorator ending
四、functools.wraps
使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:
裝飾器
def logged(func): def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging
函數
@logged def f(x): """does some math""" return x + x * x
該函數完成等價於:
def f(x): """does some math""" return x + x * x f = logged(f)
不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。
print f.__name__ # prints 'with_logging' print f.__doc__ # prints None
這個問題就比較嚴重的,好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print f.__name__ # prints 'f' print f.__doc__ # prints 'does some math'
五、內置裝飾器
@property 的用法參見:把類方法變成屬性,可以通過類直接調用
https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820062641f3bcc60a4b164f8d91df476445697b9e000
http://www.cnblogs.com/superxuezhazha/p/5793450.html
因為Python支持高階函數,在函數式編程中我們介紹了裝飾器函數,可以用裝飾器函數把 get/set 方法“裝飾”成屬性調用:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score 注意: 第一個score(self)是get方法,用@property裝飾,第二個score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個@property裝飾后的副產品。 現在,就可以像使用屬性一樣設置score了: >>> s = Student('Bob', 59) >>> s.score = 60 >>> print s.score
60
@staticmethod :靜態方法
@classmethod : 類方法
參考:http://www.cnblogs.com/taceywong/p/5813166.html
Python其實有3類方法:
- 靜態方法(staticmethod)
- 類方法(classmethod)
- 實例方法(instance method)
看一下下面的示例代碼:
def foo(x): print "executing foo(%s)" %(x) class A(object): def foo(self,x): print "executing foo(%s,%s)" %(self,x) @classmethod def class_foo(cls,x): print "executing class_foo(%s,%s)" %(cls,x) @staticmethod def static_foo(x): print "executing static_foo(%s)" %x a = A()
在示例代碼中,先理解下函數里面的self和cls。這個self和cls是對類或者實例的綁定,對於一般的函數來說我們可以這么調用foo(x)
,這個函數就是最常用的,它的工作和任何東西(類、實例)無關。對於實例方法,我們知道在類里每次定義方法的時候都需要綁定這個實例,就是foo(self,x)
,為什么要這么做呢?因為實例方法的調用離不開實例,我們需要把實例自己傳給函數,調用的時候是這樣的a.foo(x)
(其實是foo(a,x)
)。類方法一樣,只不過它傳遞的是類而不是實例,A.class_foo(x)
。注意這里的self和cls可以替換別的參數,但是python的約定是這兩個,盡量不要更改。
對於靜態方法其實和普通的方法一樣,不需要對誰進行綁定,唯一的區別是調用時候需要使用a.static_foo(x)
或A.static_foo()
來調用。
\ | 實例方法 | 類方法 | 靜態方法 |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.clas_foo(x) | A.static_foo(x) |
>>> a=A() >>> a.foo(3) executing foo(<__main__.A object at 0x108117790>,3) >>> a.class_foo(3) executing class_foo(<class '__main__.A'>,3) >>> A.class_foo(3) executing class_foo(<class '__main__.A'>,3) >>> a.static_foo(3) executing static_foo(3) >>> A.static_foo(3) executing static_foo(3) >>> A.foo(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unbound method foo() must be called with A instance as first argument (got int instance instead) >>>
參考:
1、https://www.zhihu.com/question/26930016
2、http://python.jobbole.com/85056/
3、http://pythoncentral.io/difference-between-staticmethod-and-classmethod-in-python/
4、http://30daydo.com/article/89