轉載自:http://blog.csdn.net/mdl13412/article/details/22608283
前置知識
一級對象
Python將一切視爲 objec t的子類,即一切都是對象,因此函數可以像變量一樣被指向和傳遞,我們來看下面的例子:
其運行結果如下:
- def foo():
- pass
- print issubclass(foo.__class__, object)
上述代碼說明了Python 中的函數是 object 的子類,下面讓我們看函數被當作參數傳遞時的效果:
- True
其運行結果如下:
- def foo(func):
- func()
- def bar():
- print "bar"
- foo(bar)
- bar
Python中的namespace
Python中通過提供 namespace 來實現重名函數/方法、變量等信息的識別,其一共有三種 namespace,分別爲:
- local namespace: 作用範圍爲當前函數或者類方法
- global namespace: 作用範圍爲當前模塊
- build-in namespace: 作用範圍爲所有模塊
當函數/方法、變量等信息發生重名時,Python會按照 local namespace -> global namespace -> build-in namespace的順序搜索用戶所需元素,並且以第一個找到此元素的 namespace 爲準。
下面以系統的 build-in 函數 str 爲例進行說明:
首先定義三個 global namespace 的函數 str、foo 和bar,然後在 foo 函數中定義一個內嵌的 local namespace 的函數str,然後在函數 foo 和bar 中分別調用 str("dummy"),其運行結果如下所示:
- def str(s):
- print "global str()"
- def foo():
- def str(s):
- print "closure str()"
- str("dummy")
- def bar():
- str("dummy")
- foo()
- bar()
通過編碼實驗,我們可以看到:
- closure str()
- global str()
下面我們使用Python內置的 `ocals() 和 globals() 函數查看不同 namespace 中的元素定義:
- foo 中調用 str 函數時,首先搜索 local namespace,並且成功找到了所需的函數,停止搜索,使用此namespace 中的定義
- bar 中調用 str 函數時,首先搜索 local namespace,但是沒有找到str 方法的定義,因此繼續搜索 global namespace,併成功找到了str 的定義,停止搜索,並使用此定義
運行結果如下:
- var = "var in global"
- def fun():
- var = "var in fun"
- print "fun: " + str(locals())
- print "globals: " + str(globals())
- fun()
通過運行結果,我們看到了 fun 定義了 local namespace 的變量var,在 global namespace 有一個全局的 var 變量,那麼當在global namespace 中直接訪問 var 變量的時候,將會得到 var = "var in global" 的定義,而在fun 函數的 local namespace 中訪問 var 變量,則會得到fun 私有的 var = "var in fun" 定義。
- globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'a.py', '__package__': None, 'fun': <function fun at 0x7f2ca74f66e0>, 'var': 'var in global', '__name__': '__main__', '__doc__': None}
- fun: {'var': 'var in fun'}
*args and **kwargs
- *args: 把所有的參數按出現順序打包成一個 list
- **kwargs:把所有 key-value 形式的參數打包成一個 dict
下面給出一個 *args 的例子:
其運行結果如下:
- params_list = (1, 2)
- params_tupple = (1, 2)
- def add(x, y):
- print x + y
- add(*params_list)
- add(*params_tupple)
**kwargs 的例子:
- 3
- 3
其運行結果如下:
- params = {
- 'x': 1,
- 'y': 2
- }
- def add(x, y):
- print x + y
- add(**params)
- 3
閉包
下面給出一個使用閉包實現的logger factory的例子:
- 閉包在維基百科上的定義如下: 在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。
其運行結果如下:
- 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 logger
- logger_with_prefix = logger_facroty("Prefix: ")
- logger_without_prefix = logger_facroty(with_prefix=False)
- logger_with_prefix("msg")
- logger_without_prefix("msg")
在上面這個閉包的例子中,prefix 變量時所謂的自由變量,其在 return logger 執行完畢後,便脫離了創建它的環境logger_factory,但因爲其被logger_factory 中定義的 logger 函數所引用,其生命週期將至少和 logger 函數相同。這樣,在 logger 中就可以引用到logger_factory 作用域內的變量 prefix。
- Prefix: msg
- msg
將閉包與 namespace 結合起來:
其運行結果如下:
- 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_inner
- fun_outer()()
在這個例子中,當 fun_outer 被定義時,其內部的定義的 fun_inner 函數對 print "fun_inner: " + var中所引用的 var 變量進行搜索,發現第一個被搜索到的 var 定義在 fun_outer 的 local namespace 中,因此使用此定義,通過 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),當var 超出 fun_outer 的作用域後,依然存活,而 fun_outer 中的unused_var 變量由於沒有被 fun_inner 所引用,因此會被GC。
- fun_outer: var in fun_outer
- fun_outer: {'var': 'var in fun_outer', 'unused_var': 'this var is not used in fun_inner'}
- fun_outer: 140228141915584
- fun_inner: var in fun_outer
- fun_inner: {'var': 'var in fun_outer'}
- fun_inner: 140228141915584
探索裝飾器
定義
- 裝飾器在維基百科上的定義如下: A decorator is any callable Python object that is used to modify a function, method or class definition.
基本語法
語法糖
其等價於:
- @bar
- def foo():
- print "foo"
- def foo():
- print "foo"
- foo = bar(foo)
無參數裝飾器
foo 函數被用作裝飾器,其本身接收一個函數對象作爲參數,然後做一些工作後,返回接收的參數,供外界調用。
- def foo(func):
- print 'decorator foo'
- return func
- @foo
- def bar():
- print 'bar'
- bar()
注意: 時刻牢記 @foo 只是一個語法糖,其本質是 foo = bar(foo)
帶參數裝飾器
上述代碼想要實現一個性能分析器,並接收一個參數,來控制性能分析器是否生效,其運行效果如下所示:
- import time
- def 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)
上述代碼中裝飾器的調用等價於:
- add result: 2
- used time: 0
- mul result: 10
- add = function_performance_statistics(True)(add(1, 1))
- mul = function_performance_statistics(False)(mul(10))
類的裝飾器
類的裝飾器不常用,因此只簡單介紹。
上述代碼的 inject 裝飾器爲類動態的添加一個 bar 方法,因爲類在調用非靜態方法的時候會傳進一個self 指針,因此 bar 的第一個參數我們簡單的忽略即可,其運行結果如下:
- def bar(dummy):
- print 'bar'
- def inject(cls):
- cls.bar = bar
- return cls
- @inject
- class Foo(object):
- pass
- foo = Foo()
- foo.bar()
- bar
類裝飾器
類裝飾器相比函數裝飾器,具有靈活度大,高內聚、封裝性等優點。其實現起來主要是靠類內部的 __call__ 方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法,下面時一個實例:
其運行結果如下:
- class Foo(object):
- def __init__(self, func):
- super(Foo, self).__init__()
- self._func = func
- def __call__(self):
- print 'class decorator'
- self._func()
- @Foo
- def bar():
- print 'bar'
- bar()
- class decorator
- bar
內置裝飾器
Python中內置的裝飾器有三個: staticmethod、classmethod 和property
staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 指針,並且可以在類不進行實例化的情況下調用,下面是一個實例,對比靜態方法和成員方法
其運行結果如下:
- class Foo(object):
- @staticmethod
- def statc_method(msg):
- print msg
- def member_method(self, msg):
- print msg
- foo = Foo()
- foo.member_method('some msg')
- foo.statc_method('some msg')
- Foo.statc_method('some msg')
classmethod 與成員方法的區別在於所接收的第一個參數不是 self 類實例的指針,而是當前類的具體類型,下面是一個實例:
- some msg
- some msg
- some msg
其運行結果如下:
- 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()
property 是屬性的意思,即可以通過通過類實例直接訪問的信息,下面是具體的例子:
- <class '__main__.Foo'>
- <__main__.Foo object at 0x10a611c50>
注意: 如果將上面的 @var.setter 裝飾器所裝飾的成員函數去掉,則Foo.var 屬性爲只讀屬性,使用 foo.var = 'var 2' 進行賦值時會拋出異常,其運行結果如下:
- 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 = var
- foo = Foo('var 1')
- print foo.var
- foo.var = 'var 2'
- print foo.var
注意: 如果使用老式的Python類定義,所聲明的屬性不是 read only的,下面代碼說明了這種情況:
- var 1
- var 2
其運行結果如下:
- class Foo:
- def __init__(self, var):
- self._var = var
- @property
- def var(self):
- return self._var
- foo = Foo('var 1')
- print foo.var
- foo.var = 'var 2'
- print foo.var
- var 1
- var 2
調用順序
裝飾器的調用順序與使用 @ 語法糖聲明的順序相反,如下所示:
其等價於:
- def decorator_a(func):
- print "decorator_a"
- return func
- def decorator_b(func):
- print "decorator_b"
- return func
- @decorator_a
- @decorator_b
- def foo():
- print "foo"
- foo()
通過等價的調用形式我們可以看到,按照python的函數求值序列,decorator_b(fun) 會首先被求值,然後將其結果作爲輸入,傳遞給decorator_a,因此其調用順序與聲明順序相反。其運行結果如下所示:
- def decorator_a(func):
- print "decorator_a"
- return func
- def decorator_b(func):
- print "decorator_b"
- return func
- def foo():
- print "foo"
- foo = decorator_a(decorator_b(foo))
- foo()
- decorator_b
- decorator_a
- foo
調用時機
裝飾器很好用,那麼它什麼時候被調用?性能開銷怎麼樣?會不會有副作用?接下來我們就以幾個實例來驗證我們的猜想。
首先我們驗證一下裝飾器的性能開銷,代碼如下所示:其運行結果如下:
- def decorator_a(func):
- print "decorator_a"
- print 'func id: ' + str(id(func))
- return func
- def decorator_b(func):
- print "decorator_b"
- print 'func id: ' + str(id(func))
- return func
- print 'Begin declare foo with decorators'
- @decorator_a
- @decorator_b
- def 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))
在運行結果中的:
- Begin declare foo with decorators
- decorator_b
- func id: 140124961990488
- decorator_a
- func id: 140124961990488
- End declare foo with decorators
- First call foo
- foo
- Second call foo
- foo
- Function infos
- decorator_a id: 140124961954464
- decorator_b id: 140124961988808
- fooid : 140124961990488
證實了裝飾器的調用時機爲: 被裝飾對象定義時
- Begin declare foo with decorators
- decorator_b
- func id: 140124961990488
- decorator_a
- func id: 140124961990488
- End declare foo with decorators
而運行結果中的:
證實了在相同 .py 文件中,裝飾器對所裝飾的函數只進行一次裝飾,不會每次調用相應函數時都重新裝飾,這個很容易理解,因爲其本質等價於下面的函數簽名重新綁定:
- First call foo
- foo
- Second call foo
- foo
對於跨模塊的調用,我們編寫如下結構的測試代碼:
- foo = decorator_a(decorator_b(foo))
上述所有模塊中的 __init__.py 文件均爲: # -*- coding: utf-8 -*-
- .
- ├── common
- │ ├── decorator.py
- │ ├── __init__.py
- │ ├── mod_a
- │ │ ├── fun_a.py
- │ │ └── __init__.py
- │ └── mod_b
- │ ├── fun_b.py
- │ └── __init__.py
- └── test.py
- # -*- coding: utf-8 -*-
- # common/mod_a/fun_a.py
- from common.decorator import foo
- def fun_a():
- print 'in common.mod_a.fun_a.fun_a call foo'
- foo()
- # -*- coding: utf-8 -*-
- # common/mod_b/fun_b.py
- from common.decorator import foo
- def fun_b():
- print 'in common.mod_b.fun_b.fun_b call foo'
- foo()
- # -*- coding: utf-8 -*-
- # common/decorator.py
- def decorator_a(func):
- print 'init decorator_a'
- return func
- @decorator_a
- def foo():
- print 'function foo'
上述代碼通過創建 common.mod_a 和 common.mod_b 兩個子模塊,並調用common.decorator 中的 foo 函數,來測試跨模塊時裝飾器的工作情況,運行 test.py 的結果如下所示:
- # -*- coding: utf-8 -*-
- # test.py
- from common.mod_a.fun_a import fun_a
- from common.mod_b.fun_b import fun_b
- fun_a()
- fun_b()
經過上面的驗證,可以看出,對於跨模塊的調用,裝飾器也只會初始化一次,不過這要歸功於 *.pyc,這與本文主題無關,故不詳述。
- init decorator_a
- in common.mod_a.fun_a.fun_a call foo
- function foo
- in common.mod_b.fun_b.fun_b call foo
- function foo
關於裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時候是我們設計上的問題,下面給出一個初學裝飾器時大家都會遇到的一個問題——丟失函數元信息:
其運行結果如下所示:
- def decorator_a(func):
- def inner(*args, **kwargs):
- res = func(*args, **kwargs)
- return res
- return inner
- @decorator_a
- def 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()
我們可以看到,在使用 decorator_a 對 foo 函數進行裝飾後,foo 的元信息會丟失,解決方案參見: functools.wraps
- foo.__module__: __main__
- foo.__name__: inner
- foo.__doc__: None
- foo result
多個裝飾器運行期行爲
前面已經講解過裝飾器的調用順序和調用時機,但是被多個裝飾器裝飾的函數,其運行期行爲還是有一些細節需要說明的,而且很可能其行爲會讓你感到驚訝,下面時一個實例:
大家先來看一下運行結果,看看是不是跟自己想象中的一致:
- def tracer(msg):
- print "[TRACE] %s" % msg
- def logger(func):
- tracer("logger")
- def inner(username, password):
- tracer("inner")
- print "call %s" % func.__name__
- return func(username, password)
- return inner
- 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
- print '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 True
- print 'Call login_a'
- login_a("mdl", "pwd")
首先,裝飾器初始化時的調用順序與我們前面講解的一致,如下:
- Declaring login_a
- [TRACE] login_debug_helper
- [TRACE] proxy_fun
- [TRACE] logger
- Call login_a
- [TRACE] inner
- call delegate_fun
- [TRACE] delegate_fun
- username: mdl
- password: pwd
- [TRACE] login_a
- do some login authentication
然而,接下來,來自 logger 裝飾器中的 inner 函數首先被執行,然後纔是login_debug_helper 返回的 proxy_fun 中的 delegate_fun 函數。各位讀者發現了嗎,運行期執行login_a 函數的時候,裝飾器中返回的函數的執行順序是相反的,難道是我們前面講解的例子有錯誤嗎?其實,如果大家的認爲運行期調用順序應該與裝飾器初始化階段的順序一致的話,那說明大家沒有看透這段代碼的調用流程,下面我來爲大家分析一下。
- Declaring login_a
- [TRACE] login_debug_helper
- [TRACE] proxy_fun
- [TRACE] logger
當裝飾器 login_debug_helper 被調用時,其等價於:
- 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
對於只有 login_debug_helper 的情況,現在就應該是執行玩login_a輸出結果的時刻了,但是如果現在在加上logger 裝飾器的話,那麼這個login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')就被延遲執行,而將 login_debug_helper(show_debug_info=True)(login_a) 作爲參數傳遞給 logger,我們令 login_tmp = login_debug_helper(show_debug_info=True)(login_a),則調用過程等價於:
- login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')
相信大家看過上面的等價變換後,已經明白問題出在哪裏了,如果你還沒有明白,我強烈建議你把這個例子自己敲一遍,並嘗試用自己的方式進行化簡,逐步得出結論。
- login_tmp = login_debug_helper(show_debug_info=True)(login_a)
- login_a = logger(login_tmp)
- login_a('mdl', 'pwd')
一些實例參考
本文主要講解原理性的東西,具體的實例可以參考下面的鏈接:
Python裝飾器實例:調用參數合法性驗證
參考資料
Understanding Python Decorators in 12 Easy Steps
Decorators and Functional Python
Meta-matters: Using decorators for better Python programming