python裝飾器和描述器的使用總結
被某些中文教程坑過,我的建議是有問題看官方文檔,即使沒有很詳細的例子,至少不坑
裝飾器
毫無疑問在python中用得非常多
def deco(func): def _deco(): print 'before invoked' func() print 'after invoked' return _deco @deco def f(): print 'f is invoked'
在f
上加deco
裝飾器相當于f = deco(f)
, 和functools.partial
有些類似
如果被裝飾的函數f
帶參數且有返回值
def deco(func): def _deco(*args, **kwargs): print 'before invoked' ret = func(*args, **kwargs) print 'after invoded' return ret return _deco @deco def f(a): print 'f is invoked' return a + 1
如果裝飾器帶有參數,需要多包一層,把參數調用包進去
def deco(*args): def _deco(func): def __deco(*args, **kwargs): print 'decorator args is', args print 'before invoked' ret = func(*args, **kwargs) print 'after invoded' return ret return __deco return _deco @deco('test') def f(a): print 'f is invoked' return a + 1
只有最里面一層的__deco
才會每次都調用,其它外層函數只在包裝時調用一次,當然,你可以在其中聲明變量,然后拿到__deco
里使用。如果需要保留函數名,則在__deco
上加@functools.wraps
裝飾器
使用 類 作裝飾器,注意是類此時相當于裝飾函數,被裝飾的函數會作為實例化參數,得到一個類實例,以python wiki上一個做登錄檢查的代碼為例
class LoginCheck: def __init__(self, f): self._f = f def __call__(self, *args): Status = check_function() if Status is 1: return self._f(*args) else: return alt_function() def check_function(): return test def alt_function(): return 'Sorry - this is the forced behaviour' @LoginCheck def display_members_page(): print 'This is the members page'
描述器
描述器在監視特定屬性的時候很有用,其只在新式類中起作用。所有的描述器協議如下:
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
如果一個對象同時定義了 __get__()
和 __set__()
,它叫做資料描述器(data descriptor)。僅定義了 __get__()
的描述器叫非資料描述器
描述器在屬性訪問時被自動調用。舉例來說, obj.x
會在 obj
的字典中找x
,如果x
定義了 __get__
方法,那么 x.__get__(obj)
會依據下面的優先規則被調用
調用優先級:
資料描述器 -> 實例字典 -> 非資料描述器
常用的描述器就是property
了,一般都只實現了__get__
的接口
先給出一個classmethod
的實現和一個用于測試描述器優先級的類
class classproperty(object): def __init__(self, func): self.func = func def __get__(self, instance, owner): return self.func(owner) class MyClass(object): @classproperty def name(cls): return cls.__name__ @property def x(self): return self._data @x.setter def x(self, value): self._data = value @x.deleter def x(self): del self._data def __init__(self, val): self._data = val self.x = 3 self.name = 'test'
接下來調用
s = MyClass(99) print s.x print s.name print s.__dict__
很明顯x
是資料描述器,而name
不是,所以結果是
3
5
{'_data': 3, 'name': 'test'}
如果給classproperty
加上__set__
,那么就會調用被裝飾的name
,而不是實例化時實例字典中的name
一個property
的python 實現
class Property(object): "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError, "unreadable attribute" return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, "can't set attribute" self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)