日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

Python 装饰器 函数

發(fā)布時(shí)間:2024/7/23 python 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python 装饰器 函数 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

Python裝飾器學(xué)習(xí)(九步入門):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
淺談Python裝飾器:https://blog.csdn.net/mdl13412/article/details/22608283
Python裝飾器與面向切面編程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

一些更加實(shí)用的 Python 裝飾器示例:https://sikasjc.github.io/2018/09/29/somedecorator/

Python裝飾器高級(jí)版—Python類內(nèi)定義裝飾器并傳遞self參數(shù):https://blog.51cto.com/yishi/2354752

?

?

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

?

這是在 Python學(xué)習(xí)小組上介紹的內(nèi)容,現(xiàn)學(xué)現(xiàn)賣、多練習(xí)是好的學(xué)習(xí)方式。

?

第一步:最簡(jiǎn)單的函數(shù),準(zhǔn)備附加額外功能

示例代碼:

# -*- coding:gbk -*- '''示例1: 最簡(jiǎn)單的函數(shù),表示調(diào)用了兩次'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return funcdef myfunc():print("? ? ?myfunc() called.")print('*****************************') dec_func = deco(myfunc)print('*****************************') dec_func()""" 結(jié)果: ***************************** before myfunc() called.myfunc() called. after myfunc() called. *****************************myfunc() called. """

?

第二步:使用裝飾函數(shù)在函數(shù)執(zhí)行前和執(zhí)行后分別附加額外功能

示例代碼:

# -*- coding:gbk -*- '''示例2: 替換函數(shù)(裝飾) 裝飾函數(shù)的參數(shù)是被裝飾的函數(shù)對(duì)象,返回原函數(shù)對(duì)象 裝飾的實(shí)質(zhì)語句: myfunc = deco(myfunc)'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return funcdef myfunc():print("? ? myfunc() called.")dec_func = deco(myfunc) print('****************************') dec_func() dec_func()""" 結(jié)果: before myfunc() called.myfunc() called. after myfunc() called. ****************************myfunc() called.myfunc() called. """

?

第三步:使用語法糖@來裝飾函數(shù)

本例中 deco 沒有使用內(nèi)嵌函數(shù),可以看到第一次執(zhí)行可以進(jìn)入到裝飾函數(shù),但是第二次不會(huì)進(jìn)入裝飾函數(shù)

# -*- coding:gbk -*- '''示例3: 使用語法糖@來裝飾函數(shù),相當(dāng)于“myfunc = deco(myfunc)” 但發(fā)現(xiàn)新函數(shù)只在第一次被調(diào)用,且原函數(shù)多調(diào)用了一次'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return func@deco def myfunc():print("? ? myfunc() called.")# 第一次調(diào)用后,返回的是 deco 里面的 func 對(duì)象, # 所以第二次調(diào)用myfunc() 只輸出 myfunc() called. myfunc()? # 第一次調(diào)用 print('************************') myfunc()? # 第二次調(diào)用""" 結(jié)果: before myfunc() called.myfunc() called. after myfunc() called.myfunc() called. ************************myfunc() called. """

?

第四步:使用 內(nèi)嵌包裝函數(shù) 來確保 每次 新函數(shù) 都被調(diào)用(?被裝飾的函數(shù)沒有參數(shù)

裝飾函數(shù) deco 返回內(nèi)嵌包裝函數(shù)對(duì)象 _deco。使用內(nèi)嵌包裝函數(shù)來確保每次新函數(shù)都被調(diào)用

# -*- coding:gbk -*- '''示例4: 使用內(nèi)嵌包裝函數(shù)來確保每次新函數(shù)都被調(diào)用, 內(nèi)嵌包裝函數(shù)的形參和返回值與原函數(shù)相同,裝飾函數(shù)返回內(nèi)嵌包裝函數(shù)對(duì)象'''def deco(func):def _deco():print("before myfunc() called.")func()print("after myfunc() called.")# 不需要返回func,實(shí)際上應(yīng)返回原函數(shù)的返回值return _deco@deco def myfunc():print("? ? myfunc() called.")return 'ok'myfunc() print('*************************') myfunc()""" 執(zhí)行結(jié)果: before myfunc() called.myfunc() called. after myfunc() called. ************************* before myfunc() called.myfunc() called. after myfunc() called. """

示例代碼:無參數(shù)的函數(shù)( 被裝飾的函數(shù)沒有參數(shù)

# decorator.pyfrom time import ctime, sleepdef time_fun(func):def wrapped_func():print(f"{func.__name__} called at {ctime()}")return func()return wrapped_func@time_fun def foo():passfoo() sleep(2) foo()

?

第五步:對(duì)?帶參數(shù)的函數(shù)?進(jìn)行裝飾(?被裝飾的函數(shù)帶參數(shù)?

示例代碼:

# -*- coding:gbk -*- '''示例5: 對(duì)帶參數(shù)的函數(shù)進(jìn)行裝飾, 內(nèi)嵌包裝函數(shù)的形參和返回值與原函數(shù)相同,裝飾函數(shù)返回內(nèi)嵌包裝函數(shù)對(duì)象'''def deco(func):def _deco(a, b):print("before myfunc() called.")ret = func(a, b)print("after myfunc() called. result: %s" % ret)return retreturn _deco@deco def myfunc(a, b):print(" myfunc(%s,%s) called." % (a, b))return a + bmyfunc(1, 2) print('*************************') myfunc(3, 4)""" 執(zhí)行結(jié)果: before myfunc() called.myfunc(1,2) called. after myfunc() called. result: 3 ************************* before myfunc() called.myfunc(3,4) called. after myfunc() called. result: 7 """

示例代碼:被裝飾的函數(shù)帶參數(shù)?

# decorator2.pyfrom time import ctime, sleepdef time_fun(func):def wrapped_func(a, b):print(f"{func.__name__} called at {ctime()}")print(a, b)return func(a, b)return wrapped_func@time_fun def foo(a, b):print(a + b)foo(3, 5) sleep(2) foo(2, 4)

?

第六步:對(duì) 參數(shù)數(shù)量不確定 的函數(shù)進(jìn)行裝飾

示例代碼:

# -*- coding:gbk -*- '''示例6: 對(duì)參數(shù)數(shù)量不確定的函數(shù)進(jìn)行裝飾, 參數(shù)用(*args, **kwargs),自動(dòng)適應(yīng)變參和命名參數(shù)'''def deco(func):def _deco(*args, **kwargs):print("before %s called." % func.__name__)ret = func(*args, **kwargs)print("after %s called. result: %s" % (func.__name__, ret))return retreturn _deco@deco def myfunc_1(a, b):print("? ? myfunc_1(%s,%s) called." % (a, b))return a + b@deco def myfunc_2(a, b, c):print("? ? myfunc_2(%s,%s,%s) called." % (a, b, c))return a + b + cprint('*' * 30) myfunc_1(1, 2) print('*' * 30) myfunc_1(3, 4) print('*' * 30) myfunc_2(1, 2, 3) print('*' * 30) myfunc_2(3, 4, 5)""" 執(zhí)行結(jié)果: ****************************** before myfunc_1 called.myfunc_1(1,2) called. after myfunc_1 called. result: 3 ****************************** before myfunc_1 called.myfunc_1(3,4) called. after myfunc_1 called. result: 7 ****************************** before myfunc_2 called.myfunc_2(1,2,3) called. after myfunc_2 called. result: 6 ****************************** before myfunc_2 called.myfunc_2(3,4,5) called. after myfunc_2 called. result: 12 """

?

第七步:讓 裝飾器 帶 參數(shù)

示例代碼:

# -*- coding:gbk -*- '''示例7: 在示例4的基礎(chǔ)上,讓裝飾器帶參數(shù), 和上一示例相比在外層多了一層包裝。 裝飾函數(shù)名實(shí)際上應(yīng)更有意義些'''def deco(arg):def _deco(func):def __deco():print("before %s called [%s]." % (func.__name__, arg))func()print("after %s called [%s]." % (func.__name__, arg))return __decoreturn _deco@deco("mymodule") def myfunc():print("? ? myfunc_1() called.")@deco("module2") def myfunc2():print("? ? myfunc_2() called.")print('************************************') myfunc() print('************************************') myfunc2()""" 執(zhí)行結(jié)果: ************************************ before myfunc called [mymodule].myfunc_1() called. after myfunc called [mymodule]. ************************************ before myfunc2 called [module2].myfunc_2() called. after myfunc2 called [module2]. """

裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上,設(shè)置外部變量

from time import ctime, sleepdef time_fun_arg(pre="hello"):def time_fun(func):def wrapped_func():print("%s called at %s %s"%(func.__name__, ctime(), pre))return func()return wrapped_funcreturn time_fun@time_fun_arg("12345") def foo():pass@time_fun_arg("abcde") def too():passfoo() sleep(2) foo()too() sleep(2) too()

?

第八步:讓 裝飾器 帶 類 參數(shù)

示例代碼:

# -*- coding:gbk -*- '''示例8: 裝飾器帶類參數(shù)'''class Locker:def __init__(self):print("locker.__init__() should be not called.")@staticmethoddef acquire():print("locker.acquire() called.(這是靜態(tài)方法)")@staticmethoddef release():print("locker.release() called.(不需要對(duì)象實(shí)例)")def deco(cls):'''cls 必須實(shí)現(xiàn)acquire和release靜態(tài)方法'''def _deco(func):def __deco():print("before %s called [%s]." % (func.__name__, cls))cls.acquire()try:return func()finally:cls.release()return __decoreturn _deco@deco(Locker) def myfunc():print("? ? myfunc() called.")print('*********************************************') myfunc() print('*********************************************') myfunc()""" 執(zhí)行結(jié)果: ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態(tài)方法)myfunc() called. locker.release() called.(不需要對(duì)象實(shí)例) ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態(tài)方法)myfunc() called. locker.release() called.(不需要對(duì)象實(shí)例) """

裝飾器 和 閉包 混用:

# coding=utf-8 from time import timedef logged(when):def log(f, *args, **kargs):print("fun:%s args:%r kargs:%r" % (f, args, kargs))# %r字符串的同時(shí),顯示原有對(duì)象類型def pre_logged(f):def wrapper(*args, **kargs):log(f, *args, **kargs)return f(*args, **kargs)return wrapperdef post_logged(f):def wrapper(*args, **kargs):now = time()try:return f(*args, **kargs)finally:log(f, *args, **kargs)print("time delta: %s" % (time() - now))return wrappertry:return {"pre": pre_logged, "post": post_logged}[when]except BaseException as e:print('must be "pre" or "post"')raise e@logged("post") def fun(name):print("Hello, ", name)fun("world!")

?

第九步:裝飾器帶類參數(shù),并分拆公共類到其他py文件中,

同時(shí) 演示了對(duì)一個(gè)函數(shù)應(yīng)用多個(gè)裝飾器

mylocker.py

# -*- coding:gbk -*-class MyLocker:def __init__(self):print("mylocker.__init__() called.")@staticmethoddef acquire():print("mylocker.acquire() called.")@staticmethoddef unlock():print("mylocker.unlock() called.")class LockerEx(MyLocker):@staticmethoddef acquire():print("lockerex.acquire() called.")@staticmethoddef unlock():print("lockerex.unlock() called.")def lock_helper(cls):"""cls 必須實(shí)現(xiàn)acquire和release靜態(tài)方法"""def _deco(func):def __deco(*args, **kwargs):print("before %s called." % func.__name__)cls.acquire()try:return func(*args, **kwargs)finally:cls.unlock()return __decoreturn _deco

測(cè)試代碼:

# -*- coding:gbk -*-""" 示例 9: 裝飾器帶類參數(shù),并分拆公共類到其他py文件中 同時(shí)演示了對(duì)一個(gè)函數(shù)應(yīng)用多個(gè)裝飾器 """class Example:@lock_helper(MyLocker)def func_1(self):print("\nfunc_1() called.")@lock_helper(MyLocker)@lock_helper(LockerEx)def func_2(self, a, b):print("\nfunc_2() called.")return a + bif __name__ == "__main__":a = Example()a.func_1()print(a.func_1())print(a.func_2(1, 2))print(a.func_2(3, 4))""" 執(zhí)行結(jié)果: before func_1 called. mylocker.acquire() called.func_1() called. mylocker.unlock() called. before func_1 called. mylocker.acquire() called.func_1() called. mylocker.unlock() called. None before __deco called. mylocker.acquire() called. before func_2 called. lockerex.acquire() called.func_2() called. lockerex.unlock() called. mylocker.unlock() called. 3 before __deco called. mylocker.acquire() called. before func_2 called. lockerex.acquire() called.func_2() called. lockerex.unlock() called. mylocker.unlock() called. 7 """

?

下面是參考資料,當(dāng)初有不少地方?jīng)]看明白,真正練習(xí)后才明白些:

1. Python裝飾器學(xué)習(xí):http://blog.csdn.net/thy38/article/details/4471421

2. Python裝飾器與面向切面編程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

3. Python裝飾器的理解:http://apps.hi.baidu.com/share/detail/17572338

?

?

Python 裝飾器的 4 種類型

?

  • 函數(shù)? 裝飾? 函數(shù)
  • 函數(shù)? 裝飾?
  • ? 裝飾? 函數(shù)
  • ? 裝飾?

?

@wraps(func) 要使用這個(gè)必須導(dǎo)入functools,這個(gè)作用是消除(被裝飾后的函數(shù)名等屬性的改變)副作用

參考:https://blog.csdn.net/freeking101/article/details/109662542

from functools import wrapsdef decorate(src_func):@wraps(src_func)def wrapper():print("hello")src_func()print("world")return wrapper@decorate def func():print("2019-12-31")print(func.__name__, func.__doc__)# 加上 @wraps(src_func) 輸出為func None # 不加 輸出為 wrapper None

?

一:函數(shù) 裝飾 函數(shù)

def wrapFun(func):def inner(a, b):print('function name:', func.__name__)r = func(a, b)return rreturn inner@wrapFun def myadd(a, b):return a + bprint(myadd(2, 3))

?

裝飾不帶參數(shù)的函數(shù)

# -*- coding: utf-8 -*-def clothes(func):def wear():print('Buy clothes!{}'.format(func.__name__))return func()return wear@clothes def body():print('The body feels cold!')body()#備注:@是語法糖 # 不用語法糖的情況下,使用下面語句也能實(shí)現(xiàn)裝飾作用:把body再加工,再傳給body # body = clothes(body)

示例 :

def deco(func):def _deco(*args, **kwargs):print('call deco')func(*args, **kwargs)return _deco@deco def test():print('call test')# 等同于 # def test(): # print('call test') # test = deco(func)

?

裝飾帶一個(gè)參數(shù)的函數(shù)

# -*- coding: utf-8 -*-def clothes(func):def wear(anything): # 實(shí)際是定義一個(gè)anything參數(shù),對(duì)應(yīng)body函數(shù)參數(shù)print('Buy clothes!{}'.format(func.__name__))return func(anything) # 執(zhí)行func函數(shù),并傳入調(diào)用傳入的anything參數(shù)# wear = func(anything) # 在這一步,實(shí)際上可以看出來,為啥wear必須帶參數(shù),因?yàn)樗褪莊unc(anything)return wear# 所以clothes的結(jié)果是# clothes = wear = func(anything)# 不用語法糖的情況下就是# body = clothes(body)('hands')# 進(jìn)一步證明:print(body.__name__) 顯示的是wear函數(shù)@clothes def body(part):print('The body feels could!{}'.format(part))body('hands')

?

裝飾帶不定長參數(shù)的函數(shù)

通常裝飾器不只裝飾一個(gè)函數(shù),每個(gè)函數(shù)參數(shù)的個(gè)數(shù)也不相同,這個(gè)時(shí)候使用不定長參數(shù)*args,**kwargs

def clothes(func):def wear(*args, **kwargs):print('Buy clothes!{}'.format(func.__name__))return func(*args, **kwargs)return wear@clothes def body(part):print('The body feels could!{}'.format(part))@clothes def head(head_wear, num=2):print('The head need buy {} {}!'.format(num, head_wear))body('hands') head('headdress')

?

裝飾器帶參數(shù)

# 把裝飾器再包裝,實(shí)現(xiàn)了seasons傳遞裝飾器參數(shù)。def seasons(season_type):def clothes(func):def wear(*args, **kwargs):if season_type == 1:s = 'spring'elif season_type == 2:s = 'summer'elif season_type == 3:s = 'autumn'elif season_type == 4:s = 'winter'else:print('The args is error!')return func(*args, **kwargs)print('The season is {}!{}'.format(s, func.__name__))return func(*args, **kwargs)return wearreturn clothes@seasons(2) def children():print('i am children')

示例:

def deco(*args, **kwargs):def _deco(func):print(args, kwargs)def __deco(*args, **kwargs):print('call deco')func(*args, **kwargs)return __decoreturn _deco@deco('hello', x='ni hao') def test():print('call test')# 等同于 # def test(): # print('call test') # test = deco('hello', x='ni hao')(test)

?

二:函數(shù) 裝飾 類

示例:

def deco(*args, **kwargs):def _deco(cls):cls.x = 12return clsreturn _deco@deco('hello') class A(object):pass>>> A.x 12# 等同于 # class A(object): # pass # A = deco('hello')(A)

示例:

def wrapper_class(cls):def inner(a):print('class name:', cls.__name__)return cls(a)return inner@wrapper_class class Foo(object):def __init__(self, a):self.a = adef fun(self):print('self.a =', self.a)m = Foo('Are you OK!') m.fun()

示例:

?

?

創(chuàng)建單例(Singletons)

單例是一個(gè)只有一個(gè)實(shí)例的類。Python中有幾種常用的單例,包括None、True和False。事實(shí)上,None是一個(gè)單例,允許你使用is關(guān)鍵字比較None。

示例:下面的?@singleton?裝飾器將類的第一個(gè)實(shí)例存儲(chǔ)為屬性,從而將類轉(zhuǎn)換為單例對(duì)象。稍后創(chuàng)建實(shí)例的嘗試只是返回存儲(chǔ)的實(shí)例:

import functoolsdef singleton(cls):"""Make a class a Singleton class (only one instance)"""@functools.wraps(cls)def wrapper_singleton(*args, **kwargs):if not wrapper_singleton.instance:wrapper_singleton.instance = cls(*args, **kwargs)return wrapper_singleton.instancewrapper_singleton.instance = Nonereturn wrapper_singleton@singleton class TheOne:pass

如上所示,這個(gè)類裝飾器遵循與我們的函數(shù)裝飾器相同的模板。唯一的區(qū)別是,我們使用cls作為參數(shù)名來表示它是一個(gè)類裝飾器,而不是func。

運(yùn)行效果:

>>> first_one = TheOne() >>> another_one = TheOne()>>> id(first_one) 140094218762280>>> id(another_one) 140094218762280>>> first_one is another_one True

很明顯,first_one確實(shí)與another_one完全相同。

在Python中,單例類的使用并不像在其他語言中那樣頻繁。單例的效果通常在模塊中作為全局變量更好地實(shí)現(xiàn)。

?

三: 裝飾 函數(shù)、方法

?

裝飾 函數(shù)

定義一個(gè)類裝飾器,裝飾函數(shù),默認(rèn)調(diào)用 __call__ 方法

class Decorator(object):def __init__(self, func): # 傳送的是test方法self.func = funcdef __call__(self, *args, **kwargs): # 接受任意參數(shù)print('函數(shù)調(diào)用CALL')return self.func(*args, **kwargs) # 適應(yīng)test的任意參數(shù) @Decorator # 如果帶參數(shù),init中的func是此參數(shù)。 def test(hh):print('this is the test of the function !', hh)test('hh')

示例:

class ShowFunName(object):def __init__(self, func):self._func = funcdef __call__(self, a):print('function name:', self._func.__name__)return self._func(a)@ShowFunName def bar(a):return aprint(bar('Are you OK'))

無參數(shù)

class Deco(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print('call Deco')self.func(*args, **kwargs)@Deco def test():print('call test')# 等同于 test = Deco(test)

有參數(shù)

class Deco(object):def __init__(self, *args, **kwargs):print(args, kwargs)def __call__(self, func):def _deco(*args, **kwargs):print('call Deco')func(*args, **kwargs)return _deco@Deco('hello') def test():print('call test')# 等同于 # test = Deco('hello')(func)

?

裝飾 方法

????定義一個(gè)類裝飾器,裝飾類中的函數(shù),默認(rèn)調(diào)用__get__方法

????實(shí)際上把類方法變成屬性了,還記得類屬性裝飾器吧,@property

????下面自已做一個(gè)property

class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):"""instance:代表實(shí)例,sum中的selfowner:代表類本身,Test類"""print('調(diào)用的是get函數(shù)')return self.func(instance) # instance就是Test類的selfclass Test(object):def __init__(self):self.result = 0@Decoratordef sum(self):print('There is the Func in the Class !')t = Test() print(t.sum) # 眾所周知,屬性是不加括號(hào)的,sum真的變成了屬性

示例:

做一個(gè)求和屬性sum,統(tǒng)計(jì)所有輸入的數(shù)字的和

class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):print('調(diào)用的是get函數(shù)')return self.func(instance)class Test(object):def __init__(self, *args, **kwargs):self.value_list = []if args:for i in args:if str(i).isdigit():self.value_list.append(i)if kwargs:for v in kwargs.values():if str(v).isdigit():self.value_list.append(v)@Decoratordef sum(self):result = 0print(self.value_list)for i in self.value_list:result += ireturn resultt = Test(1, 2, 3, 4, 5, 6, 7, 8, i=9, ss=10, strings='lll')print(t.sum)

無參數(shù)

class Deco(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):def _deco(*args, **kwargs):print('call Deco')self.func(instance, *args, **kwargs)return _decoclass A(object):@Decodef test(self):print('call test')# 等同于 # class A(object): # def test(self): # print('call test') # test = Deco(test)

有參數(shù)

class Deco(object):def __init__(self, *args, **kwargs):print(args, kwargs)def __get__(self, instance, owner):def _deco(*args, **kwargs):print('call Deco')self.func(instance, *args, **kwargs)return _decodef __call__(self, func):self.func = funcreturn selfclass A(object):@Deco('hello')def test(self):print('call test')# 等同于 # class A(object): # def test(self): # print('call test') # test = Deco('hello')(test)

?

四:類 裝飾 類

class ShowClassName(object):def __init__(self, cls):self._cls = clsdef __call__(self, a):print('class name:', self._cls.__name__)return self._cls(a)@ShowClassName class Foobar(object):def __init__(self, a):self.value = adef fun(self):print(self.value)a = Foobar('Are you OK') a.fun()

?

函數(shù) 裝飾 類中的方法

示例代碼 1:

From:https://www.cnblogs.com/xieqiankun/p/python_decorate_method.html

import time import datetime import requestsdef cost_time(file_name, f_or_m=1):def _deco(origin_func):if 'f' == f_or_m:def wrapper(*args, **kwargs):start_time = datetime.datetime.now()ret_val = origin_func(*args, **kwargs)end_time = datetime.datetime.now()ct = (end_time - start_time).secondsprint(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')return ret_valreturn wrapperelif 'm' == f_or_m:def wrapper(self, *args, **kwargs):start_time = datetime.datetime.now()ret_val = origin_func(self, *args, **kwargs)end_time = datetime.datetime.now()ct = (end_time - start_time).secondsprint(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')return ret_valreturn wrapperreturn _decoclass Test(object):def __init__(self):pass@cost_time(__file__, 'm')def test_1(self):time.sleep(2)pass@cost_time(__file__, 'm')def test_2(self):r = requests.get('http://www.baidu.com')if 200 == r.status_code:print(r.status_code)else:print(r.status_code)if __name__ == '__main__':t = Test()t.test_1()t.test_1()t.test_2()pass

運(yùn)行結(jié)果:

[test_1] cost_time:3 [test_1] cost_time:3 200 [test_2] cost_time:0

?

示例代碼 2:

From:https://www.kingname.info/2017/04/17/decorate-for-method/

使用 Python 的裝飾器裝飾一個(gè)類的方法,同時(shí)在裝飾器函數(shù)中調(diào)用類里面的其他方法。這里以捕獲一個(gè)方法的異常為例來進(jìn)行說明。

有一個(gè)類Test, 它的結(jié)構(gòu)如下:

class Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restoredef read_value(self):print('here I will do something.')# do something.

在類中有一個(gè)方法read_value(),這個(gè)方法在多個(gè)地方被調(diào)用。由于某些原因,方法read_value有可能隨機(jī)拋出Exception導(dǎo)致程序崩潰。所以需要對(duì)整個(gè)方法做try ... except處理。最丑陋的做法如下面的代碼所示:

class Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restoredef read_value(self):try:print('here I will do something.')# do something.except Exception as e:print(f'exception {e} raised, parse exception.')# do other thing.self.revive()

這樣寫雖然可以解決問題,但是代碼不Pythonic。

使用裝飾器來解決這個(gè)問題,裝飾器函數(shù)應(yīng)該寫在類里面還是類外面呢?答案是,寫在類外面。那么既然寫在類外面,如何調(diào)用這個(gè)類的其他方法呢?

首先寫出一個(gè)最常見的處理異常的裝飾器:

def catch_exception(origin_func):def wrapper(*args, **kwargs):try:u = origin_func(*args, **kwargs)return uexcept Exception:return 'an Exception raised.'return wrapperclass Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restore@catch_exceptiondef read_value(self):print('here I will do something.')# do something.

這種寫法,確實(shí)可以捕獲到?origin_func()的異常,但是如果在發(fā)生異常的時(shí)候,需要調(diào)用類里面的另一個(gè)方法來處理異常,這又應(yīng)該怎么辦?答案是給 wrapper 增加一個(gè)參數(shù):self.

代碼變?yōu)槿缦滦问?#xff1a;

def catch_exception(origin_func):def wrapper(self, *args, **kwargs):try:u = origin_func(self, *args, **kwargs)return uexcept Exception:self.revive() #不用顧慮,直接調(diào)用原來的類的方法return 'an Exception raised.'return wrapperclass Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restore@catch_exceptiondef read_value(self):print('here I will do something.')# do something.

執(zhí)行結(jié)果:

?

示例代碼 3:

import functoolsdef auto_retry(src_func):@functools.wraps(src_func)def wrapper(*args, **kwargs):for i in range(3):try:return src_func(*args, **kwargs)except Exception as e:print(src_func.__name__, e)return wrapperclass TestClass(object):def __init__(self):passdef __del__(self):pass@auto_retrydef crawl(self, url=None):raise Exception('crawl exception')passTestClass().crawl()

?

?

類中的方法 裝飾 方法函數(shù)

  • 方法:類中的成員函數(shù)叫做 方法
  • 函數(shù):不在類中的函數(shù),即普通函數(shù)叫做 函數(shù)

?

類中的方法 裝飾 函數(shù)

在類里面定義個(gè)函數(shù),用來裝飾其它函數(shù),嚴(yán)格意義上說不屬于類裝飾器。

class Buy(object):def __init__(self, func):self.func = func# 在類里定義一個(gè)函數(shù)def clothes(func): # 這里不能用self,因?yàn)榻邮盏氖莃ody函數(shù)# 其它都和普通的函數(shù)裝飾器相同def ware(*args, **kwargs):print('This is a decorator!')return func(*args, **kwargs)return ware@Buy.clothes def body(hh):print('The body feels could!{}'.format(hh))body('hh')

?

類中的方法 裝飾 類中的方法

????背景:想要通過裝飾器修改類里的self屬性值。

class Buy(object):def __init__(self):self.reset = True # 定義一個(gè)類屬性,稍后在裝飾器里更改self.func = True# 在類里定義一個(gè)裝飾器def clothes(func): # func接收bodydef ware(self, *args, **kwargs): # self,接收body里的self,也就是類實(shí)例print('This is a decrator!')if self.reset == True: # 判斷類屬性print('Reset is Ture, change Func..')self.func = False # 修改類屬性else:print('reset is False.')return func(self, *args, **kwargs)return ware@clothesdef body(self):print('The body feels could!')b = Buy() # 實(shí)例化類 b.body() # 運(yùn)行body print(b.func) # 查看更改后的self.func值,是False,說明修改完成

?

?

?

?

?

前置知識(shí)

?

閉 包

?

javascript 閉包:https://www.runoob.com/js/js-function-closures.html

? ? ? ? 閉包是一種保護(hù)私有變量的機(jī)制,在函數(shù)執(zhí)行時(shí)形成私有的作用域,保護(hù)里面的私有變量不受外界干擾。直觀的說:就是形成一個(gè)不銷毀的棧環(huán)境

? ? ? ? 閉包在維基百科上的定義如下:?在計(jì)算機(jī)科學(xué)中,閉包(Closure)是詞法閉包(Lexical?Closure)的簡(jiǎn)稱,是引用了自由變量的函數(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_factory(prefix="", with_prefix=True):if with_prefix:def logger(msg):print(prefix + msg)return loggerelse:def logger(msg):print(msg)return loggerlogger_with_prefix = logger_factory("Prefix: ") logger_without_prefix = logger_factory(with_prefix=False) logger_with_prefix("msg") logger_without_prefix("msg")

運(yùn)行結(jié)果:

Prefix: msg msg

在上面這個(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_outer fun_outer: {'unused_var': 'this var is not used in fun_inner', 'var': 'var in fun_outer'} fun_outer: 2543733314784 fun_inner: var in fun_outer fun_inner: {'var': 'var in fun_outer'} fun_inner: 2543733314784

在這個(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_outer?的?local namespace?中,因此使用此定義,通過?print "fun_outer: " + str(id(var))?和?print "fun_inner: " + str(id(var)),當(dāng)var?超出?fun_outer?的作用域后,依然存活,而?fun_outer?中的unused_var?變量由于沒有被?fun_inner?所引用,因此會(huì)被?GC。

?

?

什么是閉包?

?

內(nèi)部函數(shù)對(duì)外部函數(shù)作用域里變量的引用(非全局變量),則稱內(nèi)部函數(shù)為閉包。

簡(jiǎn)單說,閉包就是根據(jù)不同的配置信息得到不同的結(jié)果

再來看看專業(yè)的解釋:閉包(Closure)是詞法閉包(Lexical Closure)的簡(jiǎn)稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。

?

Python 閉包示例

示例 1:

def make_adder(arg_1=None):def adder(arg_2=None):return arg_1 + arg_2return adderp = make_adder(23) q = make_adder(44)print(p(100)) print(q(100))# 運(yùn)行結(jié)果: # 123 # 144

分析一下:

我們發(fā)現(xiàn)?make_adder?是一個(gè)函數(shù),包括一個(gè)參數(shù)?arg_1 ,比較特殊的地方是這個(gè)函數(shù)里面又定義了一個(gè)新函數(shù),這個(gè)新函數(shù)里面的一個(gè)變量正好是外部?make_adder?的參數(shù)。也就是說,外部傳遞過來的?arg_1 參數(shù)已經(jīng)和?adder?函數(shù)綁定到一起了,形成了一個(gè)新函數(shù),我們可以把??arg_1?看做新函數(shù)的一個(gè)配置信息,配置信息不同,函數(shù)的功能就不一樣了,也就是能得到定制之后的函數(shù).

再看看運(yùn)行結(jié)果,我們發(fā)現(xiàn),雖然 p 和 q 都是?make_adder?生成的,但是因?yàn)榕渲脜?shù)不同,后面再執(zhí)行相同參數(shù)的函數(shù)后得到了不同的結(jié)果。這就是閉包。

?

示例 2:

def hello_counter(name):count = [0]def counter():count[0] += 1print('Hello,', name, ',', str(count[0]) + ' access!')return counterhello = hello_counter('king') hello() hello() hello()# 執(zhí)行結(jié)果 # Hello, king , 1 access! # Hello, king , 2 access! # Hello, king , 3 access!

分析一下

這個(gè)程序比較有趣,我們可以把這個(gè)程序看做統(tǒng)計(jì)一個(gè)函數(shù)調(diào)用次數(shù)的函數(shù)。count[0]可以看做一個(gè)計(jì)數(shù)器,每執(zhí)行一次?hello?函數(shù),count[0]?的值就加 1。也許你會(huì)有疑問:為什么不直接寫?count?而用一個(gè)列表? 這是 python2 的一個(gè)bug,如果不用列表的話,會(huì)報(bào)這樣一個(gè)錯(cuò)誤:UnboundLocalError: local variable 'count' referenced before assignment.

什么意思? 就是說?conut?這個(gè)變量你沒有定義就直接引用了,我不知道這是個(gè)什么東西,程序就崩潰了。于是在 python3 里面引入了一個(gè)關(guān)鍵字:nonlocal,這個(gè)關(guān)鍵字是干什么的? 就是告訴 python 程序,我的這個(gè)?count?變量是在外部定義的,你去外面找吧。然后 python 就去外層函數(shù)找,然后就找到了?count=0?這個(gè)定義和賦值,程序就能正常執(zhí)行了。

?

示例 2 改進(jìn):

def hello_counter(name):count = 0def counter():nonlocal countcount += 1print('Hello,', name, ',', str(count) + ' access!')return counterhello = hello_counter('king') hello() hello() hello()

關(guān)于這個(gè)問題的研究您可以參考:http://linluxiang.iteye.com/blog/789946

?

示例 3:

def make_bold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef make_italic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@make_bold @make_italic def hello():return "hello world"print(hello())# 執(zhí)行結(jié)果 # <b><i>hello world</i></b>

簡(jiǎn)單分析

怎么樣? 這個(gè)程序熟悉嗎? 這不是傳說的的裝飾器嗎? 對(duì),這就是裝飾器,其實(shí),裝飾器就是一種閉包,

我們?cè)倩叵胍幌?strong>裝飾器的概念:對(duì)函數(shù)(參數(shù),返回值 等)進(jìn)行加工處理,生成一個(gè)功能增強(qiáng)版的一個(gè)函數(shù)。

