日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

淺談Python裝飾器

 CodeNutter 2016-08-07

淺談Python裝飾器

By 馬冬亮(凝霜  Loki)

一個(gè)人的戰(zhàn)爭(http://blog.csdn.net/MDL13412)

前置知識

一級對象

Python將一切視為 objec t的子類,即一切都是對象,因此函數(shù)可以像變量一樣被指向和傳遞,我們來看下面的例子:

def foo(): pass print issubclass(foo.__class__, object)
其運(yùn)行結(jié)果如下:
True
上述代碼說明了Python 中的函數(shù)是object 的子類,下面讓我們看函數(shù)被當(dāng)作參數(shù)傳遞時(shí)的效果:
def foo(func): func()def bar(): print 'bar'foo(bar)
其運(yùn)行結(jié)果如下:
bar

Python中的namespace

Python中通過提供 namespace 來實(shí)現(xiàn)重名函數(shù)/方法、變量等信息的識別,其一共有三種 namespace,分別為:

  • local namespace: 作用范圍為當(dāng)前函數(shù)或者類方法
  • global namespace: 作用范圍為當(dāng)前模塊
  • build-in namespace: 作用范圍為所有模塊

當(dāng)函數(shù)/方法、變量等信息發(fā)生重名時(shí),Python會按照 local namespace -> global namespace -> build-in namespace的順序搜索用戶所需元素,并且以第一個(gè)找到此元素的namespace 為準(zhǔn)。
下面以系統(tǒng)的 build-in 函數(shù) str 為例進(jìn)行說明:

def str(s): print 'global str()'def foo(): def str(s): print 'closure str()' str('dummy')def bar(): str('dummy')foo()bar()
首先定義三個(gè)global namespace 的函數(shù) str、foobar,然后在foo 函數(shù)中定義一個(gè)內(nèi)嵌的 local namespace 的函數(shù)str,然后在函數(shù)foo 和 bar 中分別調(diào)用 str('dummy'),其運(yùn)行結(jié)果如下所示:
closure str()global str()
通過編碼實(shí)驗(yàn),我們可以看到:
  • foo 中調(diào)用 str 函數(shù)時(shí),首先搜索 local namespace,并且成功找到了所需的函數(shù),停止搜索,使用此namespace 中的定義
  • bar 中調(diào)用 str 函數(shù)時(shí),首先搜索 local namespace,但是沒有找到str 方法的定義,因此繼續(xù)搜索global namespace,并成功找到了 str 的定義,停止搜索,并使用此定義
下面我們使用Python內(nèi)置的 `ocals() 和 globals() 函數(shù)查看不同 namespace 中的元素定義:
var = 'var in global'def fun(): var = 'var in fun' print 'fun: ' + str(locals())print 'globals: ' + str(globals())fun()
運(yùn)行結(jié)果如下:
globals: {'__builtins__': , '__file__': 'a.py', '__package__': None, 'fun': , 'var': 'var in global', '__name__': '__main__', '__doc__': None}fun: {'var': 'var in fun'}
通過運(yùn)行結(jié)果,我們看到了 fun 定義了 local namespace 的變量var,在global namespace 有一個(gè)全局的 var 變量,那么當(dāng)在global namespace 中直接訪問var 變量的時(shí)候,將會得到 var = 'var in global' 的定義,而在fun 函數(shù)的local namespace 中訪問 var 變量,則會得到fun 私有的var = 'var in fun' 定義。

*args and **kwargs

  • *args: 把所有的參數(shù)按出現(xiàn)順序打包成一個(gè) list
  • **kwargs:把所有 key-value 形式的參數(shù)打包成一個(gè) dict

下面給出一個(gè) *args 的例子:

params_list = (1, 2)params_tupple = (1, 2)def add(x, y): print x + yadd(*params_list)add(*params_tupple)
其運(yùn)行結(jié)果如下:
33
**kwargs 的例子:
params = { 'x': 1, 'y': 2}def add(x, y): print x + yadd(**params)
其運(yùn)行結(jié)果如下:
3

閉包

閉包在維基百科上的定義如下: 在計(jì)算機(jī)科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。
下面給出一個(gè)使用閉包實(shí)現(xiàn)的logger factory的例子:
def logger_facroty(prefix='', with_prefix=True): if with_prefix: def logger(msg): print prefix + msg return logger else: def logger(msg): print msg return loggerlogger_with_prefix = logger_facroty('Prefix: ')logger_without_prefix = logger_facroty(with_prefix=False)logger_with_prefix('msg')logger_without_prefix('msg')
其運(yùn)行結(jié)果如下:
Prefix: msgmsg
在上面這個(gè)閉包的例子中,prefix 變量時(shí)所謂的自由變量,其在 return logger 執(zhí)行完畢后,便脫離了創(chuàng)建它的環(huán)境logger_factory,但因?yàn)槠浔?strong>logger_factory 中定義的 logger 函數(shù)所引用,其生命周期將至少和 logger 函數(shù)相同。這樣,在 logger 中就可以引用到logger_factory 作用域內(nèi)的變量prefix
將閉包與 namespace 結(jié)合起來:

var = 'var in global'def fun_outer(): var = 'var in fun_outer' unused_var = 'this var is not used in fun_inner' print 'fun_outer: ' + var print 'fun_outer: ' + str(locals()) print 'fun_outer: ' + str(id(var)) def fun_inner(): print 'fun_inner: ' + var print 'fun_inner: ' + str(locals()) print 'fun_inner: ' + str(id(var)) return fun_innerfun_outer()()
其運(yùn)行結(jié)果如下:
fun_outer: var in fun_outerfun_outer: {'var': 'var in fun_outer', 'unused_var': 'this var is not used in fun_inner'}fun_outer: 140228141915584fun_inner: var in fun_outerfun_inner: {'var': 'var in fun_outer'}fun_inner: 140228141915584
在這個(gè)例子中,當(dāng) fun_outer 被定義時(shí),其內(nèi)部的定義的 fun_inner 函數(shù)對 print 'fun_inner: ' + var中所引用的var 變量進(jìn)行搜索,發(fā)現(xiàn)第一個(gè)被搜索到的 var 定義在 fun_outerlocal namespace 中,因此使用此定義,通過 print 'fun_outer: ' + str(id(var)) print 'fun_inner: ' + str(id(var)),當(dāng)var 超出fun_outer 的作用域后,依然存活,而 fun_outer 中的unused_var 變量由于沒有被fun_inner 所引用,因此會被 GC。

探索裝飾器

定義

點(diǎn)擊打開裝飾器在維基百科上的定義鏈接如下: A decorator is any callable Python object that is used to modify a function, method or class definition.

基本語法

語法糖

@bardef foo(): print 'foo'
其等價(jià)于:
def foo(): print 'foo'foo = bar(foo)

無參數(shù)裝飾器

def foo(func): print 'decorator foo' return func@foodef bar(): print 'bar'bar()
foo 函數(shù)被用作裝飾器,其本身接收一個(gè)函數(shù)對象作為參數(shù),然后做一些工作后,返回接收的參數(shù),供外界調(diào)用。

注意: 時(shí)刻牢記 @foo 只是一個(gè)語法糖,其本質(zhì)是 foo = bar(foo)

帶參數(shù)裝飾器

import timedef function_performance_statistics(trace_this=True): if trace_this: def performace_statistics_delegate(func): def counter(*args, **kwargs): start = time.clock() func(*args, **kwargs) end =time.clock() print 'used time: %d' % (end - start, ) return counter else: def performace_statistics_delegate(func): return func return performace_statistics_delegate@function_performance_statistics(True)def add(x, y): time.sleep(3) print 'add result: %d' % (x + y,)@function_performance_statistics(False)def mul(x, y=1): print 'mul result: %d' % (x * y,)add(1, 1)mul(10)
上述代碼想要實(shí)現(xiàn)一個(gè)性能分析器,并接收一個(gè)參數(shù),來控制性能分析器是否生效,其運(yùn)行效果如下所示:
add result: 2used time: 0mul result: 10
上述代碼中裝飾器的調(diào)用等價(jià)于:
add = function_performance_statistics(True)(add(1, 1))mul = function_performance_statistics(False)(mul(10))

類的裝飾器

類的裝飾器不常用,因此只簡單介紹。

def bar(dummy): print 'bar'def inject(cls): cls.bar = bar return cls@injectclass Foo(object): passfoo = Foo()foo.bar()
上述代碼的 inject 裝飾器為類動態(tài)的添加一個(gè) bar 方法,因?yàn)轭愒谡{(diào)用非靜態(tài)方法的時(shí)候會傳進(jìn)一個(gè)self 指針,因此bar 的第一個(gè)參數(shù)我們簡單的忽略即可,其運(yùn)行結(jié)果如下:
bar

類裝飾器

類裝飾器相比函數(shù)裝飾器,具有靈活度大,高內(nèi)聚、封裝性等優(yōu)點(diǎn)。其實(shí)現(xiàn)起來主要是靠類內(nèi)部的 __call__ 方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí),就會調(diào)用此方法,下面時(shí)一個(gè)實(shí)例:

class Foo(object): def __init__(self, func): super(Foo, self).__init__() self._func = func def __call__(self): print 'class decorator' self._func()@Foodef bar(): print 'bar'bar()
其運(yùn)行結(jié)果如下:
class decoratorbar

內(nèi)置裝飾器

Python中內(nèi)置的裝飾器有三個(gè): staticmethodclassmethodproperty

staticmethod 是類靜態(tài)方法,其跟成員方法的區(qū)別是沒有 self 指針,并且可以在類不進(jìn)行實(shí)例化的情況下調(diào)用,下面是一個(gè)實(shí)例,對比靜態(tài)方法和成員方法

class Foo(object): @staticmethod def statc_method(msg): print msg def member_method(self, msg): print msgfoo = Foo()foo.member_method('some msg')foo.statc_method('some msg')Foo.statc_method('some msg')
其運(yùn)行結(jié)果如下:
some msgsome msgsome msg
classmethod 與成員方法的區(qū)別在于所接收的第一個(gè)參數(shù)不是 self 類實(shí)例的指針,而是當(dāng)前類的具體類型,下面是一個(gè)實(shí)例:
class Foo(object): @classmethod def class_method(cls): print repr(cls) def member_method(self): print repr(self)foo = Foo()foo.class_method()foo.member_method()
其運(yùn)行結(jié)果如下:
<__main__.Foo object at 0x10a611c50>
property 是屬性的意思,即可以通過通過類實(shí)例直接訪問的信息,下面是具體的例子:
class Foo(object): def __init__(self, var): super(Foo, self).__init__() self._var = var @property def var(self): return self._var @var.setter def var(self, var): self._var = varfoo = Foo('var 1')print foo.varfoo.var = 'var 2'print foo.var
注意: 如果將上面的 @var.setter 裝飾器所裝飾的成員函數(shù)去掉,則Foo.var 屬性為只讀屬性,使用 foo.var = 'var 2'進(jìn)行賦值時(shí)會拋出異常,其運(yùn)行結(jié)果如下:
var 1var 2
注意: 如果使用老式的Python類定義,所聲明的屬性不是 read only的,下面代碼說明了這種情況:
class Foo: def __init__(self, var): self._var = var @property def var(self): return self._varfoo = Foo('var 1')print foo.varfoo.var = 'var 2'print foo.var
其運(yùn)行結(jié)果如下:
var 1var 2

調(diào)用順序

裝飾器的調(diào)用順序與使用 @ 語法糖聲明的順序相反,如下所示:

def decorator_a(func): print 'decorator_a' return funcdef decorator_b(func): print 'decorator_b' return func@decorator_a@decorator_bdef foo(): print 'foo' foo()
其等價(jià)于:
def decorator_a(func): print 'decorator_a' return funcdef decorator_b(func): print 'decorator_b' return funcdef foo(): print 'foo'foo = decorator_a(decorator_b(foo))foo()
通過等價(jià)的調(diào)用形式我們可以看到,按照python的函數(shù)求值序列,decorator_b(fun) 會首先被求值,然后將其結(jié)果作為輸入,傳遞給decorator_a,因此其調(diào)用順序與聲明順序相反。其運(yùn)行結(jié)果如下所示:
decorator_bdecorator_afoo

調(diào)用時(shí)機(jī)

裝飾器很好用,那么它什么時(shí)候被調(diào)用?性能開銷怎么樣?會不會有副作用?接下來我們就以幾個(gè)實(shí)例來驗(yàn)證我們的猜想。
首先我們驗(yàn)證一下裝飾器的性能開銷,代碼如下所示:

def decorator_a(func): print 'decorator_a' print 'func id: ' + str(id(func)) return funcdef decorator_b(func): print 'decorator_b' print 'func id: ' + str(id(func)) return funcprint 'Begin declare foo with decorators'@decorator_a@decorator_bdef foo(): print 'foo'print 'End declare foo with decorators'print 'First call foo'foo()print 'Second call foo'foo()print 'Function infos'print 'decorator_a id: ' + str(id(decorator_a))print 'decorator_b id: ' + str(id(decorator_b))print 'fooid : ' + str(id(foo))
其運(yùn)行結(jié)果如下:
Begin declare foo with decoratorsdecorator_bfunc id: 140124961990488decorator_afunc id: 140124961990488End declare foo with decoratorsFirst call foofooSecond call foofooFunction infosdecorator_a id: 140124961954464decorator_b id: 140124961988808fooid : 140124961990488
在運(yùn)行結(jié)果中的:
Begin declare foo with decoratorsdecorator_bfunc id: 140124961990488decorator_afunc id: 140124961990488End declare foo with decorators
證實(shí)了裝飾器的調(diào)用時(shí)機(jī)為: 被裝飾對象定義時(shí)
而運(yùn)行結(jié)果中的:
First call foofooSecond call foofoo
證實(shí)了在相同 .py 文件中,裝飾器對所裝飾的函數(shù)只進(jìn)行一次裝飾,不會每次調(diào)用相應(yīng)函數(shù)時(shí)都重新裝飾,這個(gè)很容易理解,因?yàn)槠浔举|(zhì)等價(jià)于下面的函數(shù)簽名重新綁定:
foo = decorator_a(decorator_b(foo))
對于跨模塊的調(diào)用,我們編寫如下結(jié)構(gòu)的測試代碼:
.├── common│   ├── decorator.py│   ├── __init__.py│   ├── mod_a│   │   ├── fun_a.py│   │   └── __init__.py│   └── mod_b│   ├── fun_b.py│   └── __init__.py└── test.py
上述所有模塊中的 __init__.py 文件均為: # -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-# common/mod_a/fun_a.pyfrom common.decorator import foodef fun_a():    print 'in common.mod_a.fun_a.fun_a call foo'    foo()

# -*- coding: utf-8 -*-# common/mod_b/fun_b.pyfrom common.decorator import foodef fun_b(): print 'in common.mod_b.fun_b.fun_b call foo' foo()
# -*- coding: utf-8 -*-# common/decorator.pydef decorator_a(func): print 'init decorator_a' return func@decorator_adef foo(): print 'function foo'
# -*- coding: utf-8 -*-# test.pyfrom common.mod_a.fun_a import fun_afrom common.mod_b.fun_b import fun_bfun_a()fun_b()
上述代碼通過創(chuàng)建 common.mod_a common.mod_b 兩個(gè)子模塊,并調(diào)用common.decorator 中的foo 函數(shù),來測試跨模塊時(shí)裝飾器的工作情況,運(yùn)行 test.py 的結(jié)果如下所示:
init decorator_ain common.mod_a.fun_a.fun_a call foofunction fooin common.mod_b.fun_b.fun_b call foofunction foo
經(jīng)過上面的驗(yàn)證,可以看出,對于跨模塊的調(diào)用,裝飾器也只會初始化一次,不過這要?dú)w功于 *.pyc,這與本文主題無關(guān),故不詳述。
關(guān)于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時(shí)候是我們設(shè)計(jì)上的問題,下面給出一個(gè)初學(xué)裝飾器時(shí)大家都會遇到的一個(gè)問題——丟失函數(shù)元信息:
def decorator_a(func): def inner(*args, **kwargs): res = func(*args, **kwargs) return res return inner@decorator_adef foo(): '''foo doc''' return 'foo result'print 'foo.__module__: ' + str(foo.__module__)print 'foo.__name__: ' + str(foo.__name__)print 'foo.__doc__: ' + str(foo.__doc__)print foo()
其運(yùn)行結(jié)果如下所示:
foo.__module__: __main__foo.__name__: innerfoo.__doc__: Nonefoo result
我們可以看到,在使用 decorator_a 對 foo 函數(shù)進(jìn)行裝飾后,foo 的元信息會丟失,解決方案參見: functools.wraps

多個(gè)裝飾器運(yùn)行期行為

前面已經(jīng)講解過裝飾器的調(diào)用順序和調(diào)用時(shí)機(jī),但是被多個(gè)裝飾器裝飾的函數(shù),其運(yùn)行期行為還是有一些細(xì)節(jié)需要說明的,而且很可能其行為會讓你感到驚訝,下面時(shí)一個(gè)實(shí)例:

def tracer(msg): print '[TRACE] %s' % msgdef logger(func): tracer('logger') def inner(username, password): tracer('inner') print 'call %s' % func.__name__ return func(username, password) return innerdef login_debug_helper(show_debug_info=False): tracer('login_debug_helper') def proxy_fun(func): tracer('proxy_fun') def delegate_fun(username, password): tracer('delegate_fun') if show_debug_info: print 'username: %s\npassword: %s' % (username, password) return func(username, password) return delegate_fun return proxy_funprint 'Declaring login_a'@logger@login_debug_helper(show_debug_info=True)def login_a(username, password): tracer('login_a') print 'do some login authentication' return Trueprint 'Call login_a'login_a('mdl', 'pwd')
大家先來看一下運(yùn)行結(jié)果,看看是不是跟自己想象中的一致:
Declaring login_a[TRACE] login_debug_helper[TRACE] proxy_fun[TRACE] loggerCall login_a[TRACE] innercall delegate_fun[TRACE] delegate_funusername: mdlpassword: pwd[TRACE] login_ado some login authentication
首先,裝飾器初始化時(shí)的調(diào)用順序與我們前面講解的一致,如下:
Declaring login_a[TRACE] login_debug_helper[TRACE] proxy_fun[TRACE] logger
然而,接下來,來自 logger 裝飾器中的 inner 函數(shù)首先被執(zhí)行,然后才是login_debug_helper 返回的proxy_fun 中的 delegate_fun 函數(shù)。各位讀者發(fā)現(xiàn)了嗎,運(yùn)行期執(zhí)行login_a 函數(shù)的時(shí)候,裝飾器中返回的函數(shù)的執(zhí)行順序是相反的,難道是我們前面講解的例子有錯(cuò)誤嗎?其實(shí),如果大家的認(rèn)為運(yùn)行期調(diào)用順序應(yīng)該與裝飾器初始化階段的順序一致的話,那說明大家沒有看透這段代碼的調(diào)用流程,下面我來為大家分析一下。
def login_debug_helper(show_debug_info=False): tracer('login_debug_helper') def proxy_fun(func): tracer('proxy_fun') def delegate_fun(username, password): tracer('delegate_fun') if show_debug_info: print 'username: %s\npassword: %s' % (username, password) return func(username, password) return delegate_fun return proxy_fun
當(dāng)裝飾器 login_debug_helper 被調(diào)用時(shí),其等價(jià)于:
login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')
對于只有 login_debug_helper 的情況,現(xiàn)在就應(yīng)該是執(zhí)行玩login_a輸出結(jié)果的時(shí)刻了,但是如果現(xiàn)在在加上logger 裝飾器的話,那么這個(gè)login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')就被延遲執(zhí)行,而將login_debug_helper(show_debug_info=True)(login_a) 作為參數(shù)傳遞給 logger,我們令 login_tmp = login_debug_helper(show_debug_info=True)(login_a),則調(diào)用過程等價(jià)于:
login_tmp = login_debug_helper(show_debug_info=True)(login_a)login_a = logger(login_tmp)login_a('mdl', 'pwd')
相信大家看過上面的等價(jià)變換后,已經(jīng)明白問題出在哪里了,如果你還沒有明白,我強(qiáng)烈建議你把這個(gè)例子自己敲一遍,并嘗試用自己的方式進(jìn)行化簡,逐步得出結(jié)論。

一些實(shí)例參考

本文主要講解原理性的東西,具體的實(shí)例可以參考下面的鏈接:
Python裝飾器實(shí)例:調(diào)用參數(shù)合法性驗(yàn)證

Python裝飾器與面向切面編程

Python裝飾器小結(jié)

Python tips: 超時(shí)裝飾器, @timeout decorator

python中判斷一個(gè)運(yùn)行時(shí)間過長的函數(shù)

python利用裝飾器和threading實(shí)現(xiàn)異步調(diào)用

python輸出指定函數(shù)運(yùn)行時(shí)間的裝飾器

python通過裝飾器和線程限制函數(shù)的執(zhí)行時(shí)間

python裝飾器的一個(gè)妙用

通過 Python 裝飾器實(shí)現(xiàn)DRY(不重復(fù)代碼)原則

參考資料

Understanding Python Decorators in 12 Easy Steps

Decorators and Functional Python

Python Wiki: PythonDecorators

Meta-matters: Using decorators for better Python programming

Python裝飾器入門(譯)

Python裝飾器與面向切面編程

Python 的閉包和裝飾器

Python裝飾器學(xué)習(xí)(九步入門)

python 裝飾器和 functools 模塊

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多