乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      淺談Python裝飾器

       CodeNutter 2016-08-07

      淺談Python裝飾器

      By 馬冬亮(凝霜  Loki)

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

      前置知識(shí)

      一級(jí)對(duì)象

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

      def foo(): pass print issubclass(foo.__class__, object)
      其運(yùn)行結(jié)果如下:
      True
      上述代碼說(shuō)明了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中通過(guò)提供 namespace 來(lái)實(shí)現(xiàn)重名函數(shù)/方法、變量等信息的識(shí)別,其一共有三種 namespace,分別為:

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

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

      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()
      通過(guò)編碼實(shí)驗(yàn),我們可以看到:
      • foo 中調(diào)用 str 函數(shù)時(shí),首先搜索 local namespace,并且成功找到了所需的函數(shù),停止搜索,使用此namespace 中的定義
      • bar 中調(diào)用 str 函數(shù)時(shí),首先搜索 local namespace,但是沒(méi)有找到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'}
      通過(guò)運(yùn)行結(jié)果,我們看到了 fun 定義了 local namespace 的變量var,在global namespace 有一個(gè)全局的 var 變量,那么當(dāng)在global namespace 中直接訪問(wèn)var 變量的時(shí)候,將會(huì)得到 var = 'var in global' 的定義,而在fun 函數(shù)的local namespace 中訪問(wèn) var 變量,則會(huì)得到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)的簡(jiǎn)稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開(kāi)了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說(shuō)法認(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é)合起來(lái):

      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ù)對(duì) print 'fun_inner: ' + var中所引用的var 變量進(jìn)行搜索,發(fā)現(xiàn)第一個(gè)被搜索到的 var 定義在 fun_outerlocal namespace 中,因此使用此定義,通過(guò) print 'fun_outer: ' + str(id(var)) print 'fun_inner: ' + str(id(var)),當(dāng)var 超出fun_outer 的作用域后,依然存活,而 fun_outer 中的unused_var 變量由于沒(méi)有被fun_inner 所引用,因此會(huì)被 GC

      探索裝飾器

      定義

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

      基本語(yǔ)法

      語(yǔ)法糖

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

      無(wú)參數(shù)裝飾器

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

      注意: 時(shí)刻牢記 @foo 只是一個(gè)語(yǔ)法糖,其本質(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ù),來(lái)控制性能分析器是否生效,其運(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))

      類的裝飾器

      類的裝飾器不常用,因此只簡(jiǎn)單介紹。

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

      類裝飾器

      類裝飾器相比函數(shù)裝飾器,具有靈活度大,高內(nèi)聚、封裝性等優(yōu)點(diǎn)。其實(shí)現(xiàn)起來(lái)主要是靠類內(nèi)部的 __call__ 方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí),就會(huì)調(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è): staticmethod、classmethodproperty

      staticmethod 是類靜態(tài)方法,其跟成員方法的區(qū)別是沒(méi)有 self 指針,并且可以在類不進(jìn)行實(shí)例化的情況下調(diào)用,下面是一個(gè)實(shí)例,對(duì)比靜態(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 是屬性的意思,即可以通過(guò)通過(guò)類實(shí)例直接訪問(wèn)的信息,下面是具體的例子:
      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í)會(huì)拋出異常,其運(yùn)行結(jié)果如下:
      var 1var 2
      注意: 如果使用老式的Python類定義,所聲明的屬性不是 read only的,下面代碼說(shuō)明了這種情況:
      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)用順序與使用 @ 語(yǔ)法糖聲明的順序相反,如下所示:

      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()
      通過(guò)等價(jià)的調(diào)用形式我們可以看到,按照python的函數(shù)求值序列,decorator_b(fun) 會(huì)首先被求值,然后將其結(jié)果作為輸入,傳遞給decorator_a,因此其調(diào)用順序與聲明順序相反。其運(yùn)行結(jié)果如下所示:
      decorator_bdecorator_afoo

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

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

      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ī)為: 被裝飾對(duì)象定義時(shí)
      而運(yùn)行結(jié)果中的:
      First call foofooSecond call foofoo
      證實(shí)了在相同 .py 文件中,裝飾器對(duì)所裝飾的函數(shù)只進(jìn)行一次裝飾,不會(huì)每次調(diào)用相應(yīng)函數(shù)時(shí)都重新裝飾,這個(gè)很容易理解,因?yàn)槠浔举|(zhì)等價(jià)于下面的函數(shù)簽名重新綁定:
      foo = decorator_a(decorator_b(foo))
      對(duì)于跨模塊的調(diào)用,我們編寫如下結(jié)構(gòu)的測(cè)試代碼:
      .├── 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()
      上述代碼通過(guò)創(chuàng)建 common.mod_a common.mod_b 兩個(gè)子模塊,并調(diào)用common.decorator 中的foo 函數(shù),來(lái)測(cè)試跨模塊時(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)過(guò)上面的驗(yàn)證,可以看出,對(duì)于跨模塊的調(diào)用,裝飾器也只會(huì)初始化一次,不過(guò)這要?dú)w功于 *.pyc,這與本文主題無(wú)關(guān),故不詳述。
      關(guān)于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問(wèn)題,更多的時(shí)候是我們?cè)O(shè)計(jì)上的問(wèn)題,下面給出一個(gè)初學(xué)裝飾器時(shí)大家都會(huì)遇到的一個(gè)問(wèn)題——丟失函數(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 對(duì) foo 函數(shù)進(jìn)行裝飾后,foo 的元信息會(huì)丟失,解決方案參見(jiàn): functools.wraps

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

      前面已經(jīng)講解過(guò)裝飾器的調(diào)用順序和調(diào)用時(shí)機(jī),但是被多個(gè)裝飾器裝飾的函數(shù),其運(yùn)行期行為還是有一些細(xì)節(jié)需要說(shuō)明的,而且很可能其行為會(huì)讓你感到驚訝,下面時(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')
      大家先來(lái)看一下運(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
      然而,接下來(lái),來(lái)自 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)該與裝飾器初始化階段的順序一致的話,那說(shuō)明大家沒(méi)有看透這段代碼的調(diào)用流程,下面我來(lái)為大家分析一下。
      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')
      對(duì)于只有 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)用過(guò)程等價(jià)于:
      login_tmp = login_debug_helper(show_debug_info=True)(login_a)login_a = logger(login_tmp)login_a('mdl', 'pwd')
      相信大家看過(guò)上面的等價(jià)變換后,已經(jīng)明白問(wèn)題出在哪里了,如果你還沒(méi)有明白,我強(qiáng)烈建議你把這個(gè)例子自己敲一遍,并嘗試用自己的方式進(jìn)行化簡(jiǎn),逐步得出結(jié)論。

      一些實(shí)例參考

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

      Python裝飾器與面向切面編程

      Python裝飾器小結(jié)

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

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

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

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

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

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

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

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多