再看看閉包的概念,這個(gè)增強(qiáng)版的函數(shù)不就是我們配置之后的函數(shù)嗎 ?

區(qū)別在于,裝飾器的參數(shù)是一個(gè) 函數(shù) ,專門對(duì) 函數(shù) 進(jìn)行加工處理。

Python 里面的好多高級(jí)功能,比如裝飾器,生成器,列表推到,閉包,匿名函數(shù)等。

?

?

?

探索 裝飾器

定義

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

    基本語法

    語法糖

    @bar def foo():print("foo")

    其等價(jià)于:

    def foo(): print("foo") foo = bar(foo)

    ?

    無參數(shù) 裝飾器

    def foo(func):print('decorator foo')return func@foo def bar():print('bar')bar()

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

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

    ?

    帶參數(shù) 的 裝飾器

    示例代碼:

    import timedef function_performance_statistics(trace_this=True):if trace_this:def performance_statistics_delegate(func):def counter(*args, **kwargs):start = time.perf_counter()func(*args, **kwargs)end = time.perf_counter()print('used time: %d' % (end - start,))return counterelse:def performance_statistics_delegate(func):return funcreturn performance_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: 2 used time: 3 mul result: 10

    上述代碼中裝飾器的調(diào)用等價(jià)于:

    import timedef function_performance_statistics(trace_this=True):if trace_this:def performance_statistics_delegate(func):def counter(*args, **kwargs):start = time.perf_counter()func(*args, **kwargs)end = time.perf_counter()print('used time: %d' % (end - start,))return counterelse:def performance_statistics_delegate(func):return funcreturn performance_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 = 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 = barreturn cls@inject class 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)起來主要是靠類內(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 = funcdef __call__(self):print('class decorator')self._func()@Foo def bar():print('bar')bar()

    運(yùn)行結(jié)果如下:

    class decorator bar

    ?

    ?

    內(nèi)置裝飾器

    Python 中內(nèi)置的裝飾器有三個(gè):?staticmethod、classmethod?和?property

    ?

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

    下面是一個(gè)實(shí)例,對(duì)比靜態(tài)方法 和 成員方法:

    class Foo(object):def __init__(self):super(Foo, self).__init__()self.msg = 'hello word'@staticmethoddef static_method(msg):print(msg)def member_method(self, msg=None):msg = msg if msg else self.msgprint(msg)foo = Foo() foo.member_method('some msg') foo.static_method('some msg') Foo.static_method('some msg')

    ?

    classmethod?與成員方法的區(qū)別在于所接收的第一個(gè)參數(shù)不是?self?類實(shí)例的指針,而是當(dāng)前類的具體類型,

    下面是一個(gè)實(shí)例:

    class Foo(object):@classmethoddef class_method(cls):print(repr(cls))def member_method(self):print(repr(self))foo = Foo() foo.class_method() foo.member_method()

    運(yùn)行結(jié)果如下:

    <class '__main__.Foo'> <__main__.Foo object at 0x000002895A412508>

    ?

    property?是屬性的意思,即可以通過通過類實(shí)例直接訪問的信息,下面是具體的例子:

    class Foo(object):def __init__(self, var):super(Foo, self).__init__()self._var = var@propertydef var(self):return self._var@var.setterdef var(self, var):self._var = varfoo = Foo('var 1') print(foo.var) foo.var = 'var 2' print(foo.var)

    注意:?如果將上面的?@var.setter?裝飾器所裝飾的成員函數(shù)去掉,則?Foo.var?屬性為只讀屬性,使用?foo.var = 'var 2'?進(jìn)行賦值時(shí)會(huì)拋出異常,其運(yùn)行結(jié)果如下:

    var 1 var 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_b def 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)?會(huì)首先被求值,然后將其結(jié)果作為輸入,傳遞給?decorator_a,因此其調(diào)用順序與聲明順序相反。

    其運(yùn)行結(jié)果如下所示:

    decorator_b decorator_a foo

    ?

    ?

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

    裝飾器很好用,那么它什么時(shí)候被調(diào)用?性能開銷怎么樣?會(huì)不會(huì)有副作用?接下來我們就以幾個(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_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('foo id : ' + str(id(foo)))

    運(yùn)行結(jié)果如下:

    Begin declare foo with decorators decorator_b func id: 1474741780056 decorator_a func id: 1474741780056 End declare foo with decorators First call foo foo Second call foo foo Function infos decorator_a id: 1474740396104 decorator_b id: 1474740398552 foo id : 1474741780056

    ?

    在運(yùn)行結(jié)果中:

    Begin declare foo with decorators decorator_b func id: 1474741780056 decorator_a func id: 1474741780056 End declare foo with decorators

    證實(shí)了裝飾器的調(diào)用時(shí)機(jī)為: 被裝飾對(duì)象定義時(shí)

    ?

    而運(yùn)行結(jié)果中的:

    First call foo foo Second call foo foo

    證實(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 -*-

    common/mod_a/fun_a.py

    # -*- coding: utf-8 -*- # common/mod_a/fun_a.py from common.decorator import foodef fun_a():print('in common.mod_a.fun_a.fun_a call foo') foo()

    common/mod_b/fun_b.py??

    # -*- coding: utf-8 -*- # common/mod_b/fun_b.py from common.decorator import foodef fun_b():print('in common.mod_b.fun_b.fun_b call foo') foo()

    common/decorator.py??

    # -*- coding: utf-8 -*- # common/decorator.pydef decorator_a(func):print('init decorator_a')return func@decorator_a def foo():print('function foo')

    test.py

    # -*- coding: utf-8 -*- # test.pyfrom common.mod_a.fun_a import fun_a from 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ù),來測(cè)試跨模塊時(shí)裝飾器的工作情況,運(yùn)行?test.py?的結(jié)果如下所示:

    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

    經(jīng)過上面的驗(yàn)證,可以看出,對(duì)于跨模塊的調(diào)用,裝飾器也只會(huì)初始化一次,不過這要?dú)w功于?*.pyc,這與本文主題無關(guān),故不詳述。

    ?

    ?

    裝飾器副作用

    關(guān)于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時(shí)候是我們?cè)O(shè)計(jì)上的問題,

    下面給出一個(gè)初學(xué)裝飾器時(shí)大家都會(huì)遇到的一個(gè)問題 —— 丟失函數(shù)元信息:

    def decorator_a(func):def inner(*args, **kwargs):res = func(*args, **kwargs)return resreturn inner@decorator_a def foo():"""foo doc:return:"""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__: inner foo.__doc__: None foo result

    可以看到,在使用 decorator_a 對(duì) foo 函數(shù)進(jìn)行裝飾后,foo 的元信息會(huì)丟失,解決方案參見:?functools.wraps

    ?

    ?

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

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

    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 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(f"username:{username}\npassword:{password}")return func(username, password)return delegate_funreturn 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] logger Call login_a [TRACE] inner call delegate_fun [TRACE] delegate_fun username:mdl password:pwd [TRACE] login_a do 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(f"username:{username}\npassword:{password}")return func(username, password)return delegate_funreturn proxy_fun

    當(dāng)裝飾器?login_debug_helper?被調(diào)用時(shí),其等價(jià)于:

    gin_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)用過程等價(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ǎ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 模塊

    ?

    ?

    ?

    理解 Python 中的裝飾器

    ?

    From:https://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html

    ?

    文章先由 stackoverflow上面的一個(gè)問題引起吧,如果使用如下的代碼:

    @makebold @makeitalic def say():return "Hello"

    打印出如下的輸出:

    <b><i>Hello<i></b>

    你會(huì)怎么做?最后給出的答案是:

    def make_bold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef make_italic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@make_bold @make_italic def hello():return "hello world"print(hello()) # 結(jié)果: <b><i>hello world</i></b>

    現(xiàn)在我們來看看如何從一些最基礎(chǔ)的方式來理解Python的裝飾器。英文討論參考Here。

    裝飾器是一個(gè)很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場(chǎng)景,較為經(jīng)典的有插入日志、性能測(cè)試、事務(wù)處理等。裝飾器是解決這類問題的絕佳設(shè)計(jì),有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能。

    ?

    1.1. 需求是怎么來的 ?

    裝飾器的定義很是抽象,我們來看一個(gè)小例子。

    def foo():print('in foo()')foo()

    這是一個(gè)很無聊的函數(shù)沒錯(cuò)。但是突然有一個(gè)更無聊的人,我們稱呼他為B君,說我想看看執(zhí)行這個(gè)函數(shù)用了多長時(shí)間,好吧,那么我們可以這樣做:

    import timedef foo():start = time.clock()print('in foo()') end = time.clock()print('used:', end - start) foo()

    很好,功能看起來無懈可擊??墒堑疤鄣腂君此刻突然不想看這個(gè)函數(shù)了,他對(duì)另一個(gè)叫foo2的函數(shù)產(chǎn)生了更濃厚的興趣。
    怎么辦呢?如果把以上新增加的代碼復(fù)制到foo2里,這就犯了大忌了~復(fù)制什么的難道不是最討厭了么!而且,如果B君繼續(xù)看了其他的函數(shù)呢?

    ?

    1.2. 以不變應(yīng)萬變,是變也

    還記得嗎,函數(shù)在Python中是一等公民,那么我們可以考慮重新定義一個(gè)函數(shù)timeit,將foo的引用傳遞給他,然后在timeit中調(diào)用foo并進(jìn)行計(jì)時(shí),這樣,我們就達(dá)到了不改動(dòng)foo定義的目的,而且,不論B君看了多少個(gè)函數(shù),我們都不用去修改函數(shù)定義了!

    import timedef foo():print('in foo()') def timeit(func):start = time.clock()func()end = time.clock()print('used:', end - start) timeit(foo)

    看起來邏輯上并沒有問題,一切都很美好并且運(yùn)作正常!……等等,我們似乎修改了調(diào)用部分的代碼。原本我們是這樣調(diào)用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調(diào)用了,你就不得不去修改這N處的代碼?;蛘吒鼧O端的,考慮其中某處調(diào)用的代碼無法修改這個(gè)情況,比如:這個(gè)函數(shù)是你交給別人使用的。

    ?

    1.3. 最大限度地少改動(dòng)!

    既然如此,我們就來想想辦法不修改調(diào)用的代碼;如果不修改調(diào)用代碼,也就意味著調(diào)用foo()需要產(chǎn)生調(diào)用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個(gè)參數(shù)……想辦法把參數(shù)統(tǒng)一吧!如果timeit(foo)不是直接產(chǎn)生調(diào)用效果,而是返回一個(gè)與foo參數(shù)列表一致的函數(shù)的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調(diào)用foo()的代碼完全不用修改!

    # -*- coding: UTF-8 -*- import timedef foo():print('in foo()')# 定義一個(gè)計(jì)時(shí)器,傳入一個(gè),并返回另一個(gè)附加了計(jì)時(shí)功能的方法 def timeit(func):# 定義一個(gè)內(nèi)嵌的包裝函數(shù),給傳入的函數(shù)加上計(jì)時(shí)功能的包裝def wrapper():start = time.perf_counter()func()end = time.perf_counter()print('used:', end - start)# 將包裝后的函數(shù)返回return wrapperfoo = timeit(foo) foo()

    這樣,一個(gè)簡(jiǎn)易的計(jì)時(shí)器就做好了!我們只需要在定義foo以后調(diào)用foo之前,加上foo = timeit(foo),就可以達(dá)到計(jì)時(shí)的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個(gè)例子中,函數(shù)進(jìn)入和退出時(shí)需要計(jì)時(shí),這被稱為一個(gè)橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習(xí)慣的從上往下執(zhí)行方式相比較而言,像是在函數(shù)執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務(wù)領(lǐng)域里,能減少大量重復(fù)代碼。面向切面編程還有相當(dāng)多的術(shù)語,這里就不多做介紹,感興趣的話可以去找找相關(guān)的資料。

    這個(gè)例子僅用于演示,并沒有考慮foo帶有參數(shù)和有返回值的情況,完善它的重任就交給你了 :)
    上面這段代碼看起來似乎已經(jīng)不能再精簡(jiǎn)了,Python于是提供了一個(gè)語法糖來降低字符輸入量。

    import timedef timeit(func):def wrapper():start = time.perf_counter()func()end = time.perf_counter()print('used:', end - start)return wrapper@timeit def foo():print('in foo()')foo()

    重點(diǎn)關(guān)注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價(jià)

    千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個(gè)額外的好處:這樣看上去更有裝飾器的感覺。

    -------------------

    ?

    Python中函數(shù)也是被視為對(duì)象(Python 中一切 皆 對(duì)象)

    要理解python的裝飾器,我們首先必須明白在Python中函數(shù)也是被視為對(duì)象。這一點(diǎn)很重要。先看一個(gè)例子:

    def shout(word="yes"):return word.capitalize() + " !"print(shout()) # 輸出 : 'Yes !'# 作為一個(gè)對(duì)象,你可以把函數(shù)賦給任何其他對(duì)象變量 scream = shout# 注意我們沒有使用圓括號(hào),因?yàn)槲覀儾皇窃谡{(diào)用函數(shù) # 我們把函數(shù)shout賦給scream,也就是說你可以通過scream調(diào)用shout print(scream()) # 輸出 : 'Yes !'# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數(shù) del shout try:print(shout()) except BaseException as e:print(e)# 輸出 : "name 'shout' is not defined"print(scream()) # 輸出 : 'Yes !'

    我們暫且把這個(gè)話題放旁邊,我們先看看 python 另外一個(gè)很有意思的屬性:可以在函數(shù)中定義函數(shù):

    def talk():# 你可以在talk中定義另外一個(gè)函數(shù)def whisper(word="yes"):return word.lower() + "..."# ... 并且立馬使用它print(whisper())# 你每次調(diào)用'talk',定義在talk里面的whisper同樣也會(huì)被調(diào)用 talk() # 輸出 : # yes...# 但是"whisper" 不會(huì)單獨(dú)存在: try:print(whisper()) except BaseException as e:print(e)# 輸出 : "name 'whisper' is not defined"*

    ?

    函數(shù)引用

    從以上兩個(gè)例子我們可以得出,函數(shù)既然作為一個(gè)對(duì)象,因此:

    • 1. 其可以被賦給其他變量
    • 2. 其可以被定義在另外一個(gè)函數(shù)內(nèi)

    這也就是說,函數(shù)可以返回一個(gè)函數(shù),看下面的例子:

    def get_talk(type="shout"):# 我們定義另外一個(gè)函數(shù)def shout(word="yes"):return word.capitalize() + " !"def whisper(word="yes"):return word.lower() + "..."# 然后我們返回其中一個(gè)if type == "shout":# 我們沒有使用(),因?yàn)槲覀儾皇窃谡{(diào)用該函數(shù)# 我們是在返回該函數(shù)return shoutelse:return whisper# 然后怎么使用呢 ? # 把該函數(shù)賦予某個(gè)變量 talk = get_talk()# 這里你可以看到talk其實(shí)是一個(gè)函數(shù)對(duì)象: print(talk) # 輸出 : <function shout at 0xb7ea817c># 該對(duì)象由函數(shù)返回的其中一個(gè)對(duì)象: print(talk())# 或者你可以直接如下調(diào)用 : print(get_talk("whisper")()) # 輸出 : yes...

    還有,既然可以返回一個(gè)函數(shù),我們可以把它作為參數(shù)傳遞給函數(shù):

    def do_something_before(func):print("I do something before then I call the function you gave me")print(func())do_something_before(scream) # 輸出 : # I do something before then I call the function you gave me # Yes !

    這里你已經(jīng)足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執(zhí)行代碼而無須改變函數(shù)本身內(nèi)容。

    ?

    ?

    手工裝飾

    那么如何進(jìn)行手動(dòng)裝飾呢?

    # 裝飾器是一個(gè)函數(shù),而其參數(shù)為另外一個(gè)函數(shù) def my_shiny_new_decorator(a_function_to_decorate):# 在內(nèi)部定義了另外一個(gè)函數(shù):一個(gè)封裝器。# 這個(gè)函數(shù)將原始函數(shù)進(jìn)行封裝,所以你可以在它之前或者之后執(zhí)行一些代碼def the_wrapper_around_the_original_function():# 放一些你希望在真正函數(shù)執(zhí)行前的一些代碼print("Before the function runs")# 執(zhí)行原始函數(shù)a_function_to_decorate()# 放一些你希望在原始函數(shù)執(zhí)行后的一些代碼print("After the function runs")# 在此刻,"a_function_to_decrorate"還沒有被執(zhí)行,我們返回了創(chuàng)建的封裝函數(shù)# 封裝器包含了函數(shù)以及其前后執(zhí)行的代碼,其已經(jīng)準(zhǔn)備完畢return the_wrapper_around_the_original_function# 現(xiàn)在想象下,你創(chuàng)建了一個(gè)你永遠(yuǎn)也不遠(yuǎn)再次接觸的函數(shù) def a_stand_alone_function():print("I am a stand alone function, don't you dare modify me")a_stand_alone_function() # 輸出: I am a stand alone function, don't you dare modify me# 好了,你可以封裝它實(shí)現(xiàn)行為的擴(kuò)展??梢院?jiǎn)單的把它丟給裝飾器 # 裝飾器將動(dòng)態(tài)地把它和你要的代碼封裝起來,并且返回一個(gè)新的可用的函數(shù)。 a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() # 輸出 : # Before the function runs # I am a stand alone function, don't you dare modify me # After the function runs# 現(xiàn)在你也許要求當(dāng)每次調(diào)用a_stand_alone_function時(shí), # 實(shí)際調(diào)用卻是a_stand_alone_function_decorated。 # 實(shí)現(xiàn)也很簡(jiǎn)單,可以用my_shiny_new_decorator來給a_stand_alone_function重新賦值。 a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() # 輸出 : # Before the function runs # I am a stand alone function, don't you dare modify me # After the function runs # And guess what, that's EXACTLY what decorators do !

    ?

    ?

    裝飾器揭秘

    前面的例子,我們可以使用裝飾器的語法:

    @my_shiny_new_decorator def another_stand_alone_function():print("Leave me alone")another_stand_alone_function() # 輸出 : # Before the function runs # Leave me alone # After the function runs

    當(dāng)然你也可以累積裝飾:

    def bread(func):def wrapper():print(r"</''''''\>")func()print(r"<\______/>")return wrapperdef ingredients(func):def wrapper():print("#tomatoes#")func()print("~salad~")return wrapperdef sandwich(food="--ham--"):print(food) sandwich() # 輸出 : --ham-- sandwich = bread(ingredients(sandwich)) sandwich() # outputs : # </''''''\> # #tomatoes# # --ham-- # ~salad~ # <\______/>

    ?

    ?

    使用 python 裝飾器語法:

    @bread @ingredients def sandwich(food="--ham--"):print(food)sandwich() # 輸出 : # </''''''\> # #tomatoes# # --ham-- # ~salad~ # <\______/>

    ?

    裝飾器的順序很重要,需要注意:

    @ingredients @bread def strange_sandwich(food="--ham--"):print(food) strange_sandwich() # 輸出 : # tomatoes# # </''''''\> # --ham-- # <\______/> # ~salad~

    ?

    最后回答前面提到的問題:

    # 裝飾器 make_bold 用于轉(zhuǎn)換為粗體 def make_bold(fn):# 結(jié)果返回該函數(shù)def wrapper():# 插入一些執(zhí)行前后的代碼return "<b>" + fn() + "</b>"return wrapper# 裝飾器 make_italic 用于轉(zhuǎn)換為斜體 def make_italic(fn):# 結(jié)果返回該函數(shù)def wrapper():# 插入一些執(zhí)行前后的代碼return "<i>" + fn() + "</i>"return wrapper@make_bold @make_italic def say():return "hello"print(say()) # 輸出: <b><i>hello</i></b># 等同于def say():return "hello"say = make_bold(make_italic(say)) print(say()) # 輸出: <b><i>hello</i></b>

    ?

    ?

    內(nèi)置的裝飾器

    內(nèi)置的裝飾器有三個(gè),分別是staticmethod、classmethod和property,作用分別是把類中定義的實(shí)例方法變成靜態(tài)方法、類方法和類屬性。由于模塊里可以定義函數(shù),所以靜態(tài)方法和類方法的用處并不是太多,除非你想要完全的面向?qū)ο缶幊獭6鴮傩砸膊皇遣豢苫蛉钡?#xff0c;Java沒有屬性也一樣活得很滋潤。從我個(gè)人的Python經(jīng)驗(yàn)來看,我沒有使用過property,使用staticmethod和classmethod的頻率也非常低。

    class Rabbit(object):def __init__(self, name):self._name = name@staticmethoddef newRabbit(name):return Rabbit(name)@classmethoddef newRabbit2(cls):return Rabbit('')@propertydef name(self):return self._name

    這里定義的屬性是一個(gè)只讀屬性,如果需要可寫,則需要再定義一個(gè)setter:
    @name.setter
    def name(self, name):
    ? ? self._name = name
    functools模塊
    functools模塊提供了兩個(gè)裝飾器。這個(gè)模塊是Python 2.5后新增的,一般來說大家用的應(yīng)該都高于這個(gè)版本。但我平時(shí)的工作環(huán)境是2.4 T-T

    2.3.1. wraps(wrapped[, assigned][, updated]):?
    這是一個(gè)很有用的裝飾器??催^前一篇反射的朋友應(yīng)該知道,函數(shù)是有幾個(gè)特殊屬性比如函數(shù)名,在被裝飾后,上例中的函數(shù)名foo會(huì)變成包裝函數(shù)的名字wrapper,如果你希望使用反射,可能會(huì)導(dǎo)致意外的結(jié)果。這個(gè)裝飾器可以解決這個(gè)問題,它能將裝飾過的函數(shù)的特殊屬性保留。

    import time import functoolsdef timeit(func):@functools.wraps(func)def wrapper():start = time.clock()func()end =time.clock()print 'used:', end - startreturn wrapper@timeit def foo():print 'in foo()'foo() print foo.__name__

    首先注意第5行,如果注釋這一行,foo.__name__將是'wrapper'。另外相信你也注意到了,這個(gè)裝飾器竟然帶有一個(gè)參數(shù)。實(shí)際上,他還有另外兩個(gè)可選的參數(shù),assigned中的屬性名將使用賦值的方式替換,而updated中的屬性名將使用update的方式合并,你可以通過查看functools的源代碼獲得它們的默認(rèn)值。對(duì)于這個(gè)裝飾器,相當(dāng)于wrapper = functools.wraps(func)(wrapper)。

    ?

    ?

    ?

    深入淺出 Python 裝飾器:16 步輕松搞定 Python 裝飾器

    ?

    而Python使用了一種相對(duì)于Decorator Pattern和Annotation來說非常優(yōu)雅的方法,這種方法不需要你去掌握什么復(fù)雜的OO模型或是Annotation的各種類庫規(guī)定,完全就是語言層面的玩法:一種函數(shù)式編程的技巧。如果你看過本站的《函數(shù)式編程》,你一定會(huì)為函數(shù)式編程的那種“描述你想干什么,而不是描述你要怎么去實(shí)現(xiàn)”的編程方式感到暢快。(如果你不了解函數(shù)式編程,那在讀本文之前,還請(qǐng)你移步去看看《函數(shù)式編程》) 好了。

    ?

    1. 函數(shù)

    在python中,函數(shù)通過def關(guān)鍵字、函數(shù)名和可選的參數(shù)列表定義。通過return關(guān)鍵字返回值。我們舉例來說明如何定義和調(diào)用一個(gè)簡(jiǎn)單的函數(shù):

    def foo():return 1 foo() 1

    方法體(當(dāng)然多行也是一樣的)是必須的,通過縮進(jìn)來表示,在方法名的后面加上雙括號(hào)()就能夠調(diào)用函數(shù)

    ?

    2. 作用域

    python中,函數(shù)會(huì)創(chuàng)建一個(gè)新的作用域。python開發(fā)者可能會(huì)說函數(shù)有自己的命名空間,差不多一個(gè)意思。這意味著在函數(shù)內(nèi)部碰到一個(gè)變量的時(shí)候函數(shù)會(huì)優(yōu)先在自己的命名空間里面去尋找。讓我們寫一個(gè)簡(jiǎn)單的函數(shù)看一下 本地作用域 和 全局作用域有什么不同:

    a_string = "This is a global variable" def foo():print locals() print globals() {..., 'a_string': 'This is a global variable'} foo() # 2 {}

    內(nèi)置的函數(shù)globals返回一個(gè)包含所有python解釋器知道的變量名稱的字典(為了干凈和洗的白白的,我省略了python自行創(chuàng)建的一些變量)。在#2我調(diào)用了函數(shù) foo 把函數(shù)內(nèi)部本地作用域里面的內(nèi)容打印出來。我們能夠看到,函數(shù)foo有自己獨(dú)立的命名空間,雖然暫時(shí)命名空間里面什么都還沒有。

    ?

    3. 變量解析規(guī)則

    當(dāng)然這并不是說我們?cè)诤瘮?shù)里面就不能訪問外面的全局變量。在python的作用域規(guī)則里面,創(chuàng)建變量一定會(huì)在當(dāng)前作用域里創(chuàng)建一個(gè)變量,但是訪問或者修改變量時(shí)會(huì)先在當(dāng)前作用域查找變量,沒有找到匹配變量的話會(huì)依次向上在閉合的作用域里面進(jìn)行查看找。所以如果我們修改函數(shù)foo的實(shí)現(xiàn)讓它打印全局的作用域里的變量也是可以的:

    a_string = "This is a global variable" def foo():print a_string # 1 foo() This is a global variable

    在#1處,python解釋器會(huì)嘗試查找變量a_string,當(dāng)然在函數(shù)的本地作用域里面找不到,所以接著會(huì)去上層的作用域里面去查找。

    但是另一方面,假如我們?cè)诤瘮?shù)內(nèi)部給全局變量賦值,結(jié)果卻和我們想的不一樣:

    a_string = "This is a global variable" def foo():a_string = "test" # 1print locals() foo() {'a_string': 'test'} a_string # 2 'This is a global variable'

    我們能夠看到,全局變量能夠被訪問到(如果是可變數(shù)據(jù)類型(像list,dict這些)甚至能夠被更改)但是賦值不行。在函數(shù)內(nèi)部的#1處,我們實(shí)際上新創(chuàng)建了一個(gè)局部變量,隱藏全局作用域中的同名變量。我們可以通過打印出局部命名空間中的內(nèi)容得出這個(gè)結(jié)論。我們也能看到在#2處打印出來的變量a_string的值并沒有改變。

    ?

    4. 變量生存周期

    值得注意的一個(gè)點(diǎn)是,變量不僅是生存在一個(gè)個(gè)的命名空間內(nèi),他們都有自己的生存周期,請(qǐng)看下面這個(gè)例子:

    def foo():x = 1 foo() print x # 1 #Traceback (most recent call last): #NameError: name 'x' is not defined

    #1處發(fā)生的錯(cuò)誤不僅僅是因?yàn)樽饔糜蛞?guī)則導(dǎo)致的(盡管這是拋出了NameError的錯(cuò)誤的原因)它還和python以及其它很多編程語言中函數(shù)調(diào)用實(shí)現(xiàn)的機(jī)制有關(guān)。在這個(gè)地方這個(gè)執(zhí)行時(shí)間點(diǎn)并沒有什么有效的語法讓我們能夠獲取變量x的值,因?yàn)樗@個(gè)時(shí)候壓根不存在!函數(shù)foo的命名空間隨著函數(shù)調(diào)用開始而開始,結(jié)束而銷毀。

    ?

    5. 函數(shù)參數(shù)

    python允許我們向函數(shù)傳遞參數(shù),參數(shù)會(huì)變成本地變量存在于函數(shù)內(nèi)部。

    def foo(x):print locals() foo(1) {'x': 1}

    在Python里有很多的方式來定義和傳遞參數(shù),完整版可以查看 python官方文檔。我們這里簡(jiǎn)略的說明一下:函數(shù)的參數(shù)可以是必須的位置參數(shù)或者是可選的命名,默認(rèn)參數(shù)。

    def foo(x, y=0): # 1return x - y foo(3, 1) # 2 2 foo(3) # 3 3 foo() # 4 #Traceback (most recent call last): #TypeError: foo() takes at least 1 argument (0 given) foo(y=1, x=3) # 5 2

    在#1處我們定義了函數(shù)foo,它有一個(gè)位置參數(shù)x和一個(gè)命名參數(shù)y。在#2處我們能夠通過常規(guī)的方式來調(diào)用函數(shù),盡管有一個(gè)命名參數(shù),但參數(shù)依然可以通過位置傳遞給函數(shù)。在調(diào)用函數(shù)的時(shí)候,對(duì)于命名參數(shù)y我們也可以完全不管就像#3處所示的一樣。如果命名參數(shù)沒有接收到任何值的話,python會(huì)自動(dòng)使用聲明的默認(rèn)值也就是0。需要注意的是我們不能省略第一個(gè)位置參數(shù)x, 否則的話就會(huì)像#4處所示發(fā)生錯(cuò)誤。

    目前還算簡(jiǎn)潔清晰吧, 但是接下來可能會(huì)有點(diǎn)令人困惑。python支持函數(shù)調(diào)用時(shí)的命名參數(shù)(個(gè)人覺得應(yīng)該是命名實(shí)參)??纯?5處的函數(shù)調(diào)用,我們傳遞的是兩個(gè)命名實(shí)參,這個(gè)時(shí)候因?yàn)橛忻Q標(biāo)識(shí),參數(shù)傳遞的順序也就不用在意了。

    當(dāng)然相反的情況也是正確的:函數(shù)的第二個(gè)形參是y,但是我們通過位置的方式傳遞值給它。在#2處的函數(shù)調(diào)用foo(3,1),我們把3傳遞給了第一個(gè)參數(shù),把1傳遞給了第二個(gè)參數(shù),盡管第二個(gè)參數(shù)是一個(gè)命名參數(shù)。

    一個(gè)簡(jiǎn)單的概念:函數(shù)的參數(shù)可以有名稱和位置。這意味著在函數(shù)的定義和調(diào)用的時(shí)候會(huì)稍稍在理解上有點(diǎn)兒不同。我們可以給只定義了位置參數(shù)的函數(shù)傳遞命名參數(shù)(實(shí)參),反之亦然!如果覺得不夠可以查看官方文檔

    ?

    6. 嵌套函數(shù)

    Python允許創(chuàng)建嵌套函數(shù)。這意味著我們可以在函數(shù)里面定義函數(shù)而且現(xiàn)有的作用域和變量生存周期依舊適用。

    def outer():x = 1def inner():print x # 1return inner() # 2 outer() 1

    這個(gè)例子有一點(diǎn)兒復(fù)雜,但是看起來也還行。想一想在#1發(fā)生了什么:python解釋器需找一個(gè)叫x的本地變量,查找失敗之后會(huì)繼續(xù)在上層的作用域里面尋找,這個(gè)上層的作用域定義在另外一個(gè)函數(shù)里面。對(duì)函數(shù)outer來說,變量x是一個(gè)本地變量,但是如先前提到的一樣,函數(shù)inner可以訪問封閉的作用域(至少可以讀和修改)。在#2處,我們調(diào)用函數(shù)inner,非常重要的一點(diǎn)是,inner也僅僅是一個(gè)遵循python變量解析規(guī)則的變量名,python解釋器會(huì)優(yōu)先在outer的作用域里面對(duì)變量名inner查找匹配的變量.

    ?

    7. 函數(shù)是 python 世界里的一級(jí)類對(duì)象

    顯而易見,在python里函數(shù)和其他東西一樣都是對(duì)象。(此處應(yīng)該大聲歌唱)啊!包含變量的函數(shù),你也并不是那么特殊!

    issubclass(int, object) # all objects in Python inherit from a common baseclass #True def foo():pass foo.__class__ # 1 #<type 'function'> issubclass(foo.__class__, object) #True

    你也許從沒有想過,你定義的函數(shù)居然會(huì)有屬性。沒辦法,函數(shù)在python里面就是對(duì)象(Python一切皆對(duì)象),和其他的東西一樣,也許這樣描述會(huì)太學(xué)院派太官方了點(diǎn):在python里,函數(shù)只是一些普通的值而已和其他的值一毛一樣。這就是說你可以把函數(shù)像參數(shù)一樣傳遞給其他的函數(shù)或者說從函數(shù)里面返回函數(shù)!如果你從來沒有這么想過,那看看下面這個(gè)例子:

    def add(x, y):return x + y def sub(x, y):return x - y def apply(func, x, y): # 1return func(x, y) # 2 apply(add, 2, 1) # 3 3 apply(sub, 2, 1) 1

    這個(gè)例子對(duì)你來說應(yīng)該不會(huì)很奇怪。add和sub是非常普通的兩個(gè)python函數(shù),接受兩個(gè)值,返回一個(gè)計(jì)算后的結(jié)果值。在#1處你們能看到準(zhǔn)備接收一個(gè)函數(shù)的變量只是一個(gè)普通的變量而已,和其他變量一樣。在#2處我們調(diào)用傳進(jìn)來的函數(shù):“()代表著調(diào)用的操作并且調(diào)用變量包含的值。在#3處,你們也能看到傳遞函數(shù)并沒有什么特殊的語法。” 函數(shù)的名稱只是很其他變量一樣的表標(biāo)識(shí)符而已。

    你們也許看到過這樣的行為:“python把頻繁要用的操作變成函數(shù)作為參數(shù)進(jìn)行使用,像通過傳遞一個(gè)函數(shù)給內(nèi)置排序函數(shù)的key參數(shù)從而來自定義排序規(guī)則。那把函數(shù)當(dāng)做返回值回事這樣的情況呢:

    def outer():def inner():print "Inside inner"return inner # 1 foo = outer() #2 foo #<function inner at 0x...> foo() #Inside inner

    這個(gè)例子看起來也許會(huì)更加的奇怪。在#1處我把恰好是函數(shù)標(biāo)識(shí)符的變量inner作為返回值返回出來。這并沒有什么特殊的語法:”把函數(shù)inner返回出來,否則它根本不可能會(huì)被調(diào)用到?!斑€記得變量的生存周期嗎?每次函數(shù)outer被調(diào)用的時(shí)候,函數(shù)inner都會(huì)被重新定義,如果它不被當(dāng)做變量返回的話,每次執(zhí)行過后它將不復(fù)存在。

    在#2處我們捕獲住返回值 – 函數(shù)inner,將它存在一個(gè)新變量foo里。我們能夠看到,當(dāng)對(duì)變量foo進(jìn)行求值,它確實(shí)包含函數(shù)inner,而且我們能夠?qū)λM(jìn)行調(diào)用。初次看起來可能會(huì)覺得有點(diǎn)奇怪,但是理解起來并不困難是吧。堅(jiān)持住,因?yàn)槠婀值霓D(zhuǎn)折馬上就要來了

    ?

    8. 閉包

    我們先不急著定義什么是閉包,先來看看一段代碼,僅僅是把上一個(gè)例子簡(jiǎn)單的調(diào)整了一下:

    def outer():x = 1def inner():print x # 1return inner foo = outer() foo.func_closure #(<cell at 0x...: int object at 0x...>,)

    在上一個(gè)例子中我們了解到,inner作為一個(gè)函數(shù)被outer返回,保存在一個(gè)變量foo,并且我們能夠?qū)λM(jìn)行調(diào)用foo()。不過它會(huì)正常的運(yùn)行嗎?我們先來看看作用域規(guī)則。

    所有的東西都在python的作用域規(guī)則下進(jìn)行工作:“x是函數(shù)outer里的一個(gè)局部變量。當(dāng)函數(shù)inner在#1處打印x的時(shí)候,python解釋器會(huì)在inner內(nèi)部查找相應(yīng)的變量,當(dāng)然會(huì)找不到,所以接著會(huì)到封閉作用域里面查找,并且會(huì)找到匹配。

    但是從變量的生存周期來看,該怎么理解呢?我們的變量x是函數(shù)outer的一個(gè)本地變量,這意味著只有當(dāng)函數(shù)outer正在運(yùn)行的時(shí)候才會(huì)存在。根據(jù)我們已知的python運(yùn)行模式,我們沒法在函數(shù)outer返回之后繼續(xù)調(diào)用函數(shù)inner,在函數(shù)inner被調(diào)用的時(shí)候,變量x早已不復(fù)存在,可能會(huì)發(fā)生一個(gè)運(yùn)行時(shí)錯(cuò)誤。

    萬萬沒想到,返回的函數(shù)inner居然能夠正常工作。Python支持一個(gè)叫做函數(shù)閉包的特性,用人話來講就是,嵌套定義在非全局作用域里面的函數(shù)能夠記住它在被定義的時(shí)候它所處的封閉命名空間。這能夠通過查看函數(shù)的func_closure屬性得出結(jié)論,這個(gè)屬性里面包含封閉作用域里面的值(只會(huì)包含被捕捉到的值,比如x,如果在outer里面還定義了其他的值,封閉作用域里面是不會(huì)有的)

    記住,每次函數(shù)outer被調(diào)用的時(shí)候,函數(shù)inner都會(huì)被重新定義。現(xiàn)在變量x的值不會(huì)變化,所以每次返回的函數(shù)inner會(huì)是同樣的邏輯,假如我們稍微改動(dòng)一下呢?

    def outer(x):def inner():print x # 1return inner print1 = outer(1) print2 = outer(2) print1() 1 print2() 2

    從這個(gè)例子中你能夠看到閉包 – 被函數(shù)記住的封閉作用域 – 能夠被用來創(chuàng)建自定義的函數(shù),本質(zhì)上來說是一個(gè)硬編碼的參數(shù)。事實(shí)上我們并不是傳遞參數(shù)1或者2給函數(shù)inner,我們實(shí)際上是創(chuàng)建了能夠打印各種數(shù)字的各種自定義版本。

    閉包單獨(dú)拿出來就是一個(gè)非常強(qiáng)大的功能, 在某些方面,你也許會(huì)把它當(dāng)做一個(gè)類似于面向?qū)ο蟮募夹g(shù):outer像是給inner服務(wù)的構(gòu)造器,x像一個(gè)私有變量。使用閉包的方式也有很多:你如果熟悉python內(nèi)置排序方法的參數(shù)key,你說不定已經(jīng)寫過一個(gè)lambda方法在排序一個(gè)列表的列表的時(shí)候基于第二個(gè)元素而不是第一個(gè)?,F(xiàn)在你說不定也可以寫一個(gè)itemgetter方法,接收一個(gè)索引值來返回一個(gè)完美的函數(shù),傳遞給排序函數(shù)的參數(shù)key。

    不過,我們現(xiàn)在不會(huì)用閉包做這么low的事(⊙o⊙)…!相反,讓我們?cè)偎淮?#xff0c;寫一個(gè)高大上的裝飾器!

    ?

    9. 裝飾器

    裝飾器其實(shí)就是一個(gè)閉包,把一個(gè)函數(shù)當(dāng)做參數(shù)然后返回一個(gè)替代版函數(shù)。我們一步步從簡(jiǎn)到繁來瞅瞅:

    def outer(some_func):def inner():print "before some_func"ret = some_func() # 1return ret + 1return inner def foo():return 1 decorated = outer(foo) # 2 decorated() #before some_func #2

    仔細(xì)看看上面這個(gè)裝飾器的例子。們定義了一個(gè)函數(shù)outer,它只有一個(gè)some_func的參數(shù),在他里面我們定義了一個(gè)嵌套的函數(shù)inner。inner會(huì)打印一串字符串,然后調(diào)用some_func,在#1處得到它的返回值。在outer每次調(diào)用的時(shí)候some_func的值可能會(huì)不一樣,但是不管some_func的之如何,我們都會(huì)調(diào)用它。最后,inner返回some_func() + 1的值 – 我們通過調(diào)用在#2處存儲(chǔ)在變量decorated里面的函數(shù)能夠看到被打印出來的字符串以及返回值2,而不是期望中調(diào)用函數(shù)foo得到的返回值1。

    我們可以認(rèn)為變量decorated是函數(shù)foo的一個(gè)裝飾版本,一個(gè)加強(qiáng)版本。事實(shí)上如果打算寫一個(gè)有用的裝飾器的話,我們可能會(huì)想愿意用裝飾版本完全取代原先的函數(shù)foo,這樣我們總是會(huì)得到我們的”加強(qiáng)版“foo。想要達(dá)到這個(gè)效果,完全不需要學(xué)習(xí)新的語法,簡(jiǎn)單地賦值給變量foo就行了:

    foo = outer(foo) foo # doctest: +ELLIPSIS #<function inner at 0x...>

    現(xiàn)在,任何怎么調(diào)用都不會(huì)牽扯到原先的函數(shù)foo,都會(huì)得到新的裝飾版本的foo。

    假設(shè)有如下函數(shù):

    def now():print '2013-12-25' f = now f() #2013-12-25

    現(xiàn)在假設(shè)我們要增強(qiáng)now()函數(shù)的功能,比如,在函數(shù)調(diào)用前后自動(dòng)打印日志,但又不希望修改now()函數(shù)的定義,這種在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)。

    本質(zhì)上,decorator就是一個(gè)返回函數(shù)的高階函數(shù)。所以,我們要定義一個(gè)能打印日志的decorator,可以定義如下:

    def log(func):def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper

    觀察上面的log,因?yàn)樗且粋€(gè)decorator,所以接受一個(gè)函數(shù)作為參數(shù),并返回一個(gè)函數(shù)。

    ?

    10. 使用 @ 標(biāo)識(shí)符將裝飾器應(yīng)用到函數(shù)

    Python2.4支持使用標(biāo)識(shí)符@將裝飾器應(yīng)用在函數(shù)上,只需要在函數(shù)的定義前加上@和裝飾器的名稱。在上一節(jié)的例子里我們是將原本的方法用裝飾后的方法代替:

    add = wrapper(add)

    這種方式能夠在任何時(shí)候?qū)θ我夥椒ㄟM(jìn)行包裝。但是如果我們自定義一個(gè)方法,我們可以使用@進(jìn)行裝飾:

    @wrapper def add(a, b):return Coordinate(a.x + b.x, a.y + b.y)

    需要明白的是,這樣的做法和先前簡(jiǎn)單的用包裝方法替代原有方法是一模一樣的, python只是加了一些語法糖讓裝飾的行為更加的直接明確和優(yōu)雅一點(diǎn)。

    多個(gè)decorator

    @decorator_one @decorator_two def func():pass

    相當(dāng)于:

    func = decorator_one(decorator_two(func))

    比如:帶參數(shù)的decorator:

    @decorator(arg1, arg2) def func():pass

    相當(dāng)于:

    func = decorator(arg1,arg2)(func)

    這意味著decorator(arg1, arg2)這個(gè)函數(shù)需要返回一個(gè)“真正的decorator”。

    ?

    11. *args and **kwargs

    我們已經(jīng)完成了一個(gè)有用的裝飾器,但是由于硬編碼的原因它只能應(yīng)用在一類具體的方法上,這類方法接收兩個(gè)參數(shù),傳遞給閉包捕獲的函數(shù)。如果我們想實(shí)現(xiàn)一個(gè)能夠應(yīng)用在任何方法上的裝飾器要怎么做呢?再比如,如果我們要實(shí)現(xiàn)一個(gè)能應(yīng)用在任何方法上的類似于計(jì)數(shù)器的裝飾器,不需要改變?cè)蟹椒ǖ娜魏芜壿嫛_@意味著裝飾器能夠接受擁有任何簽名的函數(shù)作為自己的被裝飾方法,同時(shí)能夠用傳遞給它的參數(shù)對(duì)被裝飾的方法進(jìn)行調(diào)用。

    非常巧合的是Python正好有支持這個(gè)特性的語法??梢蚤喿x Python Tutorial 獲取更多的細(xì)節(jié)。當(dāng)定義函數(shù)的時(shí)候使用了*,意味著那些通過位置傳遞的參數(shù)將會(huì)被放在帶有*前綴的變量中, 所以:

    def one(*args):print args # 1 one() #() one(1, 2, 3) #(1, 2, 3) def two(x, y, *args): # 2print x, y, args two('a', 'b', 'c') #a b ('c',)

    第一個(gè)函數(shù)one只是簡(jiǎn)單地講任何傳遞過來的位置參數(shù)全部打印出來而已,你們能夠看到,在代碼#1處我們只是引用了函數(shù)內(nèi)的變量args, *args僅僅只是用在函數(shù)定義的時(shí)候用來表示位置參數(shù)應(yīng)該存儲(chǔ)在變量args里面。Python允許我們制定一些參數(shù)并且通過args捕獲其他所有剩余的未被捕捉的位置參數(shù),就像#2處所示的那樣。
    *操作符在函數(shù)被調(diào)用的時(shí)候也能使用。意義基本是一樣的。當(dāng)調(diào)用一個(gè)函數(shù)的時(shí)候,一個(gè)用*標(biāo)志的變量意思是變量里面的內(nèi)容需要被提取出來然后當(dāng)做位置參數(shù)被使用。同樣的,來看個(gè)例子:

    def add(x, y):return x + y lst = [1,2] add(lst[0], lst[1]) # 1 3 add(*lst) # 2 3

    #1處的代碼和#2處的代碼所做的事情其實(shí)是一樣的,在#2處,python為我們所做的事其實(shí)也可以手動(dòng)完成。這也不是什么壞事,*args要么是表示調(diào)用方法大的時(shí)候額外的參數(shù)可以從一個(gè)可迭代列表中取得,要么就是定義方法的時(shí)候標(biāo)志這個(gè)方法能夠接受任意的位置參數(shù)。
    接下來提到的**會(huì)稍多更復(fù)雜一點(diǎn),**代表著鍵值對(duì)的餐宿字典,和*所代表的意義相差無幾,也很簡(jiǎn)單對(duì)不對(duì):

    def foo(**kwargs):print kwargs foo() #{} foo(x=1, y=2) #{'y': 2, 'x': 1}

    當(dāng)我們定義一個(gè)函數(shù)的時(shí)候,我們能夠用**kwargs來表明,所有未被捕獲的關(guān)鍵字參數(shù)都應(yīng)該存儲(chǔ)在kwargs的字典中。如前所訴,args、kwargs并不是python語法的一部分,但在定義函數(shù)的時(shí)候,使用這樣的變量名算是一個(gè)不成文的約定。和*一樣,我們同樣可以在定義或者調(diào)用函數(shù)的時(shí)候使用**。

    dct = {'x': 1, 'y': 2} def bar(x, y):return x + y bar(**dct) #3

    ?

    12. 更通用的裝飾器

    有了這招新的技能,我們隨隨便便就可以寫一個(gè)能夠記錄下傳遞給函數(shù)參數(shù)的裝飾器了。先來個(gè)簡(jiǎn)單地把日志輸出到界面的例子:

    def logger(func):def inner(*args, **kwargs): #1print "Arguments were: %s, %s" % (args, kwargs)return func(*args, **kwargs) #2return inner

    請(qǐng)注意函數(shù)inner,它能夠接受任意數(shù)量和類型的參數(shù)并把它們傳遞給被包裝的方法,這讓我們能夠用這個(gè)裝飾器來裝飾任何方法。

    @logger def foo1(x, y=1):return x * y @logger def foo2():return 2 foo1(5, 4) #Arguments were: (5, 4), {} #20 foo1(1) #Arguments were: (1,), {} #1 foo2() #Arguments were: (), {} #2

    隨便調(diào)用我們定義的哪個(gè)方法,相應(yīng)的日志也會(huì)打印到輸出窗口,和我們預(yù)期的一樣。

    ?

    13. 帶參數(shù)的裝飾器:

    如果decorator本身需要傳入?yún)?shù),那就需要編寫一個(gè)返回decorator的高階函數(shù),寫出來會(huì)更復(fù)雜。比如,要自定義log的文本:

    def log(text):def decorator(func):def wrapper(*args, **kw):print '%s %s():' % (text, func.__name__)return func(*args, **kw)return wrapperreturn decorator

    這個(gè)3層嵌套的decorator用法如下:

    @log('execute') def now():print '2013-12-25'

    執(zhí)行結(jié)果如下:

    >>> now() execute now(): 2013-12-25

    和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:

    now = log('execute')(now)

    我們來剖析上面的語句,首先執(zhí)行l(wèi)og('execute'),返回的是decorator函數(shù),再調(diào)用返回的函數(shù),參數(shù)是now函數(shù),返回值最終是wrapper函數(shù)。

    ?

    14. 裝飾器的副作用

    以上兩種decorator的定義都沒有問題,但還差最后一步。因?yàn)槲覀冎v了函數(shù)也是對(duì)象,它有__name__等屬性,但你去看經(jīng)過decorator裝飾之后的函數(shù),它們的__name__已經(jīng)從原來的'now'變成了'wrapper':

    >>> now.__name__ 'wrapper'

    因?yàn)榉祷氐哪莻€(gè)wrapper()函數(shù)名字就是'wrapper',所以,需要把原始函數(shù)的__name__等屬性復(fù)制到wrapper()函數(shù)中,否則,有些依賴函數(shù)簽名的代碼執(zhí)行就會(huì)出錯(cuò)。

    不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內(nèi)置的functools.wraps就是干這個(gè)事的,所以,一個(gè)完整的decorator的寫法如下:

    import functoolsdef log(func):@functools.wraps(func)def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper

    或者針對(duì)帶參數(shù)的decorator:

    import functoolsdef log(text):def decorator(func):@functools.wraps(func)def wrapper(*args, **kw):print '%s %s():' % (text, func.__name__)return func(*args, **kw)return wrapperreturn decorator

    import functools是導(dǎo)入functools模塊。模塊的概念稍候講解?,F(xiàn)在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。

    當(dāng)然,即使是你用了functools的wraps,也不能完全消除這樣的副作用。你會(huì)發(fā)現(xiàn),即使是你你用了functools的wraps,你在用getargspec時(shí),參數(shù)也不見了。要修正這一問題,我們還得用Python的反射來解決,當(dāng)然,我相信大多數(shù)人的程序都不會(huì)去getargspec。所以,用functools的wraps應(yīng)該夠用了。

    ?

    15. class式的 Decorator

    首先,先得說一下,decorator的class方式,還是看個(gè)示例:

    class myDecorator(object):def __init__(self, fn):print "inside myDecorator.__init__()"self.fn = fndef __call__(self):self.fn()print "inside myDecorator.__call__()"@myDecorator def aFunction():print "inside aFunction()"print "Finished decorating aFunction()"aFunction()# 輸出: # inside myDecorator.__init__() # Finished decorating aFunction() # inside aFunction() # inside myDecorator.__call__()

    1)一個(gè)是__init__(),這個(gè)方法是在我們給某個(gè)函數(shù)decorator時(shí)被調(diào)用,所以,需要有一個(gè)fn的參數(shù),也就是被decorator的函數(shù)。
    2)一個(gè)是__call__(),這個(gè)方法是在我們調(diào)用被decorator函數(shù)時(shí)被調(diào)用的。
    上面輸出可以看到整個(gè)程序的執(zhí)行順序。

    這看上去要比“函數(shù)式”的方式更易讀一些。

    上面這段代碼中,我們需要注意這幾點(diǎn):

    1)如果decorator有參數(shù)的話,__init__() 成員就不能傳入fn了,而fn是在__call__的時(shí)候傳入的。

    ?

    ?

    16. 一些decorator的示例

    好了,現(xiàn)在我們來看一下各種decorator的例子:

    ?

    16.1 給函數(shù)調(diào)用做緩存

    這個(gè)例實(shí)在是太經(jīng)典了,整個(gè)網(wǎng)上都用這個(gè)例子做decorator的經(jīng)典范例,因?yàn)樘?jīng)典了,所以,我這篇文章也不能免俗。

    from functools import wraps def memo(fn):cache = {}miss = object()@wraps(fn)def wrapper(*args):result = cache.get(args, miss)if result is miss:result = fn(*args)cache[args] = resultreturn resultreturn wrapper@memo def fib(n):if n < 2:return nreturn fib(n - 1) + fib(n - 2)

    上面這個(gè)例子中,是一個(gè)斐波拉契數(shù)例的遞歸算法。我們知道,這個(gè)遞歸是相當(dāng)沒有效率的,因?yàn)闀?huì)重復(fù)調(diào)用。比如:我們要計(jì)算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上來說,fib(3), fib(2), fib(1)在整個(gè)遞歸過程中被調(diào)用了兩次。

    而我們用decorator,在調(diào)用函數(shù)前查詢一下緩存,如果沒有才調(diào)用了,有了就從緩存中返回值。一下子,這個(gè)遞歸從二叉樹式的遞歸成了線性的遞歸。

    ?

    16.2 Profiler的例子

    這個(gè)例子沒什么高深的,就是實(shí)用一些。

    import cProfile, pstats, StringIOdef profiler(func):def wrapper(*args, **kwargs):datafn = func.__name__ + ".profile" # Name the data fileprof = cProfile.Profile()retval = prof.runcall(func, *args, **kwargs)#prof.dump_stats(datafn)s = StringIO.StringIO()sortby = 'cumulative'ps = pstats.Stats(prof, stream=s).sort_stats(sortby)ps.print_stats()print s.getvalue()return retvalreturn wrapper

    ?

    16.3 注冊(cè)回調(diào)函數(shù)

    下面這個(gè)示例展示了通過URL的路由來調(diào)用相關(guān)注冊(cè)的函數(shù)示例:

    class MyApp():def __init__(self):self.func_map = {}def register(self, name):def func_wrapper(func):self.func_map[name] = funcreturn funcreturn func_wrapperdef call_method(self, name=None):func = self.func_map.get(name, None)if func is None:raise Exception("No function registered against - " + str(name))return func()app = MyApp()@app.register('/') def main_page_func():return "This is the main page."@app.register('/next_page') def next_page_func():return "This is the next page."print app.call_method('/') print app.call_method('/next_page')

    注意:
    1)上面這個(gè)示例中,用類的實(shí)例來做decorator。
    2)decorator類中沒有__call__(),但是wrapper返回了原函數(shù)。所以,原函數(shù)沒有發(fā)生任何變化。

    ?

    16.4 給函數(shù)打日志

    下面這個(gè)示例演示了一個(gè)logger的decorator,這個(gè)decorator輸出了函數(shù)名,參數(shù),返回值,和運(yùn)行時(shí)間。

    from functools import wraps def logger(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()print "function = {0}".format(fn.__name__)print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)print " time = %.6f sec" % (te-ts)return resultreturn wrapper@logger def multipy(x, y):return x * y@logger def sum_num(n):s = 0for i in xrange(n+1):s += ireturn sprint multipy(2, 10) print sum_num(100) print sum_num(10000000)

    上面那個(gè)打日志還是有點(diǎn)粗糙,讓我們看一個(gè)更好一點(diǎn)的(帶log level參數(shù)的):

    import inspect def get_line_number():return inspect.currentframe().f_back.f_back.f_linenodef logger(loglevel):def log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()print "function = " + fn.__name__,print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)print " time = %.6f sec" % (te-ts)if (loglevel == 'debug'):print " called_from_line : " + str(get_line_number())return resultreturn wrapperreturn log_decorator

    但是,上面這個(gè)帶log level參數(shù)的有兩具不好的地方,
    1) loglevel不是debug的時(shí)候,還是要計(jì)算函數(shù)調(diào)用的時(shí)間。
    2) 不同level的要寫在一起,不易讀。

    我們?cè)俳又倪M(jìn):

    import inspectdef advance_logger(loglevel):def get_line_number():return inspect.currentframe().f_back.f_back.f_linenodef _basic_log(fn, result, *args, **kwargs):print "function = " + fn.__name__,print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)def info_log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):result = fn(*args, **kwargs)_basic_log(fn, result, args, kwargs)return wrapperdef debug_log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()_basic_log(fn, result, args, kwargs)print " time = %.6f sec" % (te-ts)print " called_from_line : " + str(get_line_number())return wrapperif loglevel is "debug":return debug_log_decoratorelse:return info_log_decorator

    你可以看到兩點(diǎn),
    1)我們分了兩個(gè)log level,一個(gè)是info的,一個(gè)是debug的,然后我們?cè)谕馕哺鶕?jù)不同的參數(shù)返回不同的decorator。
    2)我們把info和debug中的相同的代碼抽到了一個(gè)叫_basic_log的函數(shù)里,DRY原則。

    ?

    16.5 一個(gè)MySQL的Decorator

    下面這個(gè)decorator是我在工作中用到的代碼,我簡(jiǎn)化了一下,把DB連接池的代碼去掉了,這樣能簡(jiǎn)單點(diǎn),方便閱讀。

    import umysql from functools import wrapsclass Configuraion:def __init__(self, env):if env == "Prod":self.host = "coolshell.cn"self.port = 3306self.db = "coolshell"self.user = "coolshell"self.passwd = "fuckgfw"elif env == "Test":self.host = 'localhost'self.port = 3300self.user = 'coolshell'self.db = 'coolshell'self.passwd = 'fuckgfw'def mysql(sql):_conf = Configuraion(env="Prod")def on_sql_error(err):print errsys.exit(-1)def handle_sql_result(rs):if rs.rows > 0:fieldnames = [f[0] for f in rs.fields]return [dict(zip(fieldnames, r)) for r in rs.rows]else:return []def decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):mysqlconn = umysql.Connection()mysqlconn.settimeout(5)mysqlconn.connect(_conf.host, _conf.port, _conf.user, \_conf.passwd, _conf.db, True, 'utf8')try:rs = mysqlconn.query(sql, {})except umysql.Error as e:on_sql_error(e)data = handle_sql_result(rs)kwargs["data"] = dataresult = fn(*args, **kwargs)mysqlconn.close()return resultreturn wrapperreturn decorator@mysql(sql = "select * from coolshell" ) def get_coolshell(data):... ...... ..

    ?

    16.6 線程異步

    下面量個(gè)非常簡(jiǎn)單的異步執(zhí)行的decorator,注意,異步處理并不簡(jiǎn)單,下面只是一個(gè)示例。

    from threading import Thread from functools import wrapsdef async(func):@wraps(func)def async_func(*args, **kwargs):func_hl = Thread(target = func, args = args, kwargs = kwargs)func_hl.start()return func_hlreturn async_funcif __name__ == '__main__':from time import sleep@asyncdef print_somedata():print 'starting print_somedata'sleep(2)print 'print_somedata: 2 sec passed'sleep(2)print 'print_somedata: 2 sec passed'sleep(2)print 'finished print_somedata'def main():print_somedata()print 'back in main'print_somedata()print 'back in main'main()

    ?

    16.7?超時(shí)函數(shù)

    這個(gè)函數(shù)的作用在于可以給任意可能會(huì)hang住的函數(shù)添加超時(shí)功能,這個(gè)功能在編寫外部API調(diào)用 、網(wǎng)絡(luò)爬蟲、數(shù)據(jù)庫查詢的時(shí)候特別有用。

    timeout裝飾器的代碼如下:

    # coding=utf-8 # 測(cè)試utf-8編碼 import sysreload(sys) sys.setdefaultencoding('utf-8')import signal, functoolsclass TimeoutError(Exception): passdef timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):def decorated(func):result = ""def _handle_timeout(signum, frame):global resultresult = error_messageraise TimeoutError(error_message)def wrapper(*args, **kwargs):global resultsignal.signal(signal.SIGALRM, _handle_timeout)signal.alarm(seconds)try:result = func(*args, **kwargs)finally:signal.alarm(0)return resultreturn resultreturn functools.wraps(func)(wrapper)return decorated@timeout(2) # 限定下面的slowfunc函數(shù)如果在5s內(nèi)不返回就強(qiáng)制拋TimeoutError Exception結(jié)束 def slowfunc(sleep_time):a = 1import timetime.sleep(sleep_time)return a# slowfunc(3) #sleep 3秒,正常返回 沒有異常print slowfunc(11) # 被終止

    ?

    16.8 Trace函數(shù)

    有時(shí)候出于演示目的或者調(diào)試目的,我們需要程序運(yùn)行的時(shí)候打印出每一步的運(yùn)行順序 和調(diào)用邏輯。類似寫bash的時(shí)候的bash -x調(diào)試功能,然后Python解釋器并沒有 內(nèi)置這個(gè)時(shí)分有用的功能,那么我們就“自己動(dòng)手,豐衣足食”。

    Trace裝飾器的代碼如下:

    # coding=utf-8 # 測(cè)試utf-8編碼 import sys reload(sys) sys.setdefaultencoding('utf-8')import sys,os,linecache def trace(f):def globaltrace(frame, why, arg):if why == "call": return localtracereturn Nonedef localtrace(frame=1, why=2, arg=4):if why == "line":# record the file name and line number of every tracefilename = frame.f_code.co_filenamelineno = frame.f_linenobname = os.path.basename(filename)print "{}({}): {}".format( bname,lineno,linecache.getline(filename, lineno)),return localtracedef _f(*args, **kwds):sys.settrace(globaltrace)result = f(*args, **kwds)sys.settrace(None)return resultreturn _f@trace def xxx():a=1print aprint 22print 333xxx() #調(diào)用####################################### C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py t2.py(31): a=1 t2.py(32): print a 1 t2.py(33): print 22 22 t2.py(34): print 333 333Process finished with exit code 0

    ?

    16.9?單例模式

    示例代碼:

    # coding=utf-8 # 測(cè)試utf-8編碼 # 單例裝飾器 import sys reload(sys) sys.setdefaultencoding('utf-8')# 使用裝飾器實(shí)現(xiàn)簡(jiǎn)單的單例模式 def singleton(cls):instances = dict() # 初始為空def _singleton(*args, **kwargs):if cls not in instances: #如果不存在, 則創(chuàng)建并放入字典instances[cls] = cls(*args, **kwargs)return instances[cls]return _singleton@singleton class Test(object):pass if __name__ == '__main__':t1 = Test()t2 = Test()# 兩者具有相同的地址print t1print t2

    ?

    16.10?LRUCache

    下面要分享的這個(gè)LRUCache不是我做的,是github上的一個(gè)庫,我們?cè)趯?shí)際環(huán)境中有用到。

    先來說下這個(gè)概念,cache的意思就是緩存,LRU就是Least Recently Used,即最近最少使用,是一種內(nèi)存管理算法。總結(jié)來說這就是一種緩存方法,基于時(shí)間和容量。

    一般在簡(jiǎn)單的python程序中,遇到需要處理緩存的情況時(shí)最簡(jiǎn)單的方式,聲明一個(gè)全局的dict就能解決(在python中應(yīng)盡量避免使用全局變量)。但是只是簡(jiǎn)單情況,這種情況會(huì)帶來的問題就是內(nèi)存泄漏,因?yàn)榭赡軙?huì)出現(xiàn)一直不命中的情況。

    由此導(dǎo)致的一個(gè)需求就是,你要設(shè)定這個(gè)dict的最大容量,防止發(fā)生泄漏。但僅僅是設(shè)定最大容量是不夠的,設(shè)想當(dāng)你的dict變量已被占滿,還是沒有命中,該如何處理。

    這時(shí)就需要加一個(gè)失效時(shí)間了。如果在指定失效期內(nèi)沒有使用到該緩存,則刪除。

    綜述上面的需求和功能就是LRUCache干的事了。不過這份代碼做了更進(jìn)一層的封裝,可以讓你直接把緩存功能做為一個(gè)裝飾器來用。具體實(shí)現(xiàn)可以去參考代碼,別人實(shí)現(xiàn)之后看起來并不復(fù)雜 :)

    from lru import lru_cache_function@lru_cache_function(max_size=1024, expiration=15*60) def f(x):print "Calling f(" + str(x) + ")"return xf(3) # This will print "Calling f(3)", will return 3 f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).

    代碼:?https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py

    從python3.2開始內(nèi)置在functools了lru_cache的功能,說明這個(gè)需求是很普遍的。

    ?

    ?

    17. 小結(jié)

    在面向?qū)ο?#xff08;OOP)的設(shè)計(jì)模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實(shí)現(xiàn),而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數(shù)實(shí)現(xiàn),也可以用類實(shí)現(xiàn)。

    decorator可以增強(qiáng)函數(shù)的功能,定義起來雖然有點(diǎn)復(fù)雜,但使用起來非常靈活和方便。

    最后留個(gè)小作業(yè):

    請(qǐng)編寫一個(gè)decorator,能在函數(shù)調(diào)用的前后打印出'begin call'和'end call'的日志。

    再思考一下能否寫出一個(gè)@log的decorator,使它既支持:

    @log def f():pass

    又支持:

    @log('execute') def f():pass

    ?

    ?

    18. Refer:

    [1]?12步輕松搞定python裝飾器

    http://python.jobbole.com/81683/

    [2]?裝飾器

    liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000

    [3]?Python修飾器的函數(shù)式編程

    http://coolshell.cn/articles/11265.html

    [4]?Python Decorator Library

    https://wiki.python.org/moin/PythonDecoratorLibrary

    [5]?Python裝飾器實(shí)例:調(diào)用參數(shù)合法性驗(yàn)證

    http://python.jobbole.com/82114/

    [6]?Python 裝飾器

    http://python.jobbole.com/82344/

    [7]?兩個(gè)實(shí)用的Python的裝飾器

    http://blog.51reboot.com/%E4%B8%A4%E4%B8%AA%E5%AE%9E%E7%94%A8%E7%9A%84python%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8/

    [8]?Python 中的閉包總結(jié)

    http://dwz.cn/2CiO78

    [9]?Python 的閉包和裝飾器

    https://segmentfault.com/a/1190000004461404

    [10]?Python修飾器的問題

    https://segmentfault.com/q/1010000004595899

    ?

    ?

    ?

    ?

    總結(jié)

    以上是生活随笔為你收集整理的Python 装饰器 函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    精品99在线| 天天综合精品 | 91精品国产福利 | 国产精品欧美一区二区 | 91精品伦理 | 国产你懂的在线 | 日韩国产高清在线 | 最新国产精品亚洲 | 偷拍精偷拍精品欧洲亚洲网站 | 东方av免费在线观看 | 免费视频国产 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 欧美一级特黄aaaaaa大片在线观看 | 日韩国产精品毛片 | 国产精品第一页在线 | 国产精品久久久久久久久搜平片 | 中文字幕欧美三区 | 国产日韩高清在线 | 亚洲精品乱码久久久久久蜜桃动漫 | 欧美日韩中文在线视频 | 日韩av在线一区二区 | 精品视频成人 | 97超碰.com| 久久久久国产一区二区 | 99久久精品无免国产免费 | 91视频成人免费 | 亚洲精品字幕在线 | 99视频精品免费视频 | 成人羞羞视频在线观看免费 | 亚洲欧美日本一区二区三区 | 久久精品96 | 91天堂素人约啪 | 国产片免费在线观看视频 | 天天干天天干天天干 | 欧美一级片免费 | 国产久草在线观看 | 精品国产aⅴ麻豆 | 97操碰 | 婷婷四房综合激情五月 | 天天操天天射天天插 | 欧美精品一区二区在线播放 | 亚洲精品国产第一综合99久久 | 美女视频黄在线观看 | 九九免费观看全部免费视频 | 亚洲成人二区 | 高清免费av在线 | av中文字幕av | 中文字幕人成一区 | 免费h视频| 欧美精品被 | 日日夜夜网 | 福利视频导航网址 | 亚洲 欧美 国产 va在线影院 | 在线观看中文av | 成人app在线免费观看 | 人人干在线观看 | 久视频在线播放 | 99爱视频在线观看 | 国产精品久久久久永久免费看 | 国产视频高清 | 美女很黄免费网站 | 久久久影片 | 亚洲精品美女久久久久 | 91久久国产自产拍夜夜嗨 | 午夜视频在线观看一区二区三区 | 欧美人zozo| 久久久免费高清视频 | 正在播放 国产精品 | 免费观看全黄做爰大片国产 | 手机看片 | 久久99精品久久久久久秒播蜜臀 | 婷婷色网址 | 99精品视频在线观看视频 | 久久免费在线观看视频 | 亚洲国产精品资源 | 五月综合激情网 | 国产正在播放 | 久久y| 久久综合偷偷噜噜噜色 | 免费看黄20分钟 | 91亚洲精品国偷拍 | 国产日韩亚洲 | 久久网站av | 三级视频国产 | 高清不卡一区二区在线 | .国产精品成人自产拍在线观看6 | 婷婷丁香av | 欧美日韩国产一区二区三区在线观看 | 超碰在线94 | 色av男人的天堂免费在线 | 午夜视频99 | 色五月色开心色婷婷色丁香 | 热久久电影| 开心色停停 | av在线直接看 | 久久美女视频 | 日韩欧美综合在线视频 | 特级毛片在线免费观看 | 久久在线视频精品 | 91人人爽人人爽人人精88v | 日韩美一区二区三区 | 精品国产欧美一区二区 | 五月婷婷在线综合 | 国产成人一二片 | 精品亚洲免费视频 | 国产一区二区三区四区大秀 | 香蕉视频在线免费 | 精品一区二区综合 | 久久久国产一区二区三区四区小说 | 成片人卡1卡2卡3手机免费看 | 久草在线最新 | 成人综合免费 | 欧美精品在线视频 | 五月婷婷影院 | 精品久久精品久久 | 国产精品不卡av | 亚洲爱爱视频 | 欧美 亚洲 另类 激情 另类 | 欧美在线观看视频一区二区三区 | 亚洲国产精品成人女人久久 | 亚洲情感电影大片 | 亚洲精品看片 | 国产伦理一区二区 | 91视频在线播放视频 | 国产精品福利在线播放 | 在线不卡中文字幕播放 | 亚洲成av| 天天爱天天操天天干 | 国产视频中文字幕在线观看 | 色香com.| 国产一区二区三区四区在线 | 成人黄色小视频 | 国产成人精品网站 | 婷婷色中文网 | 色综合中文字幕 | 久久久亚洲精品 | 亚洲综合精品在线 | 玖草影院 | 国产精品 国产精品 | 午夜 免费 | 久久a热6| 麻豆影视在线播放 | www.午夜| 91色偷偷| 久久观看| 视频 天天草| 一区 在线观看 | 亚洲 中文 欧美 日韩vr 在线 | 国产精品一区二区中文字幕 | 天天操夜夜逼 | 精品二区久久 | 欧美在线视频一区二区三区 | 免费色av | 成人欧美一区二区三区在线观看 | 日日干美女 | 中文字幕乱码在线播放 | 精品日韩在线 | 天天操天天干天天插 | 黄色视屏av| www五月 | 911久久 | 国精产品999国精产品视频 | www.久久免费视频 | 91桃色在线免费观看 | 亚洲黄色在线播放 | 国产小视频在线看 | 日韩电影久久久 | 亚洲精品乱码久久久久久久久久 | 亚洲欧洲视频 | 91av在线不卡 | 国产精品久久久久一区二区 | 久久成人视屏 | 四虎在线观看精品视频 | 九九免费精品视频在线观看 | 91精品国产一区二区在线观看 | 日韩久久精品一区二区三区下载 | 91网址在线| 国产精品久久久久久久久久久不卡 | 岛国av在线免费 | 亚洲综合小说电影qvod | 日本精品久久久久影院 | 国产又黄又硬又爽 | 国产激情小视频在线观看 | 国产综合精品久久 | 天天曰天天爽 | 日韩精品高清视频 | 欧美日韩视频一区二区 | 这里有精品在线视频 | 国产精品成人久久久久久久 | 欧美精品久久久久久久久久 | 久久精品91久久久久久再现 | 久久一线 | 久久免费国产 | 四虎影视精品成人 | 五月激情丁香婷婷 | 91亚洲精品国产 | 亚洲精品观看 | 亚洲国产中文在线 | 久久婷婷影视 | 国产精品18毛片一区二区 | av成人在线网站 | 欧美激情视频在线观看免费 | 亚洲精品福利视频 | 成年人黄色大片在线 | 国产黄色看片 | 狠狠干我 | 国产精品一区二区三区在线看 | 蜜臀久久99精品久久久久久网站 | 在线日韩av| 色全色在线资源网 | 国产精品视频久久 | 99色国产 | 99热在线国产精品 | 欧美激情一区不卡 | 中文字幕精品视频 | 久久天堂网站 | 三级视频日韩 | 亚洲免费av电影 | 99超碰在线播放 | 黄色亚洲大片免费在线观看 | 搡bbbb搡bbb视频 | 国产久视频| 91精品看片| 天天色成人网 | 五月花激情 | 日韩三级久久 | 久久亚洲综合国产精品99麻豆的功能介绍 | 天天爽天天爽天天爽 | 黄色软件在线看 | 在线观看中文字幕一区 | 日韩在线视频精品 | 国产欧美精品一区二区三区 | 在线免费观看的av | 成人免费观看网址 | 日韩在线理论 | 国产日韩视频在线 | 色婷婷伊人 | 91干干干 | 久久国产网 | 国产精品手机视频 | 精品国产一区二区三区四区在线观看 | 韩日电影在线 | 国产精品爽爽爽 | 91精选在线观看 | 国产精品久久久久久一区二区三区 | 国产视频一区在线免费观看 | 韩国三级av在线 | 天天操狠狠操网站 | 国产精品久久久久久久久久久久 | 久久久免费观看完整版 | 亚洲日韩精品欧美一区二区 | 亚洲天天做 | 日韩精品一卡 | 综合激情久久 | 日韩伦理片一区二区三区 | 免费在线观看不卡av | 婷婷激情久久 | 在线观看国产www | av中文字幕第一页 | 九九九毛片 | 国产欧美日韩精品一区二区免费 | 国内精品久久久久久久影视简单 | 久久夜av| 国产一级片视频 | 久久国产精品色av免费看 | 久久久久久久久久久久影院 | 久久人人爽人人爽人人 | 激情导航 | 手机在线免费av | 欧美精品乱码久久久久久按摩 | 日韩激情视频在线观看 | 伊人永久 | 国产成人精品999在线观看 | 狠狠色丁香久久婷婷综合_中 | 国产精品福利在线 | www色综合 | 免费三级影片 | 天天操夜夜逼 | 日韩色高清 | 不卡中文字幕在线 | 69av网| 日韩免费一区二区 | 久久成熟 | 国产污视频在线观看 | 久久久免费毛片 | 国内久久精品视频 | 亚洲国产精品久久久久婷婷884 | 超碰在线日韩 | 在线免费高清视频 | 国产精品原创av片国产免费 | 欧美久久综合 | 超碰最新网址 | 99 色| 99久久精品国产欧美主题曲 | 日韩电影在线一区二区 | 久久久国产精品视频 | 色综合久久综合中文综合网 | 96看片| 五月综合在线观看 | 久久综合五月天 | 亚洲国产大片 | 久久久久国产一区二区 | 麻豆精品传媒视频 | 伊人成人激情 | 亚洲精品玖玖玖av在线看 | 四虎4hu永久免费 | 视频三区 | av三级在线播放 | 日本久久99| 碰碰影院 | 51久久成人国产精品麻豆 | 国产免费中文字幕 | av高清影院 | 精品国产一区二区三区久久久 | 99欧美视频 | 91在线视频在线 | 狠狠色丁香婷婷综合久久片 | 久久精品99国产精品亚洲最刺激 | 中文在线免费观看 | 九九免费视频 | 国产成人精品一区二区三区在线 | 激情婷婷网| 99视频国产精品免费观看 | 91精品国产乱码在线观看 | 91九色porny在线 | 婷婷激情久久 | 亚洲国内精品在线 | 奇米影视777四色米奇影院 | 亚洲电影自拍 | 久久试看 | 波多野结衣亚洲一区二区 | 久久国产色 | 日本视频精品 | 正在播放久久 | 精品一区 在线 | 欧美极度另类 | 91一区啪爱嗯打偷拍欧美 | 久久精品99久久 | 91亚洲视频在线观看 | 西西www4444大胆在线 | 丁香婷婷激情 | 8x成人免费视频 | 亚洲清纯国产 | 日本不卡一区二区三区在线观看 | 中文字幕欧美三区 | 在线观看国产91 | 日韩在线观看中文字幕 | 国产一级免费片 | 久久首页 | 国语久久 | 精品特级毛片 | 性色在线视频 | 欧美精品中文字幕亚洲专区 | 久久69精品久久久久久久电影好 | 在线观看视频中文字幕 | 欧美精品在线免费 | 亚洲精品在线看 | 国产精品av在线免费观看 | 狠狠色噜噜狠狠狠狠2022 | 91精品久久久久久综合乱菊 | 日韩高清在线不卡 | 黄色影院在线免费观看 | 国产又粗又猛又爽 | 精品国产美女 | 欧美在线视频一区二区三区 | 91欧美视频网站 | 国产精品高清一区二区三区 | 美女网色 | 91精品在线观看入口 | 国产精品成人一区二区三区 | 国内外成人免费在线视频 | 色在线中文字幕 | 99精品国产高清在线观看 | 久草在在线| 日韩午夜电影网 | 91国内在线视频 | 日韩精品一区二区在线观看视频 | 精品高清视频 | 97超碰人人模人人人爽人人爱 | 粉嫩av一区二区三区四区在线观看 | 美女网站视频免费黄 | 午夜三级理论 | 在线a人v观看视频 | 成人av影视观看 | 夜夜躁日日躁狠狠久久88av | 黄色毛片在线观看 | 日韩久久久久久久 | 一区二区中文字幕在线播放 | 色视频 在线 | 夜添久久精品亚洲国产精品 | 日韩毛片在线播放 | 综合久久久久 | 国产成人一区二区三区在线观看 | 在线观看黄色的网站 | 国产视频 亚洲精品 | 一区二区三区电影大全 | 永久免费视频国产 | 国产精品手机看片 | 国产一级片一区二区三区 | 国产精品色婷婷视频 | 就要色综合 | 在线观看黄污 | 国产高清久久久 | 色婷婷啪啪免费在线电影观看 | 欧美精品久久久久久久免费 | 亚洲国产精品一区二区久久,亚洲午夜 | 成人黄色电影视频 | 97在线公开视频 | 91亚洲夫妻 | 在线91精品 | 久久综合中文色婷婷 | 欧美日韩中| 国产群p | 国产精品久久久久一区二区国产 | www色av| 黄色的视频| 中文字幕免费高清 | 国产99久久九九精品免费 | 一区免费观看 | 91在线一区 | 国产精品久久毛片 | 日韩视频专区 | 亚洲国产精品久久久久久 | 日本久久成人 | 日韩欧美视频一区二区三区 | 在线午夜| 中文字幕乱码电影 | 成人免费在线观看入口 | 偷拍福利视频一区二区三区 | 亚洲国产美女久久久久 | 国产日韩欧美在线看 | 亚洲aⅴ在线观看 | 国产精品精品视频 | 69绿帽绿奴3pvideos | 欧美一级电影片 | 在线观看aaa | 国产污视频在线观看 | 久久九九久久九九 | 天天激情 | 欧美黄色成人 | aa一级片| 欧美日韩亚洲第一 | 免费在线观看成年人视频 | 黄色片毛片 | 看黄色91 | 亚洲精品久久久久www | 日日干综合 | 国产高清视频在线 | 亚洲精品国偷拍自产在线观看 | 在线观看成年人 | 成人免费视频观看 | 中国成人一区 | 中文乱幕日产无线码1区 | 日本中文乱码卡一卡二新区 | 亚洲经典中文字幕 | 日本中文乱码卡一卡二新区 | 午夜精品一二三区 | 欧美精品成人在线 | 国产一二三区在线观看 | 亚洲国产精品成人va在线观看 | 久草视频免费播放 | 在线精品视频免费观看 | 九九爱免费视频在线观看 | a黄色| 国产五月色婷婷六月丁香视频 | 久久私人影院 | 色综合咪咪久久网 | 天天天天天天操 | 8090yy亚洲精品久久 | 亚洲精品视频免费在线观看 | 精品国产一区二区三区久久久蜜臀 | 亚洲欧美一区二区三区孕妇写真 | 999视频在线播放 | 国产精品99久久久久人中文网介绍 | 亚洲精品久久久久久中文传媒 | 免费日韩在线 | 国产精品久久久久久久久免费 | 国产精品一区二区在线 | 在线一级片 | www.久久久久 | 在线观看91精品国产网站 | 在线观看国产高清视频 | 99r在线精品 | 日韩高清一二区 | 特黄特色特刺激视频免费播放 | 亚洲精品免费在线播放 | 久久久精品网 | 国产精品v欧美精品 | 亚洲综合视频网 | 国产精品a久久久久 | 成人啪啪18免费游戏链接 | 超碰人人av | www成人精品 | 欧美日韩国产一二三区 | 国产老太婆免费交性大片 | 午夜久久电影网 | 国产视频精选在线 | 亚洲精品动漫在线 | 免费网站在线观看人 | 日韩精品在线视频免费观看 | 国产精品久久久久久久久软件 | 日韩欧美在线观看一区二区三区 | 亚洲精品国产拍在线 | 亚洲成av| 狠狠天天| 精品国产电影一区二区 | 美女视频黄是免费的 | 成人毛片在线视频 | 日韩激情视频在线 | av不卡免费看 | 久久99免费观看 | 免费成人在线观看视频 | 久久久精品日本 | 精品国产人成亚洲区 | 婷婷午夜天 | 久久色亚洲 | 99精品视频在线免费观看 | 色狠狠综合天天综合综合 | 久草精品在线观看 | 色婷婷伊人 | 最近最新中文字幕视频 | 黄色免费国产 | 色综合久久五月天 | 在线观看精品黄av片免费 | 成人在线观看av | 极品美女被弄高潮视频网站 | 国产999精品视频 | 又黄又爽又无遮挡免费的网站 | 成人va天堂| 在线观看一二三区 | av成人免费| www日韩视频 | 免费h漫在线观看 | www天天干com | 中文字幕在线观看网 | 久久综合成人网 | 欧美成人中文字幕 | 91精品视频观看 | 啪啪小视频网站 | 99国产精品一区二区 | 国产日韩精品一区二区在线观看播放 | 亚洲 欧美 精品 | 亚洲欧美激情精品一区二区 | 日韩精品视频免费在线观看 | 99热 精品在线 | 国产精品成人国产乱一区 | 深夜福利视频一区二区 | 91av视频在线观看 | 字幕网资源站中文字幕 | 色操插| 国产精品国产三级国产aⅴ入口 | 又湿又紧又大又爽a视频国产 | 亚洲欧美日韩国产精品一区午夜 | 国产精品毛片久久蜜 | 国产不卡精品 | 人人澡人人舔 | 中文字幕在线视频一区 | 九色激情网 | 国产视频18 | 91字幕 | 欧美人体xx | 久久综合亚洲鲁鲁五月久久 | 四川bbb搡bbb爽爽视频 | 一级做a视频 | 日韩a级免费视频 | 国际av在线 | 国产精品激情偷乱一区二区∴ | 91tv国产成人福利 | 国产女人免费看a级丨片 | 又粗又长又大又爽又黄少妇毛片 | 亚洲成人黄色av | 精品国产诱惑 | 亚洲一区二区黄色 | 91高清免费 | 久久另类小说 | 国产精品18久久久久久久 | 国产精品麻豆视频 | av电影在线免费 | 国产中文字幕91 | 日韩城人在线 | 九九免费在线视频 | 久久视频 | 日韩精品一区二区三区免费视频观看 | 久久精品亚洲国产 | 黄色在线成人 | 国产黄色视 | 二区中文字幕 | 中文在线8资源库 | 911精品视频 | 色多多在线观看 | 久久免费成人网 | 欧美激情亚洲综合 | 91免费观看网站 | 国产乱老熟视频网88av | 日韩精品aaa | 日韩免费在线一区 | 欧美一级高清片 | 亚洲电影第一页av | 99视频这里有精品 | 国产欧美精品xxxx另类 | 日本激情视频中文字幕 | 久久人人爽人人片 | 欧美激情视频在线免费观看 | 国产精品18久久久久久久久久久久 | 四虎在线观看网址 | 国产精品字幕 | 免费高清在线观看成人 | 91精品国产高清自在线观看 | 免费视频资源 | 久久久久久久久久免费 | 国产一级黄 | 午夜91在线| 在线观看涩涩 | 成人在线播放视频 | 日韩欧美v| 久久国产精品久久精品 | 国精产品999国精产 久久久久 | 日韩免费视频 | 91漂亮少妇露脸在线播放 | 久久经典视频 | 国产一区视频在线播放 | 精品中文字幕视频 | 精品亚洲视频在线观看 | 欧美成人91| 精品麻豆入口免费 | 探花系列在线 | 亚洲午夜精品福利 | 日韩精品中文字幕一区二区 | 久久久久久久久久久网站 | 中文字幕av播放 | 99国产成+人+综合+亚洲 欧美 | 婷久久| 四虎在线影视 | 97人人模人人爽人人少妇 | 欧美色伊人 | 99久热在线精品视频成人一区 | 成年人在线电影 | 亚洲黄色成人网 | 日本高清dvd | 国产一区二区三区 在线 | 亚洲国产精品小视频 | 国产精品成人一区二区三区吃奶 | 人人爽人人爱 | 国产精品中文久久久久久久 | 国产不卡免费 | 香蕉日日| 日韩av一区二区三区四区 | 精品一二三区视频 | 黄色小说免费在线观看 | 黄网站污| 手机av观看 | 黄色影院在线播放 | 国产一级大片在线观看 | 99精彩视频| 日韩欧美一区二区三区视频 | 91亚洲夫妻 | 在线观看黄色大片 | 三级黄免费看 | 色网站在线免费观看 | 中文字幕二区在线观看 | 免费一级片在线观看 | 西西444www高清大胆 | 国产 视频 高清 免费 | a级片久久| 亚洲国产无| 久久艹中文字幕 | 久久精品国产免费观看 | 27xxoo无遮挡动态视频 | 欧美五月婷婷 | 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 国产一区二区精 | 久久久91精品国产 | 亚洲欧洲xxxx | 午夜精品久久久久久久99婷婷 | 成人毛片一区二区三区 | 国产黄色精品在线观看 | 欧美极品久久 | 国语精品久久 | 中文字幕在线乱 | 久久久麻豆 | 欧美日韩一级久久久久久免费看 | 国产精品黑丝在线观看 | 91久久国产露脸精品国产闺蜜 | 91激情| 国产涩涩网站 | 在线视频91| 国产精品久久久精品 | 国产视频一区二区三区在线 | 中文字幕一区二区三区四区久久 | 国产中文字幕亚洲 | 四虎影视精品 | 毛片随便看 | 亚洲资源在线 | 亚洲日本va在线观看 | 97精品视频在线播放 | 欧美韩国日本在线 | 91免费观看国产 | 9992tv成人免费看片 | 天天射射天天 | a级成人毛片 | 国产精品一二 | 久久99精品国产91久久来源 | 午夜视频二区 | 国产成人精品在线观看 | 国产精品久久久久永久免费看 | 一区二区三区在线播放 | 在线免费观看成人 | 色之综合网 | 不卡精品 | 九九交易行官网 | 日韩免费在线观看网站 | 999久久久免费精品国产 | 亚洲精品97 | a亚洲视频| av在线永久免费观看 | 91av在线精品 | 人人爽人人爽人人片av | 激情文学综合丁香 | 婷婷丁香在线 | bbbbb女女女女女bbbbb国产 | 91cn国产在线 | 又色又爽又黄高潮的免费视频 | 一本色道久久精品 | 久久久久久久综合色一本 | 国产福利在线不卡 | 免费美女av | 日韩精品电影在线播放 | 欧美aaaxxxx做受视频 | 奇米影视777影音先锋 | 五月激情婷婷丁香 | 久久久久久久国产精品影院 | av久久在线 | 亚洲一级电影 | 日韩在线观看电影 | 天天干人人插 | 天天爽夜夜爽精品视频婷婷 | 在线影院 国内精品 | 一区二区精品在线视频 | 国产精品99久久久久久久久久久久 | 欧美夫妻性生活电影 | 欧美a级在线免费观看 | 97色在线视频 | 国内精品视频一区二区三区八戒 | 超碰精品在线观看 | 丁香婷婷在线观看 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 在线视频一二区 | 一本一道久久a久久综合蜜桃 | 国产免费黄色 | 日韩a级黄色 | 欧美精品少妇xxxxx喷水 | 欧美另类sm图片 | 成人毛片网 | 麻豆成人精品视频 | 日韩午夜在线 | 亚洲黄色小说网 | 综合久久一本 | av在线影视 | 久久久久免费观看 | 99久久日韩精品视频免费在线观看 | 91视频 - x99av | 99热九九这里只有精品10 | 91久久国产自产拍夜夜嗨 | 99久久精品国产系列 | 色妞久久福利网 | av在线电影播放 | 黄网站色视频免费观看 | 久久全国免费视频 | 免费看高清毛片 | 久久久久美女 | 黄色精品久久 | 中文电影网 | 99热这里精品 | 国产成人精品一二三区 | 日本视频不卡 | 天天爱天天射 | 91网站免费观看 | av在线播放不卡 | 国产高清在线观看av | 久久婷婷一区 | 看片的网址 | 亚洲黄色一级大片 | 97精品国产97久久久久久 | 国产成人精品一区二区三区福利 | 亚洲视频 视频在线 | av网址在线播放 | 人人要人人澡人人爽人人dvd | 在线视频一二区 | 精品a级片 | 五月激情久久 | 久久国产精品视频观看 | 成人毛片a | 一级成人网 | 国产精品中文久久久久久久 | 欧美一区二区三区特黄 | 天天看天天干天天操 | 99在线免费观看 | 黄色免费观看 | 国产成人久久av977小说 | 五月激情久久 | 精品中文字幕视频 | 日韩91精品| 精品久久久久久久久久久院品网 | 中文在线免费看视频 | 日韩精品视频在线观看免费 | 日本视频不卡 | 国产精品成人自拍 | 丁香五月缴情综合网 | 黄a在线看 | 久久不射电影院 | 欧美视频www | 国产99久久九九精品免费 | 操操操com| 国产精选在线 | 午夜久久久久久久久 | 五月天久久激情 | 天天天干 | 狠狠操狠狠干2017 | 国产91免费在线观看 | 久久99久国产精品黄毛片入口 | 国产精品精品国产婷婷这里av | 97国产大学生情侣酒店的特点 | 在线观看一区二区视频 | 国产一区二区精品久久91 | 日韩在线观看你懂的 | 天天看天天干 | 国产成人精品一区二区三区在线观看 | 午夜精品久久久久久久99 | 丁香六月婷婷开心婷婷网 | 99久久婷婷国产一区二区三区 | 色综合网在线 | 国产精品黄色 | 日韩在线观看不卡 | 色综合www | av经典在线 | 国产天天爽 | 久久精品99国产精品亚洲最刺激 | 久久精品国产亚洲精品 | 久久久夜色| 精品成人在线 | 激情综合电影网 | 国产精品久久久久久久久久久久午 | 国产黄视频在线观看 | 人成免费网站 | 国产精品国产自产拍高清av | 亚洲成av人片 | 久久国内精品视频 | 精品国产免费观看 | www.超碰 | 国产伦精品一区二区三区在线 | av一区二区三区在线观看 | 国产成人一区二区三区电影 | 超碰在线人 | 97日日 | avwww在线 | 久久久久久久久久免费视频 | 久精品视频在线 | 亚洲精品999| 国产精品第一页在线 | 色99之美女主播在线视频 | 国产理论一区二区三区 | 婷婷色在线视频 | 日日操天天操狠狠操 | 久久久受www免费人成 | 国产a级片免费观看 | 欧美亚洲精品一区 | 精品综合久久久 | 成人一级片视频 | 久久综合丁香 | 亚洲成a人片77777kkkk1在线观看 | 亚洲国产影院av久久久久 | 国产成人av电影在线观看 | 激情偷乱人伦小说视频在线观看 | 欧美日韩国产二区 | 六月丁香婷 | 国产手机视频 | 五月婷在线视频 | 女人魂免费观看 | 久久成人高清视频 | 久草国产精品 | 中文在线最新版天堂 | 精品一区二区亚洲 | 最新av免费 | 又黄又刺激又爽的视频 | 18性欧美xxxⅹ性满足 | 91九色视频国产 | 国产传媒一区在线 | 久久黄网站 | 婷婷激情小说网 | 在线观看视频黄 | 中字幕视频在线永久在线观看免费 | 欧美一二在线 | 中文字幕有码在线 | 国产精品免费高清 | 久久久久久黄 | 天天操天天操天天干 | 国产成人精品av在线观 | 在线观看www视频 | 亚洲视频www | 99视频在线观看视频 | 国产日韩欧美视频在线观看 | 成年人视频在线免费观看 | 成人av高清在线 | 亚洲有 在线 | 蜜桃视频在线观看一区 | 国产精品原创在线 | 一区中文字幕电影 | 99热这里只有精品在线观看 | 99精品在线看 | 特级西西444www大胆高清无视频 | www.亚洲精品 | 国产区精品区 | 国产精品高 | 久射网| 久久69精品| 天堂激情网| 国产精品久久嫩一区二区免费 | 91社区国产高清 | 韩日av在线 | 国产一级三级 | 日p视频 | 日韩免费高清在线观看 | 91av在线免费观看 | 国产手机视频在线播放 | 五月开心激情 | 一区二区视频播放 | 国产在线观看免费观看 | 亚洲综合婷婷 | 成人精品电影 | 国产精品一区二区精品视频免费看 | 国产小视频免费在线网址 | 91欧美视频网站 | 中文字幕日韩高清 | 中文字幕二区在线观看 | 日韩av在线高清 | 国产日韩欧美精品在线观看 | 男女激情片在线观看 | 久草精品资源 | 麻豆国产在线播放 | 日韩一级片网址 | 一级淫片在线观看 | 精品字幕在线 | 西西www4444大胆在线 | 国产精品一区二区在线观看 | 国产小视频在线观看免费 | 麻豆极品 | 精品国产人成亚洲区 | 天天视频亚洲 | 免费视频久久久久久久 | av专区在线| 大荫蒂欧美视频另类xxxx | www.香蕉视频 | 二区三区中文字幕 | av亚洲产国偷v产偷v自拍小说 | 99re亚洲国产精品 | 久久久久久久久久免费 | 在线免费黄网站 | 亚洲精品tv久久久久久久久久 | 美女性爽视频国产免费app | 男女激情免费网站 | 国产精品99久久久久久久久久久久 | 久久国产精品99久久久久久进口 | 狠狠精品 | 国产一级特黄毛片在线毛片 | 国产九九九精品视频 | 欧美a级在线 | 片黄色毛片黄色毛片 | 丁香婷婷成人 | 99国产精品久久久久老师 | 国产亚洲午夜高清国产拍精品 | 日韩av黄 | 特黄色大片 | 91香蕉视频在线下载 | 中文字幕在线播放视频 | 天天干天天天 | 日韩中文字幕视频在线观看 | 日韩精选在线 | 九九精品视频在线观看 | 久久99精品国产一区二区三区 | 91精品免费 | 国产免费资源 | 黄网站app在线观看免费视频 | 日韩黄色大片在线观看 | 成年人在线免费视频观看 | 成人午夜av电影 | 国产精品永久在线观看 | 久操97| av中文字幕在线免费观看 | 91香蕉视频在线 | 五月天综合激情网 | 天海翼一区二区三区免费 | 最近中文字幕视频完整版 | 91亚洲精品国偷拍自产在线观看 | 国产精品精品国产婷婷这里av | 国产精品美女免费视频 | 在线观看一区二区精品 | 激情片av| 亚洲视频精品 |