裝飾器
裝飾器本質上是一個Python函數(shù),它可以讓其他函數(shù)在不需要做任何代碼變動的前提下增加額外功能杂腰,裝飾器的返回值也是一個函數(shù)對象森渐。
它經常用于有切面需求的場景,比如:插入日志臊泰、性能測試蛉加、事務處理、緩存缸逃、權限校驗等場景针饥。裝飾器是解決這類問題的絕佳設計,有了裝飾器需频,我們就可以抽離出大量與函數(shù)功能本身無關的雷同代碼并繼續(xù)重用丁眼。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能昭殉。
def foo():
print('i am foo')
現(xiàn)在有一個新的需求苞七,希望可以記錄下函數(shù)的執(zhí)行日志,于是在代碼中添加日志代碼:
def foo():
print('i am foo')
logging.info("foo is running")
bar()
挪丢、bar2()
也有類似的需求蹂风,怎么做?再寫一個logging在bar函數(shù)里乾蓬?這樣就造成大量雷同的代碼惠啄,為了減少重復寫代碼,我們可以這樣做任内,重新定義一個函數(shù):專門處理日志 撵渡,日志處理完之后再執(zhí)行真正的業(yè)務代碼
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()
def bar():
print('i am bar')
use_logging(bar)
邏輯上不難理解, 但是這樣的話死嗦,我們每次都要將一個函數(shù)作為參數(shù)傳遞給use_logging函數(shù)姥闭。而且這種方式已經破壞了原有的代碼邏輯結構,之前執(zhí)行業(yè)務邏輯時越走,執(zhí)行運行bar()棚品,但是現(xiàn)在不得不改成use_logging(bar)靠欢。那么有沒有更好的方式的呢?當然有铜跑,答案就是裝飾器门怪。
def use_logging(func): # 不帶參數(shù)的裝飾器 第一個def 參數(shù)就是func,帶參數(shù)的裝飾器锅纺,第一個def參數(shù)是裝飾元素參數(shù)掷空,第二個def參數(shù)才是func,注意區(qū)分(都是套路)
# 裝飾器就是某種閉包,總是return一些函數(shù)
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()
函數(shù)use_logging
就是裝飾器囤锉,它把執(zhí)行真正業(yè)務方法的func包裹在函數(shù)里面坦弟,看起來像bar被use_logging
裝飾了。在這個例子中官地,函數(shù)進入和退出時 酿傍,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)驱入。
@符號是裝飾器的語法糖赤炒,在定義函數(shù)的時候使用,避免再一次賦值操作
def use_logging(func):
# 不帶參數(shù)的裝飾器 第一個def 參數(shù)就是func亏较,帶參數(shù)的裝飾器莺褒,第一個def參數(shù)是裝飾元素參數(shù),第二個def參數(shù)才是func,注意區(qū)分(都是套路)
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper # return幾回雪情,最終回去的還是func
@use_logging
def foo():
print("i am foo")
@use_logging
def bar():
print("i am bar")
bar() # 相當于省去`bar = use_logging(bar)`
如上所示遵岩,這樣我們就可以省去bar = use_logging(bar)
這一句了,直接調用bar()
即可得到想要的結果巡通。如果我們有其他的類似函數(shù)旷余,我們可以繼續(xù)調用裝飾器來修飾函數(shù),而不用重復修改函數(shù)或者增加新的封裝扁达。這樣正卧,我們就提高了程序的可重復利用性,并增加了程序的可讀性跪解。
帶參數(shù)的裝飾器
裝飾器還有更大的靈活性炉旷,例如帶參數(shù)的裝飾器:在上面的裝飾器調用中,比如@use_logging
叉讥,該裝飾器唯一的參數(shù)就是執(zhí)行業(yè)務的函數(shù)窘行。裝飾器的語法允許我們在調用時,提供其它參數(shù)图仓,比如@decorator(a)
罐盔。這樣,就為裝飾器的編寫和使用提供了更大的靈活性救崔。
In [10]: import logging
In [11]: 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
...:
In [12]: @use_logging(level="warn")
...: def foo(name="foo"):
...: print('i am %s'% name)
...:
In [13]: foo()
/usr/local/bin/ipython3:5: DeprecationWarning: The 'warn' function is deprecated, use 'warning' instead
import sys
WARNING:root:foo is running
i am foo
In [14]: @use_logging(level)
...: def foo(name="foo"):
...: print('i am %s'% name)
...:
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-14-203ce5546292> in <module>()
----> 1 @use_logging(level)
2 def foo(name="foo"):
3 print('i am %s'% name)
4
NameError: name 'level' is not defined
上面的use_logging
是允許帶參數(shù)的裝飾器惶看。它實際上是對原有裝飾器的一個函數(shù)封裝捏顺,并返回一個裝飾器。我們可以將它理解為一個含有參數(shù)的閉包纬黎。當我 們使用@use_logging(level="warn")
調用的時候幅骄,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中本今。
類裝飾器
再來看看類裝飾器拆座,相比函數(shù)裝飾器,類裝飾器具有靈活度大冠息、高內聚挪凑、封裝性等優(yōu)點。使用類裝飾器還可以依靠類內部的__call__方法逛艰,當使用 @ 形式將裝飾器附加到函數(shù)上時躏碳,就會調用此方法。
In [19]: class Foo(object):
...: def __init__(self,func):
...: self._func = func
...: def __call__(self): # 內置的特殊函數(shù)
...: print('class decorator running')
...: self._func()
...: print('class decorator ending')
...:
In [20]: @Foo
...: def bar():
...: print('bar')
...:
In [21]: bar()
class decorator running
bar
class decorator ending
functools.wraps
使用裝飾器極大地復用了代碼瓮孙,但是他有一個缺點就是原函數(shù)的元信息不見了,比如函數(shù)的docstring
选脊、__name__
杭抠、參數(shù)列表,先看例子:
裝飾器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
函數(shù)
@logged
def f(x):
"""does some math"""
return x + x * x
該函數(shù)完成等價于:
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
不難發(fā)現(xiàn)恳啥,函數(shù)f
被with_logging
取代了偏灿,當然它的docstring
,__name__
就是變成了with_logging
函數(shù)的信息了钝的。
print f.__name__ # prints 'with_logging'
print f.__doc__ # prints None
這個問題就比較嚴重的翁垂,好在我們有functools.wraps
,wraps
本身也是一個裝飾器硝桩,它能把原函數(shù)的元信息拷貝到裝飾器函數(shù)中沿猜,這使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息了。
from functools import wraps
def logged(func):
@wraps(func) # 引入wraps,其他代碼不動碗脊,即解決問題
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'
內置裝飾器
@staticmathod啼肩、@classmethod、@property
裝飾器的順序
@a
@b
@c
def f ():
等效于
f = a(b(c(f)))
函數(shù)與方法
類的函數(shù)稱為方法(method)衙伶,模塊里的函數(shù)稱為函數(shù)(function)祈坠。凡是def foo()
這種,都是函數(shù)矢劲,在類中定義的函數(shù)赦拘,就是方法。
每一個包芬沉,模塊躺同,類阁猜,函數(shù),方法都應該包含文檔笋籽,包括類的__init__
方法
特殊函數(shù)__call__
所有的函數(shù)都是可調用對象蹦漠。
一個類實例也可以變成一個可調用對象,只需要實現(xiàn)一個特殊方法__call__()
车海。
我們把 Person 類變成一個可調用對象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
現(xiàn)在可以對 Person 實例直接調用:
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...
單看p('Tim')
你無法確定p
是一個函數(shù)還是一個類實例笛园,所以,在Python中侍芝,函數(shù)也是對象研铆,對象和函數(shù)的區(qū)別并不顯著。
函數(shù)封裝
函數(shù)封裝是一種函數(shù)的功能州叠,它把一個程序員寫的一個或者多個功能通過函數(shù)棵红、類的方式封裝起來,對外只提供一個簡單的函數(shù)接口咧栗。當程序員在寫程序的過程中需要執(zhí)行同樣的操作時逆甜,程序員(調用者)不需要寫同樣的函數(shù)來調用,直接可以從函數(shù)庫里面調用致板。程序員也可以從網絡上下載的功能函數(shù)交煞,然后封裝到[編譯器]的[庫函數(shù)]中,當需要執(zhí)行這一功能的函數(shù)時斟或,直接調用即可素征。而程序員不必知道函數(shù)內部如何實現(xiàn)的,只需要知道這個函數(shù)或者類提供什么功能萝挤。
語法糖
語法糖(Syntactic sugar),是由Peter J. Landin(和圖靈一樣的天才人物御毅,是他最先發(fā)現(xiàn)了Lambda演算,由此而創(chuàng)立了函數(shù)式編程)創(chuàng)造的一個詞語端蛆,它意指那些沒有給計算機語言添加新功能,而只是對人類來說更“甜蜜”的語法酥泛。語法糖往往給程序員提供了更實用的編碼方式欺税,有益于更好的編碼風格,更易讀揭璃。不過其并沒有給語言添加什么新東西晚凿。
對于列表形如list_1 = [[1, 2], [3, 4, 5], [6, 7], [8], [9]]轉化成列表list_2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]的問題。
一般方法
list_1 = [[1, 2], [3, 4, 5], [6, 7], [8], [9]]
list_2 = []
for _ in list_1:
list_2 += _
print(list_2)
更Pythonic的方法二瘦馍,列表推導
list_1 = [[1, 2], [3, 4, 5], [6, 7], [8], [9]]
[i for k in list_1 for i in k] # ??注意歼秽,這里沒有 , 號
抽象用法(知道就好但不推薦哦)
list_1 = [[1, 2], [3, 4, 5], [6, 7], [8], [9]]
sum(list_1, [])
sum
的第一個參數(shù)為可迭代對象即可,第二個參數(shù)默認為0
lambda表達式
函數(shù)式那一套黑魔法-語法糖
+1函數(shù)
f=lambda x:x+1
max
函數(shù)(條件語句的寫法如下)
f_max=lambda x,y:x if x>y else y
filter, map, reduce
filter
函數(shù)接受兩個參數(shù)情组,第一個是過濾函數(shù)燥筷,第二個是可遍歷的對象箩祥,用于選擇出所有滿足過濾條件的元素
去除小寫字母
s=filter(lambda x:not str(x).islower(),"asdasfAsfBsdfC")
for ch in s:
print(ch)
map
函數(shù)接受的參數(shù)類型與filter
類似,它用于把函數(shù)作用于可遍歷對象的每一個元素肆氓。類似于數(shù)學中映射的概念袍祖。
例:求y=2x+1(偷偷用了一下range函數(shù)生成定義域)
s=map(lambda x:2*x+1,range(6))
for x in s:
print(x)
range(6) = range(0, 6) = [ 0 ,1, 2, 3, 4, 5 ]
函數(shù)原型:range(start, end谢揪, scan):
range()
函數(shù)可創(chuàng)建一個整數(shù)列表蕉陋,一般用在 for 循環(huán)中計數(shù)從start開始到end結束,但不包括end,scan:每次跳躍的間距拨扶,默認為1
reduce
函數(shù)對每個元素作累計操作凳鬓,它接受的第一個參數(shù)必須是有兩個參數(shù)的函數(shù)。
In [54]: from functools import reduce
In [55]: s = reduce( lambda x,y: x+y, range(1,6) )
In [56]: print(s)
15
求乘積(第三個可選參數(shù)表示累計變量的初值)
from functools import reduce
s=reduce(lambda x,y:x*y,range(1,6),1)
print(s)
# 120
柯里化(curry)函數(shù)
如果一個函數(shù)需要2個參數(shù)患民,而你只傳入一個參數(shù)缩举,那么你就可以得到一個柯里化的函數(shù),這是函數(shù)式編程語言的重要特性之一
*3函數(shù)
f_mul=lambda x,y:x*y
from functools import partial
mul3=partial(f_mul,3)
print(mul3(1))
print(mul3(6))
注:匿名函數(shù)過多會影響代碼效率匹颤,勁酒雖好仅孩,不能貪杯
廖神關于裝飾器的解釋
如果decorator本身需要傳入?yún)?shù),那就需要編寫一個返回decorator的高階函數(shù)印蓖,寫出來會更復雜辽慕。比如,要自定義log的文本:
def log(text): # text是想打印出的裝飾提示另伍,func指代的就是需要被裝飾的函數(shù)鼻百,都是這樣的套路
def decorator(func): # 記住套路=事谩0诔ⅰ!
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
這個3層嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
執(zhí)行結果如下:
>>> now()
execute now():
2015-3-25
和兩層嵌套的decorator相比因悲,3層嵌套的效果是這樣的:
>>> now = log('execute')(now)
我們來剖析上面的語句堕汞,首先執(zhí)行log('execute')
,返回的是decorator
函數(shù)晃琳,再調用返回的函數(shù)讯检,參數(shù)是now
函數(shù),返回值最終是wrapper
函數(shù)卫旱。
以上兩種decorator的定義都沒有問題人灼,但還差最后一步。因為我們講了函數(shù)也是對象顾翼,它有__name__
等屬性投放,但你去看經過decorator裝飾之后的函數(shù),它們的__name__
已經從原來的'now'變成了'wrapper':
>>> now.__name__
'wrapper'
因為返回的那個wrapper()
函數(shù)名字就是'wrapper'适贸,所以灸芳,需要把原始函數(shù)的__name__
等屬性復制到wrapper()
函數(shù)中涝桅,否則,有些依賴函數(shù)簽名的代碼執(zhí)行就會出錯烙样。
不需要編寫wrapper.__name__ = func.__name__
這樣的代碼冯遂,Python內置的functools.wraps
就是干這個事的,所以谒获,一個完整的decorator的寫法如下:
import functools
def log(func):
@functools.wraps(func) # 加這一行就可以了蛤肌,其他代碼不變
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者針對帶參數(shù)的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func) # 加這一行就可以了,其他代碼不變
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator