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

歡迎訪問 生活随笔!

生活随笔

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

python

《流畅的Python》 读书笔记 第7章_函数装饰器和闭包

發(fā)布時(shí)間:2023/11/16 python 71 coder
生活随笔 收集整理的這篇文章主要介紹了 《流畅的Python》 读书笔记 第7章_函数装饰器和闭包 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

第7章 函數(shù)裝飾器和閉包

裝飾器這個(gè)名稱可能更適合在編譯器領(lǐng)域使用,因?yàn)樗鼤?huì)遍歷并注解句法樹

函數(shù)裝飾器用于在源碼中“標(biāo)記”函數(shù),以某種方式增強(qiáng)函數(shù)的行為。這是一項(xiàng)強(qiáng)大的功能,但是若想掌握,必須理解閉包

如果你想自己實(shí)現(xiàn)函數(shù)裝飾器,那就必須了解閉包的方方面面,因此也就需要知道 nonlocal

閉包還是回調(diào)式異步編程和函數(shù)式編程風(fēng)格的基礎(chǔ)

本章的最終目標(biāo)是解釋清楚函數(shù)裝飾器的工作原理,包括最簡(jiǎn)單的注冊(cè)裝飾器和較復(fù)雜的參數(shù)化裝飾器

討論如下話題

  • Python 如何計(jì)算裝飾器句法
  • Python 如何判斷變量是不是局部的
  • 閉包存在的原因和工作原理
  • nonlocal 能解決什么問題

再進(jìn)一步探討

  • 實(shí)現(xiàn)行為良好的裝飾器
  • 標(biāo)準(zhǔn)庫中有用的裝飾器
  • 實(shí)現(xiàn)一個(gè)參數(shù)化裝飾器

7.1 裝飾器基礎(chǔ)知識(shí)

裝飾器是可調(diào)用的對(duì)象,其參數(shù)是另一個(gè)函數(shù)(被裝飾的函數(shù))

裝飾器可能會(huì)處理被裝飾的函數(shù),然后把它返回,或者將其替換成另一個(gè)函數(shù)或可調(diào)用對(duì)象

@decorate
def target():
	print('running target()')

等價(jià)于

def target():
	print('running target()')
target = decorate(target)

此處的decorate是你定義好的裝飾器,姑且認(rèn)為是個(gè)函數(shù)

這個(gè)函數(shù)被更改了,這也是網(wǎng)上流傳裝飾器萬能公式,記住了這點(diǎn)其實(shí)理解裝飾器或?qū)憘€(gè)簡(jiǎn)單的裝飾器是很容易的。

裝飾器的適用范圍非常廣泛,你可以參考《7.12 延伸閱讀- 關(guān)于裝飾器的一個(gè)典型應(yīng)用》

來看一個(gè)完整的例子

def deco(func):
    def inner():
        func() # 注: 此處我加的
        print('running inner()')
    return inner #?

@deco
def target(): #?
    print('running target()')

target() #?

print(target) #?

? deco 返回 inner 函數(shù)對(duì)象。
? 使用 deco 裝飾 target。
? 調(diào)用被裝飾的 target 其實(shí)會(huì)運(yùn)行 inner。
? 審查對(duì)象,發(fā)現(xiàn) target 現(xiàn)在是 inner 的引用

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

running target()
running inner()
<function deco.<locals>.inner at 0x00000190D7E77D30>

可以看到如果target沒加這個(gè)裝飾器,肯定是單單執(zhí)行running target(),但加了裝飾器后

看似target執(zhí)行可以多出來running inner(),實(shí)際上此時(shí)的target已經(jīng)不再是原來的它了,它變了

根據(jù)萬能公式

@deco
def target():
	pass

你這樣后會(huì)讓target變?yōu)?code>target = deco(target)

再根據(jù)deco的定義

def deco(func):
	...
    return inner #?

你在執(zhí)行deco(target)的時(shí)候,返回的是一個(gè)叫inner的東西

因?yàn)槟阕罱K執(zhí)行的是target(),所以也就是inner()

再看inner定義

    def inner():
        func()
        print('running inner()')

inner()的時(shí)候,會(huì)執(zhí)行func()func來自deco的實(shí)參,此處對(duì)應(yīng)target,所以你會(huì)先執(zhí)行target(),再執(zhí)行print('running inner()')

裝飾器只是語法糖

裝飾器可以像常規(guī)的可調(diào)用對(duì)象那樣調(diào)用,其參數(shù)是另一個(gè)函數(shù)。有時(shí),這樣做更方便,尤其是做元編程(在運(yùn)行時(shí)改變程序的行為)時(shí)

裝飾器的一大特性是,能把被裝飾的函數(shù)替換成其他函數(shù)

第二個(gè)特性是,裝飾器在加載模塊時(shí)立即執(zhí)行

7.2 Python何時(shí)執(zhí)行裝飾器

裝飾器的一個(gè)關(guān)鍵特性是,它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。這通常是在導(dǎo)入時(shí)(即 Python 加載模塊時(shí))

書中的示例 registration.py 模塊

registry = [] #?
def register(func): #?
    print('running register(%s)' % func) #?
    registry.append(func) #?
    return func #?
@register #?
def f1():
    print('running f1()')
@register
def f2():
    print('running f2()')
def f3(): #?
    print('running f3()')
def main(): #?
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()
if __name__=='__main__': 
    main() # ?

? registry 保存被 @register 裝飾的函數(shù)引用。
? register 的參數(shù)是一個(gè)函數(shù)。
? 為了演示,顯示被裝飾的函數(shù)。
? 把 func 存入 registry。
? 返回 func:必須返回函數(shù);這里返回的函數(shù)與通過參數(shù)傳入的一樣。
? f1 和 f2 被 @register 裝飾。
? f3 沒有裝飾。
? main 顯示 registry,然后調(diào)用 f1()、f2() 和 f3()。
? 只有把 registration.py 當(dāng)作腳本運(yùn)行時(shí)才調(diào)用 main()。

我做了一些測(cè)試

  1. 21行的main()不寫,直接就一個(gè)pass,也會(huì)執(zhí)行
running register(<function f1 at 0x000001940F3D7EE0>)
running register(<function f2 at 0x000001940F3F6040>)
  1. 這個(gè)跟你import 這個(gè)py文件的效果是一樣的,也充分說明了在導(dǎo)入時(shí)立即運(yùn)行
  2. 這也是為何你在打印registry這個(gè)列表的時(shí)候已經(jīng)能看到里面有2個(gè)
  3. 類似的你把21行改為f1(),會(huì)打印如下。注意,有了上面的概念,你可能反而會(huì)覺得是不是會(huì)多打印一個(gè)running register...,實(shí)則不然。
running register(<function f1 at 0x0000021998027E50>)
running register(<function f2 at 0x0000021998027D30>)
running f1()
  1. 最終寫上main()的運(yùn)行效果
running register(<function f1 at 0x000002A0F6CF7E50>)
running register(<function f2 at 0x000002A0F6CF7D30>)
running main()
registry -> [<function f1 at 0x000002A0F6CF7E50>, <function f2 at 0x000002A0F6CF7D30>]
running f1()
running f2()
running f3()

函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。這突出了 Python 程序員所說的導(dǎo)入時(shí)和運(yùn)行時(shí)之間的區(qū)別

  • 裝飾器函數(shù)與被裝飾的函數(shù)在同一個(gè)模塊中定義。實(shí)際情況是,裝飾器通常在一個(gè)模塊中定義,然后應(yīng)用到其他模塊中的函數(shù)上。
  • register 裝飾器返回的函數(shù)與通過參數(shù)傳入的相同。實(shí)際上,大多數(shù)裝飾器會(huì)在內(nèi)部定義一個(gè)函數(shù),然后將其返回。

裝飾器原封不動(dòng)地返回被裝飾的函數(shù),但是這種技術(shù)并非沒有用處。很多 Python Web 框架使用這樣的裝飾器把函數(shù)添加到某種*注冊(cè)處,例如把URL 模式映射到生成 HTTP 響應(yīng)的函數(shù)上的注冊(cè)處。這種注冊(cè)裝飾器可能會(huì)也可能不會(huì)修改被裝飾的函數(shù)

7.3 使用裝飾器改進(jìn)“策略”模式

策略模式是第6章的內(nèi)容,比較的拗口,就先不寫了。

TODO

7.4 變量作用域規(guī)則

>>> def f(a): print(a)
...     print(b)
  File "<stdin>", line 2
    print(b)
IndentationError: unexpected indent
>>> def f(a):
...     print(a)
...     print(b)
...
>>> f(1)
1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f
NameError: name 'b' is not defined

在書中,最后一行是這樣的

NameError: global name 'b' is not defined

雖然顯示不同(從Python3.5開始的),但的確b還是那個(gè)global,用生成的字節(jié)碼可以說明這點(diǎn)

>>> from dis import dis
>>> dis(f)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_GLOBAL              1 (b)  # 看這里
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

加一個(gè)b的定義就能正常輸出了

>>> b=2
>>> f(1)
1
2

再看另外一個(gè)例子

>>> b = 1
>>> def func(a):
...     print(a)
...     print(b)
...     b = 1
...
>>> func(2)   你可以思考下會(huì)輸出什么?為什么?













2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in func
UnboundLocalError: local variable 'b' referenced before assignment

你可能會(huì)覺得應(yīng)該打印b的值6,因?yàn)橥饷娑x了一個(gè)全局變量b,而在print(b)之后的b=9是后面執(zhí)行的, 不會(huì)打印9才是。

事實(shí)是,Python 編譯函數(shù)的定義體時(shí),它判斷 b 是局部變量,因?yàn)樵诤瘮?shù)中給它賦值了

也就是說在函數(shù)中加了一句b = 1,下面的就是b就從global變成了local variable

而且在函數(shù)外定義了全局變量b=1,這個(gè)函數(shù)是用不了的

從生成的字節(jié)碼看下

>>> from dis import dis
>>> dis(func)
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

  3           8 LOAD_GLOBAL              0 (print)
             10 LOAD_FAST                1 (b)  # 這里
             12 CALL_FUNCTION            1
             14 POP_TOP

  4          16 LOAD_CONST               1 (1)
             18 STORE_FAST               1 (b)
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE

10 LOAD_FAST 1 (b)這一行暴露了b是個(gè)local variable

這不是缺陷,而是設(shè)計(jì)選擇:Python 不要求聲明變量,但是假定在函數(shù)定義體中賦值的變量是局部變量

這比 JavaScript 的行為好多了,JavaScript 也不要求聲明變量,但是如果忘記把變量聲明為局部變量(使用 var),可能會(huì)在不知情的情況下獲取全局變量

b = 6
def fun(a):
    global b
    print(a)
    print(b)
    b=9

fun(3)
print(b)

這個(gè)global必須要在fun中定義

此時(shí)的字節(jié)碼

 12           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

 13           8 LOAD_GLOBAL              0 (print)
             10 LOAD_GLOBAL              1 (b)
             12 CALL_FUNCTION            1
             14 POP_TOP

 14          16 LOAD_CONST               1 (9)
             18 STORE_GLOBAL             1 (b)
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE

7.5 閉包

人們有時(shí)會(huì)把閉包和匿名函數(shù)弄混。這是有歷史原因的:在函數(shù)內(nèi)部定義函數(shù)不常見,直到開始使用匿名函數(shù)才會(huì)這樣做。而且,只有涉及嵌套函數(shù)時(shí)才有閉包問題。因此,很多人是同時(shí)知道這兩個(gè)概念的

閉包指延伸了作用域的函數(shù),其中包含函數(shù)定義體中引用、但是不在定義體中定義的非全局變量。

函數(shù)是不是匿名的沒有關(guān)系,關(guān)鍵是它能訪問定義體之外定義的非全局變量

書中的一個(gè)例子,要實(shí)現(xiàn)類似下面的效果

它的作用是計(jì)算不斷增加的系列值的均值;例如,整個(gè)歷史中某個(gè)商品的平均收盤價(jià)。每天都會(huì)增加新價(jià)格,因此平均值要考慮至目前為止所有的價(jià)格

>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

乍一看這個(gè)題,你肯定會(huì)想到這是個(gè)函數(shù),這個(gè)函數(shù)傳入1個(gè)參數(shù),內(nèi)部有個(gè)東西可以記錄它的值,并計(jì)算出迄今為止所有數(shù)據(jù)的平均值

難道是這樣的?V1

def avg(value):
    values = []
    values.append(value)
    return sum(values)/len(values)

print(avg(10))
print(avg(11))

顯然不對(duì),每次調(diào)用的時(shí)候values會(huì)被重新初始化成[],所以始終就一個(gè)值

難道是這樣的?V2

values = []
def avg(value):
    values.append(value)
    return sum(values)/len(values)

print(avg(10))
print(avg(11))
print(avg(12))

竟然對(duì)了,但是這values不能在外面啊,你拿到外面去算啥嗎~

上面是我寫的,來看作者寫的

class Averager():
	def __init__(self):
		self.series = []
	def __call__(self, new_value):
		self.series.append(new_value)
		total = sum(self.series)
		return total/len(self.series)
avg = Averager()
print(avg(10))
print(avg(11))
print(avg(12))
    

看到avg()你指應(yīng)該想到它是一個(gè)可調(diào)用對(duì)象,類實(shí)例也可以進(jìn)行調(diào)用,實(shí)現(xiàn)__call__就行啦

那在類這里你要實(shí)現(xiàn)這個(gè)代碼就簡(jiǎn)單了,上面的代碼應(yīng)該可以想通,跟我們之前的蹩腳代碼異曲同工。

來看看函數(shù)式的實(shí)現(xiàn):示例 7-9 average.py:計(jì)算移動(dòng)平均值的高階函數(shù)

def make_averager():
	series = []
	def averager(new_value):
		series.append(new_value)
		total = sum(series)
		return total/len(series)
	return averager
avg = make_averager() # 你得到的是 averager 函數(shù)名這個(gè)一等對(duì)象
print(avg(10)) # averager(10) 就是平均值
print(avg(11)) 
print(avg(12))

書中給出的類和函數(shù)的實(shí)現(xiàn)有共通之處:調(diào)用 Averager() 或 make_averager() 得到一個(gè)可調(diào)用對(duì)象avg,它會(huì)更新歷史值,然后計(jì)算當(dāng)前均值

這個(gè)函數(shù)為何能進(jìn)行累加呢?當(dāng)然你能看得到這個(gè)寫法的特殊之處,函數(shù)里面有局部變量(series),又有內(nèi)部函數(shù)averager。但注意這個(gè)內(nèi)部函數(shù)用到了上面的局部變量

Averager 類的實(shí)例 avg 在哪里存儲(chǔ)歷史值很明顯:self.series 實(shí)例屬性。

但是第二個(gè)示例中的 avg 函數(shù)在哪里尋找 series 呢?

而且調(diào)用 avg(10) 時(shí),make_averager 函數(shù)已經(jīng)返回了,而它的本地作用域也一去不復(fù)返了。來看原文給的圖,我稍微擬合了下。

def make_averager():
	series = []
	def averager(new_value):
		series.append(new_value)
		total = sum(series)
		return total/len(series)
	return averager
avg = make_averager()
avg(10)
avg(11)
avg(12)
# 審查返回的 averager 對(duì)象,我們發(fā)現(xiàn) Python 在 __code__ 屬性(表示編譯后的函數(shù)定義體)中保存局部變量和*變量的名稱
	# 局部變量
print(avg.__code__.co_varnames)
    # *變量
print(avg.__code__.co_freevars)
# avg.__closure__ 中的各個(gè)元素對(duì)應(yīng)于 avg.__code__.co_freevars 中的一個(gè)名稱。這些元素是 cell 對(duì)象,有個(gè) cell_contents 屬性,保存著真正的值
print(avg.__closure__)
print(avg.__closure__[0].cell_contents)

輸出

('new_value', 'total') # 局部變量
('series',) #*變量
(<cell at 0x00000197FA6B4FD0: list object at 0x00000197FA083240>,)#包含該函數(shù)可用變量的綁定的單元的元組
[10, 11, 12] # 單元的值

這里再說明下這幾個(gè)屬性的作用

屬性 作用
co_varnames 參數(shù)名和局部變量的元組
co_freevars *變量的名字組成的元組(通過函數(shù)閉包引用)
__closesure__ None 或包含該函數(shù)可用變量的綁定的單元的元組。有關(guān) cell_contents 屬性的詳情見下。
cell_contents 單元對(duì)象具有 cell_contents 屬性。這可被用來獲取以及設(shè)置單元的值

引用自

https://docs.python.org/zh-cn/3.9/reference/datamodel.html?highlight=closure#the-standard-type-hierarchy

https://docs.python.org/zh-cn/3.9/library/inspect.html?highlight=inspect#module-inspect

avg.__closure__ 中的各個(gè)元素對(duì)應(yīng)于 avg.__code__.co_freevars 中的一個(gè)名稱。這些元素是 cell 對(duì)象,有個(gè) cell_contents 屬性,保存著真正的值

劃重點(diǎn): 閉包是一種函數(shù),它會(huì)保留定義函數(shù)時(shí)存在的*變量的綁定,這樣調(diào)用函數(shù)時(shí),雖然定義作用域不可用了,但是仍能使用那些綁定

注意,只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中的外部變量

7.6 nonlocal聲明

def make_averager():
	series = []
	def averager(new_value):
		series.append(new_value)
		total = sum(series)
		return total/len(series)
	return averager

在上面的做法中

實(shí)現(xiàn) make_averager 函數(shù)的方法效率不高

我們把所有值存儲(chǔ)在歷史數(shù)列中,然后在每次調(diào)用 averager 時(shí)使用 sum 求和

更好的實(shí)現(xiàn)方式是,只存儲(chǔ)目前的總值和元素個(gè)數(shù),然后使用這兩個(gè)數(shù)計(jì)算均值

書中也給了個(gè)示例,但是個(gè)陷阱,你還能看出來問題所在?

def make_averager():
	total = 0
	count = 0
	def averager(new_value):
		total += new_value
		count += 1
		return count/length
	return averager
avg = make_averager()
avg(10)

在Pycharm中定義函數(shù)就是紅色的警告,會(huì)提示類似未解析的引用 'count' ,里面三行都紅的。

但執(zhí)行的時(shí)候會(huì)提示

Traceback (most recent call last):
  File "demo_fluent7.py", line 10, in <module>
    avg(10)
  File "demo_fluent7.py", line 5, in averager
    count += new_value
UnboundLocalError: local variable 'count' referenced before assignment

你上一次遇到它是在這里

>>> b = 1
>>> def func(a):
...     print(a)
...     print(b)
...     b = 1

說明,這個(gè)count又成了一個(gè)局部變量?

看下dis

def make_averager():
    ...#省略
avg = make_averager()
from dis import dis
dis(avg)

輸出

  5           0 LOAD_FAST                1 (count)
              2 LOAD_FAST                0 (new_value)
              4 INPLACE_ADD
              6 STORE_FAST               1 (count)

  6           8 LOAD_FAST                2 (total )
             10 LOAD_CONST               1 (1)
             12 INPLACE_ADD
             14 STORE_FAST               2 (total )

  7          16 LOAD_FAST                1 (count) # 看此處
             18 LOAD_FAST                2 (total )
             20 BINARY_TRUE_DIVIDE
             22 RETURN_VALUE

為何會(huì)這樣呢?其實(shí)之前講過

當(dāng) count 是數(shù)字或任何不可變類型時(shí),count += 1 語句的作用其實(shí)與 count =count + 1 一樣。因此,我們?cè)?averager 的定義體中為 count 賦值了,這會(huì)把 count 變成局部變量。total 變量也受這個(gè)問題影響。

當(dāng)你寫series = []的時(shí)候,我們利用了列表是可變的對(duì)象這一事實(shí),你在內(nèi)部函數(shù)體中只是做了series.append,這個(gè)對(duì)象并沒有改變

但是對(duì)數(shù)字、字符串、元組等不可變類型來說,只能讀取,不能更新。如果嘗試重新綁定,例如 count = count + 1,其實(shí)會(huì)隱式創(chuàng)建局部變量 count。這樣,count 就不是*變量了,因此不會(huì)保存在閉包中

這個(gè)細(xì)節(jié)在本書《第2章 數(shù)據(jù)結(jié)構(gòu)》的2.6 序列的增量賦值有描述,就是對(duì)數(shù)字而言,做count+=1的時(shí)候count不再是原來的count了

那是不是這樣的思路就不行了呢?倒也不是,就是稍微有點(diǎn)牽強(qiáng)

為了解決這個(gè)問題,Python 3 引入了 nonlocal 聲明。它的作用是把變量標(biāo)記為*變量,即使在函數(shù)中為變量賦予新值了,也會(huì)變成*變量。如果為 nonlocal 聲明的變量賦予新值,閉包中保存的綁定會(huì)更新

最終可以替代前面的例子的代碼如下

def make_averager():
	total  = 0
	count = 0
	def averager(new_value):
		nonlocal count,total
		count += 1
		total  += new_value
		return total/count
	return averager
avg = make_averager()
print(avg(10))
print(avg(11))
print(avg(12))

在沒有實(shí)現(xiàn)nonlocal的情況下(比如Python2中)

http://www.python.org/dev/peps/pep-3104/

PEP 3104—Access to Names inOuter Scopes

其中的第三個(gè)代碼片段給出了一種方法?;旧?,這種處理方式是把內(nèi)部函數(shù)需要修改的變量(如 count 和 total)存儲(chǔ)為可變對(duì)象(如字典或簡(jiǎn)單的實(shí)例)的元素或?qū)傩?,并且把那個(gè)對(duì)象綁定給一個(gè)*變量

7.7 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的裝飾器

示例 7-15 一個(gè)簡(jiǎn)單的裝飾器,輸出函數(shù)的運(yùn)行時(shí)間

import time
def clock(func):
	def clocked(*args): # ?
		t0 = time.perf_counter()
		result = func(*args) # ?
		elapsed = time.perf_counter() - t0
		name = func.__name__
		arg_str = ', '.join(repr(arg) for arg in args)
		print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
		return result
	return clocked # ?
@clock
def get_time():
	from time import sleep
	sleep(2)

get_time()

? 定義內(nèi)部函數(shù) clocked,它接受任意個(gè)定位注:位置參數(shù)。
? 這行代碼可用,是因?yàn)?clocked 的閉包中包含*變量 func。
? 返回內(nèi)部函數(shù),取代被裝飾的函數(shù)

關(guān)于第2點(diǎn),用代碼說明下

test = clock(get_time)
print(test.__code__.co_freevars) # ('func',)

示例 7-16 使用 clock 裝飾器

需要用到上面的代碼

import time

@clock
def snooze(seconds):
	time.sleep(seconds)
@clock
def factorial(n):
	return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
	print('*' * 40, 'Calling snooze(.123)')
	snooze(.123)
	print('*' * 40, 'Calling factorial(6)')
	print('6! =', factorial(6))
    
    print(factorial.__name__)

執(zhí)行效果

**************************************** Calling snooze(.123)
[0.12786180s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000050s] factorial(1) -> 1
[0.00000770s] factorial(2) -> 2
[0.00001190s] factorial(3) -> 6
[0.00001650s] factorial(4) -> 24
[0.00002100s] factorial(5) -> 120
[0.00002730s] factorial(6) -> 720
6! = 720

clocked

工作原理

@clock
def factorial(n):
	return 1 if n < 2 else n*factorial(n-1)

等價(jià)于

def factorial(n):
	return 1 if n < 2 else n*factorial(n-1)
factorial = clock(factorial)

factorial成為了clock的實(shí)參,指向func形參;調(diào)用后clock(factorial)返回的是clocked

看上面我加的調(diào)試代碼print(factorial.__name__)得到的就是clocked

現(xiàn)在 factorial 保存的是 clocked 函數(shù)的引用。自此之后,每次調(diào)用 factorial(n),執(zhí)行的都是 clocked(n)。

代碼上clocked做了以下事情:

(1) 記錄初始時(shí)間 t0。                    # t0 = time.perf_counter()
(2) 調(diào)用原來的 factorial 函數(shù),保存結(jié)果。 # result = func(*args) # ?
(3) 計(jì)算經(jīng)過的時(shí)間。                     # elapsed = time.perf_counter() - t0
(4) 格式化收集的數(shù)據(jù),然后打印出來。  
		# 收集的數(shù)據(jù):包括前面的 elapsed 和 result
		# name = func.__name__
		# arg_str = ', '.join(repr(arg) for arg in args)
         # print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
(5) 返回第 2 步保存的結(jié)果。              # return result

裝飾器的典型行為:

? 1. 把被裝飾的函數(shù)替換成新函數(shù)

? 2. 二者接受相同的參數(shù)

? 3. 而且(通常)返回被裝飾的函數(shù)本該返回的值

? 4. 同時(shí)還會(huì)做些額外操作

Gamma 等人寫的《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書是這樣概述“裝飾器”模式的:“動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)?!焙瘮?shù)裝飾器符合這一說法。

但在實(shí)現(xiàn)層面,Python 裝飾器與《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》中所述的“裝飾器”沒有多少相似之處

示例 7-15 中實(shí)現(xiàn)的 clock 裝飾器有幾個(gè)缺點(diǎn):不支持關(guān)鍵字參數(shù),而且遮蓋了被裝飾函數(shù)的__name__ __doc__ 屬性

import time
def clock(func):
	'''doc of clock'''
	def clocked(*args): # ?
		'''doc of clocked'''
		t0 = time.perf_counter()
		result = func(*args) # ?
		elapsed = time.perf_counter() - t0
		name = func.__name__
		arg_str = ', '.join(repr(arg) for arg in args)
		print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
		return result
	return clocked # ?


@clock
def snooze(seconds):
	time.sleep(seconds)
@clock
def factorial(n):
	'''doc of fact'''
	return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
	print(factorial.__doc__)
	print(factorial.__name__)
	print('*' * 40, 'Calling snooze(.123)')
	snooze(.123)
	snooze(seconds=.123)

注意上面的代碼,做了一些更改

  1. 加了factorial、clock和clocked等函數(shù)的doc,你可以看到,print(factorial.__doc__)輸出的是clocked的doc
  2. 測(cè)試了下關(guān)鍵字輸入方式snooze(seconds=.123)提示如下
TypeError: clocked() got an unexpected keyword argument 'seconds'

如果要支持關(guān)鍵字只需做如下更改

def clock(func):
	'''doc of clock'''
	def clocked(*args,**kwargs): # ?
		'''doc of clocked'''
		t0 = time.perf_counter()
		result = func(*args,**kwargs) # ?

? clocked本身要支持**kwargs
? 內(nèi)部調(diào)用的時(shí)候要接受**kwargs

輸出大致如下

doc of clocked
clocked
**************************************** Calling snooze(.123)
[0.13518720s] snooze(0.123) -> None
[0.12407520s] snooze() -> None

問題1: 你可以看到,factorial的__doc____name__被遮擋了,這點(diǎn)在前面的萬能公式中我們也有提到

怎么處理呢?

使用 functools.wraps 裝飾器把相關(guān)的屬性從 func復(fù)制到 clocked 中

import time
import functools
def clock(func):
    @functools.wraps(func)
    def clocked(*args,**kwargs): # ?
        t0 = time.perf_counter()
        result = func(*args,**kwargs) # ?
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result
    return clocked # ?


@clock
def snooze(seconds):
    time.sleep(seconds)
@clock
def factorial(n):
    '''doc of fact'''
    return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
    print(factorial.__doc__)  # doc of fact
    print(factorial.__name__) # factorial

可以看到__doc____name__改過來了


問題2:snooze(seconds=.123)這種調(diào)用方式在結(jié)果中沒有輸出參數(shù)

原因很簡(jiǎn)單,你沒有處理,你處理的只是args,你還要處理kwargs,參考代碼如下

import time
import functools
def clock(func):
    @functools.wraps(func)
    def clocked(*args,**kwargs): # ?
        t0 = time.perf_counter()
        result = func(*args,**kwargs) # ?
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ''.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result
    return clocked # ?


@clock
def snooze(seconds):
    time.sleep(seconds)
@clock
def factorial(n):
    '''doc of fact'''
    return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
    print(factorial.__doc__)
    print(factorial.__name__)
    print('*' * 40, 'Calling snooze(.123)')
    snooze(.123)
    snooze(seconds=.123)

7.8 標(biāo)準(zhǔn)庫中的裝飾器

Python 內(nèi)置了三個(gè)用于裝飾方法的函數(shù):property、classmethod 和 staticmethod

另一個(gè)常見的裝飾器是 functools.wraps,它的作用是協(xié)助構(gòu)建行為良好的裝飾器

標(biāo) 準(zhǔn) 庫 中 最 值 得 關(guān) 注 的 兩 個(gè) 裝 飾 器 是 lru_cache 和 全 新 的singledispatch(Python 3.4 新增)

7.8.1 使用functools.lru_cache做備忘

functools.lru_cache 是非常實(shí)用的裝飾器,它實(shí)現(xiàn)了備忘(memoization)功能。這是一項(xiàng)優(yōu)化技術(shù),它把耗時(shí)的函數(shù)的結(jié)果保存起來,避免傳入相同的參數(shù)時(shí)重復(fù)計(jì)算。LRU 三個(gè)字母是“Least Recently Used”的縮寫,表明緩存不會(huì)無限制增長,一段時(shí)間不用的緩存條目會(huì)被扔掉

示例 7-18 生成第 n 個(gè)斐波納契數(shù),遞歸方式非常耗時(shí)

@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
if __name__=='__main__':
    print(fibonacci(6))

這對(duì)你理解遞歸也是有幫助的

輸出如下(這個(gè)調(diào)用順序?qū)嵲谟悬c(diǎn)...)

[0.00000030s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00002610s] fibonacci(2) -> 1
[0.00000020s] fibonacci(1) -> 1
[0.00003430s] fibonacci(3) -> 2
[0.00000020s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000760s] fibonacci(2) -> 1
[0.00004960s] fibonacci(4) -> 3
[0.00000010s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000750s] fibonacci(2) -> 1
[0.00000020s] fibonacci(1) -> 1
[0.00001490s] fibonacci(3) -> 2
[0.00007280s] fibonacci(5) -> 5
[0.00000020s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000750s] fibonacci(2) -> 1
[0.00000010s] fibonacci(1) -> 1
[0.00001470s] fibonacci(3) -> 2
[0.00000020s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000750s] fibonacci(2) -> 1
[0.00002930s] fibonacci(4) -> 3
[0.00010970s] fibonacci(6) -> 8
8

畫個(gè)圖

從圖上可以看到,這里存在大量的重復(fù)操作

增加兩行代碼,使用 lru_cache,性能會(huì)顯著改善

@functools.lru_cache()
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
if __name__=='__main__':
    print(fibonacci(6))

注意@functools.lru_cache()必須放@clock前面

這時(shí)候的輸出就是這樣,重復(fù)的調(diào)用都沒了

[0.00000040s] fibonacci(1) -> 1
[0.00000030s] fibonacci(0) -> 0
[0.00002740s] fibonacci(2) -> 1
[0.00003230s] fibonacci(3) -> 2
[0.00003680s] fibonacci(4) -> 3
[0.00004120s] fibonacci(5) -> 5
[0.00004570s] fibonacci(6) -> 8
8

另外一個(gè)注意的點(diǎn)是:必須像常規(guī)函數(shù)那樣調(diào)用 lru_cache,后面有個(gè)()

作者做了個(gè)測(cè)試,可以看出,提升是巨大的

示例 7-19 中的版本(加了lru_cache的)在 0.0005 秒內(nèi)調(diào)用了 31 次fibonacci 函數(shù)

示例 7-18 中未緩存版本調(diào)用 fibonacci 函數(shù) 2 692 537 次,在使用Intel Core i7 處理器的筆記本電腦中耗時(shí) 17.7 秒

lru_cache簽名

functools.lru_cache(maxsize=128, typed=False)
  • maxsize指定存儲(chǔ)多少個(gè)調(diào)用的結(jié)果,緩存滿了之后,舊的結(jié)果會(huì)被扔掉,騰出空間。為了得到最佳性能,maxsize 應(yīng)該設(shè)為 2 的冪
  • typed 參數(shù)如果設(shè)為 True,把不同參數(shù)類型得到的結(jié)果分開保存,即把通常認(rèn)為相等的浮點(diǎn)數(shù)和整數(shù)參數(shù)區(qū)分開
  • lru_cache 使用字典存儲(chǔ)結(jié)果,而且鍵根據(jù)調(diào)用時(shí)傳入的定位參數(shù)和關(guān)鍵字參數(shù)創(chuàng)建,所以被 lru_cache 裝飾的函數(shù),它的所有參數(shù)都必須是可散列的,即不可變的

7.8.2 單分派泛函數(shù)

背景: 假設(shè)我們?cè)陂_發(fā)一個(gè)調(diào)試 Web 應(yīng)用的工具,我們想生成 HTML,顯示不同類型的 Python對(duì)象

import html
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

想改造這個(gè)函數(shù),以期達(dá)到下面的效果

  • str:把內(nèi)部的換行符替換為<br>\n';不使用<pre>,而是使用 <p>。
  • int:以十進(jìn)制和十六進(jìn)制顯示數(shù)字。
  • list:輸出一個(gè) HTML 列表,根據(jù)各個(gè)元素的類型進(jìn)行格式化

示例 7-20 生成 HTML 的 htmlize 函數(shù),調(diào)整了幾種對(duì)象的輸出

>>> htmlize({1, 2, 3}) ?
'<pre>{1, 2, 3}</pre>'
>>> htmlize(abs)
'<pre>&lt;built-in function abs&gt;</pre>'
>>> htmlize('Heimlich & Co.\n- a game') ?
'<p>Heimlich &amp; Co.<br>\n- a game</p>'
>>> htmlize(42) ?
'<pre>42 (0x2a)</pre>'
>>> print(htmlize(['alpha', 66, {3, 2, 1}])) ?
<ul>
<li><p>alpha</p></li>
<li><pre>66 (0x42)</pre></li>
<li><pre>{1, 2, 3}</pre></li>
</ul>

? 默認(rèn)情況下,在 <pre></pre> 中顯示 HTML 轉(zhuǎn)義后的對(duì)象字符串表示形式。

? 為 str 對(duì)象顯示的也是 HTML 轉(zhuǎn)義后的字符串表示形式,不過放在 <p></p> 中,而且使用 <br> 表示換行。
? int 顯示為十進(jìn)制和十六進(jìn)制兩種形式,放在<pre></pre> 中。
? 各個(gè)列表項(xiàng)目根據(jù)各自的類型格式化,整個(gè)列表則渲染成 HTML 列表。

因?yàn)?Python 不支持重載方法或函數(shù),所以我們不能使用不同的簽名定義 htmlize 的變體,也無法使用不同的方式處理不同的數(shù)據(jù)類型。

重載overload,java中可以。Python只有override(重寫)。

不同的簽名是不支持的

def htmlize(obj:int):
    pass
def htmlize(obj:str):
    pass
def htmlize(obj:list):
    pass

但我知道的一種做法是可以用第三方庫,類似于這樣

from multipledispatch import dispatch
@dispatch(int)
def htmlize(obj):
    print('int')
@dispatch(str)
def htmlize(obj):
    print('str')
@dispatch(list)
def htmlize(obj):
    print('list')

htmlize(1) # int
htmlize('1') # str
htmlize([1,]) # list
htmlize((1,)) # 報(bào)錯(cuò) 
# NotImplementedError: Could not find signature for htmlize: <tuple>

書中還說了一句也無法使用不同的方式處理不同的數(shù)據(jù)類型,我沒太理解,不是可以用isinstance來處理嗎?莫非在寫的時(shí)候

沒有這個(gè)玩意

第二版英文原文如下

Because we don’t have Java-style method overloading in Python, we can’t simply cre‐ate variations of htmlize with different signatures for each data type we want to han‐dle differently

在 Python 中,一種常見的做法是把 htmlize變成一個(gè)分派函數(shù),使用一串 if/elif/elif,調(diào)用專門的函數(shù),如 htmlize_str、htmlize_int,等等。這樣不便于模塊的用戶擴(kuò)展,還顯得笨拙:時(shí)間一長,分派函數(shù) htmlize 會(huì)變
得很大,而且它與各個(gè)專門函數(shù)之間的耦合也很緊密

書中給出的示例 7-21 singledispatch 創(chuàng)建一個(gè)自定義的 htmlize.register 裝飾器,把多個(gè)函數(shù)綁在一起組成一個(gè)泛函數(shù)

from functools import singledispatch
from collections import abc
import numbers
import html


@singledispatch  # ?
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)


@htmlize.register(str)  # ?
def _(text):  # ?
    content = html.escape(text).replace('\n', '<br>\n')
    return '<p>{0}</p>'.format(content)


@htmlize.register(numbers.Integral)  # ?
def _(n):
    return '<pre>{0} (0x{0:x})</pre>'.format(n)


@htmlize.register(tuple)  # ?
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    return '<ul>\n<li>' + inner + '</li>\n</ul>'
# 測(cè)試數(shù)據(jù)
print(htmlize({1, 2, 3}))
print(htmlize(abs))
print(htmlize('Heimlich & Co.\n- a game') )
print(htmlize(42) )
print(htmlize(['alpha', 66, {3, 2, 1}]))

? @singledispatch 標(biāo)記處理 object 類型的基函數(shù)。
? 各個(gè)專門函數(shù)使用 @?base_function?.register(?type?) 裝飾。
? 專門函數(shù)的名稱無關(guān)緊要;_ 是個(gè)不錯(cuò)的選擇,簡(jiǎn)單明了。
? 為每個(gè)需要特殊處理的類型注冊(cè)一個(gè)函數(shù)。numbers.Integral 是 int 的虛擬超類。
? 可以疊放多個(gè) register 裝飾器,讓同一個(gè)函數(shù)支持不同類型。

只要可能,注冊(cè)的專門函數(shù)應(yīng)該處理抽象基類(如 numbers.Integral 和 abc.MutableSequence),不要處理具體實(shí)現(xiàn)(如 int 和 list)。這樣,代碼支持的兼容類型更廣泛。例如,Python擴(kuò)展可以子類化 numbers.Integral,使用固定的位數(shù)實(shí)現(xiàn) int 類型

使用抽象基類檢查類型,可以讓代碼支持這些抽象基類現(xiàn)有和未來的具體子類或虛擬子類

singledispatch 機(jī)制的一個(gè)顯著特征是,你可以在系統(tǒng)的任何地方和任何模塊中注冊(cè)專門函數(shù)。

如果后來在新的模塊中定義了新的類型,可以輕松地添加一個(gè)新的專門函數(shù)來處理那個(gè)類型。

此外,你還可以為不是自己編寫的或者不能修改的類添加自定義函數(shù)。

singledispatch 是經(jīng)過深思熟慮之后才添加到標(biāo)準(zhǔn)庫中的,它提供的特性很多 , 詳見

PEP 443 — Single-dispatch generic functions

https://www.python.org/dev/peps/pep-0443/

@singledispatch 不是為了把 Java 的那種方法重載帶入 Python。在一個(gè)類中為同一個(gè)方法定義多個(gè)重載變體,比在一個(gè)函數(shù)中使用一長串 if/elif/elif/elif 塊要更好。但是這兩種方案都有缺陷,因?yàn)樗鼈冏尨a單元(類
或函數(shù))承擔(dān)的職責(zé)太多。

@singledispath 的優(yōu)點(diǎn)是支持模塊化擴(kuò)展:各個(gè)模塊可以為它支持的各個(gè)類型注冊(cè)一個(gè)專門函數(shù)

7.9 疊放裝飾器

裝飾器是函數(shù),因此可以組合起來使用(即,可以在已經(jīng)被裝飾的函數(shù)上應(yīng)用裝飾器)

前面已經(jīng)多次這樣使用,比如

@functools.lru_cache()
@clock
def fibonacci(n):
    pass

但要注意順序

@d1
@d2
def f():
	print('f')

等價(jià)于f = d1(d2(f)),就近原則,最近@的最先裝飾

7.10 參數(shù)化裝飾器

解析源碼中的裝飾器時(shí),Python 把被裝飾的函數(shù)作為第一個(gè)參數(shù)傳給裝飾器函數(shù)。

那怎么讓裝飾器接受其他參數(shù)呢?

答案是:創(chuàng)建一個(gè)裝飾器工廠函數(shù),把參數(shù)傳給它,返回一個(gè)裝飾器,然后再把它應(yīng)用到要裝飾的函數(shù)上

書中給了個(gè)示例示例 7-22 示例 7-2 中 registration.py 模塊的刪減版

registry = []
def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func
@register
def f1():
    print('running f1()')


print('running main()')
print('registry ->', registry)
f1()

7.10.1 一個(gè)參數(shù)化的注冊(cè)裝飾器

為了便于啟用或禁用 register 執(zhí)行的函數(shù)注冊(cè)功能,我們?yōu)樗峁┮粋€(gè)可選的 active 參數(shù),設(shè)為 False 時(shí),不注冊(cè)被裝飾的函數(shù)

從概念上看,這個(gè)新的 register 函數(shù)不是裝飾器,而是裝飾器工廠函數(shù)。調(diào)用它會(huì)返回真正的裝飾器,這才是應(yīng)用到目標(biāo)函數(shù)上的裝飾器。

示例 7-23 為了接受參數(shù),新的 register 裝飾器必須作為函數(shù)調(diào)用

registry = set() #?

def register(active=True): #?
    def decorate(func): #?
        print('running register(active=%s)->decorate(%s)'% (active, func))
        if active: #?
            registry.add(func)
        else:
            registry.discard(func) #?
        return func #?
    return decorate #?
@register(active=False) #?
def f1():
    print('running f1()')
@register() #?
def f2():
    print('running f2()')
def f3():
    print('running f3()')

? registry 現(xiàn)在是一個(gè) set 對(duì)象,這樣添加和刪除函數(shù)的速度更快。
? register 接受一個(gè)可選的關(guān)鍵字參數(shù)。
? decorate 這個(gè)內(nèi)部函數(shù)是真正的裝飾器;注意,它的參數(shù)是一個(gè)函數(shù)。
? 只有 active 參數(shù)的值(從閉包中獲取)是 True 時(shí)才注冊(cè) func。
? 如果 active 不為真,而且 func 在 registry 中,那么把它刪除。
? decorate 是裝飾器,必須返回一個(gè)函數(shù)。
? register 是裝飾器工廠函數(shù),因此返回 decorate。然后把它應(yīng)用到被裝飾的函數(shù)上
? @register 工廠函數(shù)必須作為函數(shù)調(diào)用,并且傳入所需的參數(shù)。
? 即使不傳入?yún)?shù),register 也必須作為函數(shù)調(diào)用(@register()),即要返回真正的裝飾器 decorate

在終端下你可以測(cè)試出以下結(jié)果,假設(shè)文件是demo.py

>>> import demo                                                     
running register(active=False)->decorate(<function f1 at 0x000002860CF2CEE0>)
running register(active=True)->decorate(<function f2 at 0x000002860CF2CF70>)
>>> demo.registry
{<function f2 at 0x000002860CF2CF70>}

跟之前7.2說的一樣導(dǎo)入的時(shí)候就會(huì)執(zhí)行

只有 f2 函數(shù)在 registry 中;f1 不在其中,因?yàn)閭鹘o register 裝飾器工廠函數(shù)的參數(shù)是 active=False,所以應(yīng)用到 f1 上的 decorate 沒有把它添加到 registry 中

如果不使用 @ 句法,那就要像常規(guī)函數(shù)那樣使用 register;若想把 f 添加到 registry中,則裝飾 f 函數(shù)的句法是 register()(f);不想添加(或把它刪除)的話,句法是register(active=False)(f)

上面這部分是關(guān)鍵

@clock
def fibonacci(n):
    pass

你知道,fibonacci=clock(fibonacci)

那你現(xiàn)在要做的是

@clock(param='xxx')
def fibonacci(n):
    pass

那自然fibonacci=clock(param='xxx')(fibonacci)

所以你應(yīng)該定義一個(gè)

def clock(param='xxx'):
    pass

而這個(gè)clock的返回需要是一個(gè)函數(shù),參數(shù)應(yīng)該是一個(gè)函數(shù)(比如fibonacci)

def clock(param='xxx'):
    def decorate(func):
        pass
    decorate

書中還給你做了下如下測(cè)試

>>> from registration_param import * # 我上面的測(cè)試改了此處的名字 demo
running register(active=False)->decorate(<function f1 at 0x10073c1e0>)
running register(active=True)->decorate(<function f2 at 0x10073c268>)
>>> registry # ?
{<function f2 at 0x10073c268>}
>>> register()(f3) # ?
running register(active=True)->decorate(<function f3 at 0x10073c158>)
<function f3 at 0x10073c158>
>>> registry # ?
{<function f3 at 0x10073c158>, <function f2 at 0x10073c268>}
>>> register(active=False)(f2) # ?
running register(active=False)->decorate(<function f2 at 0x10073c268>)
<function f2 at 0x10073c268>
>>> registry # ?
{<function f3 at 0x10073c158>}

? 導(dǎo)入這個(gè)模塊時(shí),f2 在 registry 中。
? register() 表達(dá)式返回 decorate,然后把它應(yīng)用到 f3 上。
? 前一行把 f3 添加到 registry 中。
? 這次調(diào)用從 registry 中刪除 f2。
? 確認(rèn) registry 中只有 f3。

7.10.2 參數(shù)化clock裝飾器

上面的裝飾器比較簡(jiǎn)單,但通常參數(shù)化裝飾器的原理相當(dāng)復(fù)雜,

參數(shù)化裝飾器通常會(huì)把被裝飾的函數(shù)替換掉,而且結(jié)構(gòu)上需要多一層嵌套。

import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'


def clock(fmt=DEFAULT_FMT):  # ?
    def decorate(func):  # ?
        def clocked(*_args):  # ?
            t0 = time.time()
            _result = func(*_args)  # ?
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)  # ?

            result = repr(_result)  # ?
            print(fmt.format(**locals()))  # ?
            return _result  # ?

        return clocked  # ?

    return decorate  # ?

? clock 是參數(shù)化裝飾器工廠函數(shù)。
? decorate 是真正的裝飾器。
? clocked 包裝被裝飾的函數(shù)。
? _result 是被裝飾的函數(shù)返回的真正結(jié)果。
? _args 是 clocked 的參數(shù),args 是用于顯示的字符串。
? result 是 _result 的字符串表示形式,用于顯示。
? 這里使用**locals()是為了在 fmt 中引用 clocked 的局部變量。
? clocked 會(huì)取代被裝飾的函數(shù),因此它應(yīng)該返回被裝飾的函數(shù)返回的值。
? decorate 返回 clocked。
? clock 返回 decorate。在這個(gè)模塊中測(cè)試,不傳入?yún)?shù)調(diào)用 clock(),因此應(yīng)用的裝飾器使用默認(rèn)的格式 str。應(yīng)該是DEFAULT_FMT

**locals()** 函數(shù)會(huì)以字典類型返回當(dāng)前位置的全部局部變量,配合fmt來用,還是挺巧妙的~

locals: {'_args': (0.123,), 't0': 1699234406.3928096, '_result': None, 'elapsed': 0.12681794166564941, 'name': 'snooze', 'args': '0.123', 'result': 'None', 'fmt': '[{elapsed:0.8f}s] {name}({args}) -> {result}', 'func': <function snooze at 0x0000026ED4107F70>}
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' # 在上面也有

另外一點(diǎn)就是

參數(shù)化裝飾器通常會(huì)把被裝飾的函數(shù)替換掉,而且結(jié)構(gòu)上需要多一層嵌套。

考慮上面的結(jié)構(gòu)

def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            ...
            return _result
        return clocked
    return decorate
@clock()
def snooze(seconds):
	pass

結(jié)合萬能公式

snooze=clock()(snooze) #注意此處的第一個(gè)()

snooze=decorate(snooze) # 轉(zhuǎn)換下

snooze=clocked # 替換了

最終

    for i in range(3):
        snooze(.123)

就相當(dāng)于

    for i in range(3):
        clocked(.123)

所以下面的幾個(gè)測(cè)試結(jié)果

測(cè)試1

if __name__ == '__main__':
    @clock()
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)

輸出

[0.13555145s] snooze(0.123) -> None
[0.12589598s] snooze(0.123) -> None
[0.12798786s] snooze(0.123) -> None

測(cè)試2

if __name__ == '__main__':
    @clock('{name}: {elapsed}s')
    def snooze(seconds):
        time.sleep(seconds)

    for i in range(3):
        snooze(.123)

輸出

snooze: 0.12915396690368652s
snooze: 0.1259920597076416s
snooze: 0.1258389949798584s

測(cè)試3

if __name__ == '__main__':
    @clock('{name}({args}) dt={elapsed:0.3f}s')
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)

輸出

snooze(0.123) dt=0.126s
snooze(0.123) dt=0.126s
snooze(0.123) dt=0.126s

Graham Dumpleton 和 Lennart Regebro(本書的技術(shù)審校之一)認(rèn)為,裝飾器最好通過實(shí)現(xiàn) __call__ 方法的類實(shí)現(xiàn),不應(yīng)該像本章的示例那樣通過函數(shù)實(shí)現(xiàn)

import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

class Clock:
    def __init__(self,fmt=DEFAULT_FMT):
        self.fmt = fmt

    def __call__(self, func):
        def clocked(*_args):
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)

            result = repr(_result)
            #print('locals:',locals())
            print(self.fmt.format(**locals()))
            return _result

        return clocked


if __name__ == '__main__':
    @Clock()
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)
    @Clock('{name}: {elapsed}s')
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)
    @Clock('{name}({args}) dt={elapsed:0.3f}s')
    def snooze(seconds):
        time.sleep(seconds)
    for i in range(3):
        snooze(.123)

同樣的推導(dǎo)

snooze=Clock()(snooze)

其中Clock()是個(gè)實(shí)例,假設(shè)為clock_instance

那clock_instance(snnoze)就是在調(diào)用__call__,返回的就是clocked,也發(fā)生了替換

從寫法上更讓清晰一些

7.11 本章小結(jié)

從本章開始進(jìn)入元編程領(lǐng)域

開始,我們先編寫了一個(gè)沒有內(nèi)部函數(shù)的 @register 裝飾器;最后,我們實(shí)現(xiàn)了有兩層嵌套函數(shù)的參數(shù)化裝飾器 @clock()

參數(shù)化裝飾器基本上都涉及至少兩層嵌套函數(shù),如果想使用 @functools.wraps 生成裝飾器,為高級(jí)技術(shù)提供更好的支持,嵌套層級(jí)可能還會(huì)更深,比如前面簡(jiǎn)要介紹過的疊放裝飾器

討論了標(biāo)準(zhǔn)庫中 functools 模塊提供的兩個(gè)出色的函數(shù)裝飾器:@lru_cache() 和@singledispatch

若想真正理解裝飾器,需要區(qū)分導(dǎo)入時(shí)和運(yùn)行時(shí),還要知道變量作用域、閉包和新增的nonlocal 聲明。掌握閉包和 nonlocal 不僅對(duì)構(gòu)建裝飾器有幫助,還能協(xié)助你在構(gòu)建 GUI程序時(shí)面向事件編程,或者使用回調(diào)處理異步 I/O

7.12 延伸閱讀

素材 URL 相關(guān)信息
Python Cookbook(第 3 版)中文版》第 9 章“元編程” 有幾個(gè)訣竅構(gòu)建了基本的裝飾器和特別復(fù)雜的裝飾器
9.6 定義一個(gè)能接收可選參數(shù)的裝飾器”一節(jié)中的裝飾器可以作為常規(guī)的裝飾器調(diào)用,也可以作為裝飾器工廠函數(shù)調(diào)用,例如 @clock 或 @clock()
Graham Dumpleton 博 客 文 章 https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/README.md 深入剖析了如何實(shí)現(xiàn)行為良好的裝飾器
How You Implemented Your Python Decorator is Wrong https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/01-how-you-implemented-your-python-decorator-is-wrong.md
wrapt 模塊 http://wrapt.readthedocs.org/en/latest 這個(gè)模塊的作用是簡(jiǎn)化裝飾器和動(dòng)態(tài)函數(shù)包裝器的實(shí)現(xiàn),即使多層裝飾也支持內(nèi)省,而且行為正確,既可以應(yīng)用到方法上,也可以作為描述符使用
Michele Simionato的decorator包 https://pypi.python.org/pypi/decorator 簡(jiǎn)化普通程序員使用裝飾器的方式,并且通過各種復(fù)雜的示例推廣裝飾器
Python Decorator Library 維基頁面 https://wiki.python.org/moin/PythonDecoratorLibrary 里面有很多示例
PEP 443 http://www.python.org/dev/peps/pep-0443 對(duì)單分派泛函數(shù)的基本原理和細(xì)節(jié)做了說明
Five-Minute Multimethods in Python http://www.artima.com/weblogs/viewpost.jsp?thread=101605 詳細(xì)說明了如何使用裝飾器實(shí)現(xiàn)泛函數(shù)(也叫多方法),他給出的代碼支持多分派(即根據(jù)多個(gè)定位參數(shù)進(jìn)行分派)
Martijn Faassen 開發(fā)的 Reg http://reg.readthedocs.io/en/latest/ 如果想使用現(xiàn)代 的技術(shù)實(shí)現(xiàn)多分派泛函數(shù),并支持在生產(chǎn)環(huán)境中使用,可以用 它
Fredrik Lundh 寫的一篇短文Closures in Python http://effbot.org/zone/closure.htm 解說了閉包這個(gè)術(shù)語
PEP 3104—Access to Names in Outer Scopes http://www.python.org/dev/peps/pep-3104 說明了引入 nonlocal 聲明的原因:重新綁定既不在本地作用域中也不在全局作用域中的名稱。這份 PEP 還概述了其他動(dòng)態(tài)語言(Perl、Ruby、JavaScript,等等)解決這個(gè)問題的方式,以及 Python 中可用設(shè)計(jì)方案的優(yōu)缺點(diǎn)
PEP 227—Statically Nested Scopes http://www.python.org/dev/peps/pep-0227/ 說明了 Python 2.1 引入的詞法作用域;這份 PEP 還說明了 Python 中閉包的基本原理和實(shí)現(xiàn)方式的選擇

雜談

  • 任何把函數(shù)當(dāng)作一等對(duì)象的語言,它的設(shè)計(jì)者都要面對(duì)一個(gè)問題:作為一等對(duì)象的函數(shù)在某個(gè)作用域中定義,但是可能會(huì)在其他作用域中調(diào)用。問題是,如何計(jì)算*變量?首先出現(xiàn)的最簡(jiǎn)單的處理方式是使用“動(dòng)態(tài)作用域”。也就是說,根據(jù)函數(shù)調(diào)用所在的環(huán)境計(jì)算*變量。

  • 動(dòng)態(tài)作用域易于實(shí)現(xiàn),這可能就是 John McCarthy 創(chuàng)建 Lisp(第一門把函數(shù)視作一等對(duì)象的語言)時(shí)采用這種方式的原因

  • Python 函數(shù)裝飾器符合 Gamma 等人在《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書中對(duì)“裝飾器”模式的一般描述:“動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就擴(kuò)展功能而言,裝飾器模式比子類化更靈活。”

  • 在設(shè)計(jì)模式中,Decorator 和 Component 是抽象類。為了給具體組件添加行為,具體裝飾器的實(shí)例要包裝具體組件的實(shí)例

  • 裝飾器與它所裝飾的組件接口一致,因此它對(duì)使用該組件的客戶透明。它將客戶請(qǐng)求轉(zhuǎn)發(fā)給該組件,并且可能在轉(zhuǎn)發(fā)前后執(zhí)行一些額外的操作(例如繪制一個(gè)邊框)。透明性使得你可以遞歸嵌套多個(gè)裝飾器,從而可以添加任意多的功能

  • 一般來說,實(shí)現(xiàn)“裝飾器”模式時(shí)最好使用類表示裝飾器和要包裝的組件

還有很多,不再一一羅列了啦,雜談部分就當(dāng)看Python歷史了

關(guān)于裝飾器的一個(gè)典型應(yīng)用

引自 劉江的博客

有一個(gè)大公司,下屬的基礎(chǔ)平臺(tái)部負(fù)責(zé)內(nèi)部應(yīng)用程序及API的開發(fā)。另外還有上百個(gè)業(yè)務(wù)部門負(fù)責(zé)不同的業(yè)務(wù),這些業(yè)務(wù)部門各自調(diào)用基礎(chǔ)平臺(tái)部提供的不同函數(shù),也就是API處理自己的業(yè)務(wù),情況如下:

# 基礎(chǔ)平臺(tái)部門開發(fā)了上百個(gè)函數(shù)API
def f1():
    print("業(yè)務(wù)部門1的數(shù)據(jù)接口......")
def f2():
    print("業(yè)務(wù)部門2的數(shù)據(jù)接口......")
def f3():
    print("業(yè)務(wù)部門3的數(shù)據(jù)接口......")
def f100():
    print("業(yè)務(wù)部門100的數(shù)據(jù)接口......")

#各部門分別調(diào)用自己需要的API
f1()
f2()
f3()
f100()

公司還在創(chuàng)業(yè)初期時(shí),基礎(chǔ)平臺(tái)部就開發(fā)了這些函數(shù)。由于各種原因,比如時(shí)間緊,比如人手不足,比如架構(gòu)缺陷,比如考慮不周等等,沒有為函數(shù)的調(diào)用進(jìn)行安全認(rèn)證?,F(xiàn)在,公司發(fā)展壯大了,不能再像初創(chuàng)時(shí)期的“草臺(tái)班子”一樣將就下去了,基礎(chǔ)平臺(tái)部主管決定彌補(bǔ)這個(gè)缺陷,于是(以下場(chǎng)景純屬虛構(gòu),調(diào)侃之言,切勿對(duì)號(hào)入座):

第一天:主管叫來了一個(gè)運(yùn)維工程師,工程師跑上跑下逐個(gè)部門進(jìn)行通知,讓他們?cè)诖a里加上認(rèn)證功能,然后,當(dāng)天他被開除了。

第二天:主管又叫來了一個(gè)運(yùn)維工程師,工程師用shell寫了個(gè)復(fù)雜的腳本,勉強(qiáng)實(shí)現(xiàn)了功能。但他很快就回去接著做運(yùn)維了,不會(huì)開發(fā)的運(yùn)維不是好運(yùn)維....

第三天:主管叫來了一個(gè)python自動(dòng)化開發(fā)工程師。哥們是這么干的,只對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行重構(gòu),讓N個(gè)業(yè)務(wù)部門無需做任何修改。這哥們很快也被開了,連運(yùn)維也沒得做?! ?/p>

def f1():
    #加入認(rèn)證程序代碼
    print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
def f2():
    # 加入認(rèn)證程序代碼
    print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
def f3():
    # 加入認(rèn)證程序代碼
    print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
def f100():
    #加入認(rèn)證程序代碼
    print("業(yè)務(wù)部門100數(shù)據(jù)接口......")

#各部門分別調(diào)用
f1()
f2()
f3()
f100()

第四天:主管又換了個(gè)開發(fā)工程師。他是這么干的:定義個(gè)認(rèn)證函數(shù),在原來其他的函數(shù)中調(diào)用它,代碼如下。

def login():
    print("認(rèn)證成功!")

def f1():
    login()
    print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
def f2():
    login()
    print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
def f3():
    login()
    print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
def f100():
    login()
    print("業(yè)務(wù)部門100數(shù)據(jù)接口......")

#各部門分別調(diào)用
f1()
f2()
f3()
f100()

但是主管依然不滿意,不過這一次他解釋了為什么。主管說:寫代碼要遵循開放封閉原則,簡(jiǎn)單來說,已經(jīng)實(shí)現(xiàn)的功能代碼內(nèi)部不允許被修改,但外部可以被擴(kuò)展。如果將開放封閉原則應(yīng)用在上面的需求中,那么就是不允許在函數(shù)f1 、f2、f3......f100的內(nèi)部進(jìn)行代碼修改,但是可以在外部對(duì)它們進(jìn)行擴(kuò)展。

第五天:已經(jīng)沒有時(shí)間讓主管找別人來干這活了,他決定親自上陣,使用裝飾器完成這一任務(wù),并且打算在函數(shù)執(zhí)行后再增加個(gè)日志功能。主管的代碼如下:

def outer(func):
    def inner():
        print("認(rèn)證成功!")
        result = func()
        print("日志添加成功")
        return result
    return inner

@outer
def f1():
    print("業(yè)務(wù)部門1數(shù)據(jù)接口......")

@outer
def f2():
    print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
@outer
def f3():
    print("業(yè)務(wù)部門3數(shù)據(jù)接口......")

@outer
def f100():
    print("業(yè)務(wù)部門100數(shù)據(jù)接口......")

#各部門分別調(diào)用
f1()
f2()
f3()
f100()

使用裝飾器@outer,也是僅需對(duì)基礎(chǔ)平臺(tái)的代碼進(jìn)行拓展,就可以實(shí)現(xiàn)在其他部門調(diào)用函數(shù)API之前都進(jìn)行認(rèn)證操作,在操作結(jié)束后保存日志,并且其他業(yè)務(wù)部門無需對(duì)他們自己的代碼做任何修改,調(diào)用方式也不用變

總結(jié)

以上是生活随笔為你收集整理的《流畅的Python》 读书笔记 第7章_函数装饰器和闭包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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

中文字幕在线观看第一页 | 中文字幕在线观看的网站 | 99色在线播放 | 成人免费一级 | 一级片视频在线 | 麻豆免费看片 | 色婷婷成人 | 欧美 国产 视频 | 国产伦精品一区二区三区免费 | 久久久久亚洲国产 | 亚洲特级片 | 精品久久久久久久久久久久久久久久 | 色婷婷国产精品一区在线观看 | 国产精品久久99综合免费观看尤物 | 在线观看黄色小视频 | 成人免费视频播放 | 成人午夜电影在线 | 成人久久18免费网站 | 久久国产精品久久精品国产演员表 | 国产主播大尺度精品福利免费 | 中国黄色一级大片 | 激情av资源| 337p日本大胆噜噜噜噜 | 九九九热精品免费视频观看网站 | 日韩成人免费电影 | 免费黄色激情视频 | 在线观看免费日韩 | 99精品在线视频播放 | 五月宗合网 | 麻花豆传媒mv在线观看网站 | 91在线免费观看网站 | 99免在线观看免费视频高清 | 在线观看深夜视频 | 婷婷色视频 | 精品国产一区二区三区四区在线观看 | 日韩免费在线一区 | 欧美日韩视频在线 | 日韩素人在线观看 | 欧美激情视频一区二区三区免费 | 国产91精品一区二区麻豆亚洲 | 超碰人人在| 在线观看不卡的av | 国产色女人 | 成人国产在线 | 欧美日韩免费网站 | 久久99精品国产 | 国产精品99久久久精品免费观看 | 国产色视频一区二区三区qq号 | 97av影院 | 国产 一区二区三区 在线 | 四虎影视成人精品 | 狠狠干免费 | 久久精品第一页 | 天天狠狠干 | 亚洲欧美日韩中文在线 | 免费日韩| 国产精品 欧美 日韩 | 国产精品一区二区在线播放 | 久久久久久久久久久网站 | 中国一级片在线 | 免费观看www7722午夜电影 | 97操碰 | 444av| 成人精品国产免费网站 | 国产日韩欧美在线 | www.看片网站| 一区二区三区四区不卡 | 在线 视频 亚洲 | 成人一级在线 | 黄色a级片在线观看 | 三级黄色大片在线观看 | 96精品在线| 亚洲精品女人久久久 | 波多野结衣一区二区三区中文字幕 | 国产成人精品一区一区一区 | 人人看人人做人人澡 | 97色婷婷 | 天天爱天天操天天爽 | 日日操天天射 | 国产精品成人久久久 | 久久99精品久久久久婷婷 | 日韩亚洲国产中文字幕 | 超碰在线观看av.com | 天天综合网 天天综合色 | 亚洲精品视频在线 | 全久久久久久久久久久电影 | av免费在线观 | 青青草在久久免费久久免费 | 国产毛片久久 | 国产午夜亚洲精品 | 天天爱综合 | 亚洲成人av片 | 国产精品午夜在线观看 | 久久激情视频 久久 | 91免费版在线 | 亚洲一片黄 | 国产精品高潮呻吟久久久久 | 国产精品3 | 日韩欧美视频 | 欧美一二三四在线 | 国产a国产 | 亚洲午夜精品一区二区三区电影院 | 国产在线观看二区 | 久久免费成人 | 欧美黑人xxxx猛性大交 | 色婷婷av一区 | 在线观看免费一级片 | 91精品在线免费视频 | 欧美大片www | 国产美女精品人人做人人爽 | 精品国偷自产在线 | 久久婷婷开心 | www.色五月.com| www.国产在线观看 | av黄色一级片 | 日韩免费观看高清 | 六月丁香在线视频 | 国产视频亚洲精品 | 久久综合狠狠综合 | 六月色婷| 黄av资源 | 久久亚洲精品国产亚洲老地址 | 操处女逼 | 黄色小说免费在线观看 | 久久精品人人做人人综合老师 | 午夜三级理论 | 日韩欧美精品在线 | 婷婷综合导航 | 国产不卡在线播放 | 91桃色在线免费观看 | 久草在线在线视频 | 欧美午夜精品久久久久久孕妇 | 狠狠色噜噜狠狠狠合久 | 999国内精品永久免费视频 | 天天天操天天天干 | 午夜黄色影院 | a v在线视频 | 国产黄色片免费观看 | 狠狠色丁香婷婷综合橹88 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 色多多视频在线 | 日韩电影中文,亚洲精品乱码 | 欧美色图30p | 亚洲视频 一区 | 精品国产精品国产偷麻豆 | 99精品国产免费久久久久久下载 | 福利一区二区 | 欧美精品久久久久久久久久久 | 三级av在线 | 成人午夜剧场在线观看 | 欧美日韩色婷婷 | 在线 视频 亚洲 | 欧美精品久久久久久久久久 | 国产又粗又猛又色 | 亚洲一区二区三区91 | 亚洲精品乱码久久久久v最新版 | 色婷婷六月天 | 欧美a级一区二区 | 国内精品二区 | 96亚洲精品久久 | av中文字幕剧情 | 96久久| 午夜在线看片 | 久久99热这里只有精品国产 | 国产精品久久久av | 色综合小说 | 视频在线播放国产 | 国产精品99久久免费黑人 | 97精品超碰一区二区三区 | 高清日韩一区二区 | 亚洲精品ww | 国产999精品久久久久久麻豆 | 美女视频a美女大全免费下载蜜臀 | 久草网视频在线观看 | 天天操天天射天天舔 | 国产精品美女视频网站 | 99婷婷狠狠成为人免费视频 | 亚洲视频在线看 | 六月色丁| www.久久久.com | 国产亚洲欧美在线视频 | 五月综合网 | 久久国产视频网站 | 久久精品毛片基地 | 欧美亚洲三级 | 久久成年人网站 | 91麻豆精品国产91久久久久久久久 | 91av播放| 亚洲爱爱视频 | 日日干日日色 | 成人av在线直播 | 婷婷丁香久久五月婷婷 | 国产精品2019 | 四虎国产永久在线精品 | 美女网站黄免费 | 国产又粗又猛又色又黄网站 | 欧美精品生活片 | 黄色一区三区 | 久久99爱视频 | 欧美最猛性xxx | 久久久蜜桃一区二区 | 天天射天天干天天插 | 五月香视频在线观看 | 久久精品亚洲一区二区三区观看模式 | 免费视频一级片 | 超碰99在线 | 激情六月婷婷久久 | 国产区精品在线 | 国产精品中文字幕在线观看 | 免费视频91蜜桃 | 五月激情六月丁香 | 美女精品久久 | 欧美激情视频在线观看免费 | 久久影视网 | 免费a一级 | 国产黄色精品在线 | 午夜精品999 | 日韩在线免费播放 | 波多野结衣综合网 | www视频在线播放 | 欧美日韩国产精品一区二区三区 | 麻豆91精品 | 欧美黑人性爽 | 色欧美日韩| 久久久亚洲网站 | 天天色天天干天天 | 日韩在线高清 | 在线观看视频国产 | 一区二区三区四区久久 | 黄色在线成人 | 免费在线黄 | 玖玖爱在线观看 | 国产精品国产毛片 | 黄色国产在线 | 亚洲精品国偷拍自产在线观看 | 91丨porny丨九色 | 色偷偷88欧美精品久久久 | 不卡中文字幕av | 亚洲区另类春色综合小说 | 久久9999久久 | 天天射天天爽 | 黄色的网站免费看 | 超碰免费在线公开 | 亚洲激情在线播放 | 亚洲国产剧情 | 亚洲九九九在线观看 | 亚洲成人精品久久 | 91福利影院在线观看 | 色欧美88888久久久久久影院 | 亚洲一区二区三区毛片 | 国产在线专区 | 天天操夜夜曰 | 最新超碰在线 | 国产a级片免费观看 | 天天射天天拍 | 九九精品无码 | 免费黄色网址网站 | 一级黄色av | 国产精品久久嫩一区二区免费 | 亚洲视频一级 | 伊人欧美| 欧美视频在线观看免费网址 | 欧美爽爽爽| 激情综合亚洲精品 | 玖玖玖影院 | av手机版 | 最近2019年日本中文免费字幕 | 国产蜜臀av | 欧洲精品码一区二区三区免费看 | 日本一区二区免费在线观看 | 四虎影视成人永久免费观看视频 | 免费高清在线观看成人 | 国产精品欧美一区二区三区不卡 | 一区二区中文字幕在线 | 国产手机精品视频 | 天天干天天干天天干 | 欧美一区免费观看 | 色瓜 | 中文视频在线播放 | 18岁免费看片 | 久久久在线观看 | 狠狠干2018| 久久精品国产v日韩v亚洲 | av电影中文字幕在线观看 | 色网站在线观看 | 中文字幕一区二区在线观看 | 国产视频久久 | 日韩av中文 | 香蕉视频一级 | 免费久久99精品国产 | 奇米影音四色 | 天天色天天射综合网 | 国产黄色精品在线观看 | 国产免费作爱视频 | 五月婷婷综合在线视频 | 91丨九色丨高潮丰满 | 在线看免费| 日本精品视频在线观看 | 日本深夜福利视频 | 国产原厂视频在线观看 | 精品国模一区二区 | 国产精品久免费的黄网站 | 亚洲激情在线视频 | 黄在线免费看 | 精品在线观看视频 | 国产日产精品久久久久快鸭 | 欧美韩国日本在线观看 | 久久成人18免费网站 | 五月婷婷免费 | www色com | 五月天婷婷狠狠 | 超碰av在线 | 夜夜躁狠狠燥 | 国产一级视频免费看 | 综合天天 | 在线v片免费观看视频 | 久久久久国产精品视频 | 国产精品久久久久久久久免费看 | 97超碰人人澡人人爱 | 五月天狠狠操 | 久久国产精品一二三区 | 日韩欧美在线播放 | 亚洲精品乱码久久久久久高潮 | 免费成人av在线看 | 精品久久久精品 | 人人艹人人 | 四虎国产精品免费 | 五月婷婷久久丁香 | 亚洲人片在线观看 | 亚洲国产福利视频 | 在线精品视频免费播放 | 黄色小网站在线 | 午夜性生活片 | av先锋中文字幕 | 中文字幕一区二区三区在线观看 | 久久人网 | 久久看毛片 | 中文字幕免费看 | 伊人久久一区 | 黄网站www| 午夜精品久久久久久久99 | 欧美黄色高清 | 精一区二区| 欧美日韩国产一区二区在线观看 | 中文字幕日韩国产 | 久久成人在线视频 | 国产理论片在线观看 | 欧美性黑人 | av在线中文 | 五月激情视频 | 久久综合桃花 | 久久99精品久久久久蜜臀 | www.久久91| 狠狠色狠狠色综合日日92 | 精产嫩模国品一二三区 | 美女黄色网在线播放 | 久久精品视 | 国产精品99久久久久久宅男 | 中文字幕久久久精品 | 国产中文字幕在线视频 | 日韩欧美99| 国产在线视频在线观看 | 亚洲欧美在线视频免费 | 四虎免费av | 久草免费新视频 | 日韩理论视频 | 人人揉人人揉人人揉人人揉97 | 亚洲九九九在线观看 | 免费网站黄 | 色综合久久五月天 | 公与妇乱理三级xxx 在线观看视频在线观看 | 五月婷婷操 | 在线观看国产中文字幕 | 免费看的黄色片 | 免费av视屏 | 色wwww| 欧美精品999 | 在线激情影院一区 | 午夜影院一级 | 亚洲一级黄色大片 | 99九九免费视频 | 成年人视频在线 | 欧美日韩一区二区三区在线观看视频 | 久久精精品视频 | 欧美激情精品久久久久久变态 | 成在线播放 | 国产高清视频在线播放 | 日韩电影中文,亚洲精品乱码 | 久久精品久久综合 | 久草视频播放 | 久久精品老司机 | 五月天激情婷婷 | 久久99亚洲精品久久 | 91精品推荐 | 久久久久久久久久久精 | 亚洲国产精品成人精品 | 久久午夜精品 | 超碰在线国产 | 97偷拍在线视频 | 亚洲精品国产高清 | 99视频国产在线 | 在线观看视频免费播放 | 亚洲免费观看在线视频 | a特级毛片 | 久久人91精品久久久久久不卡 | 九九久久久久99精品 | 日本久久免费电影 | 99婷婷狠狠成为人免费视频 | 亚洲午夜电影网 | 天天色播| 国产麻豆成人传媒免费观看 | 日韩网站在线免费观看 | 射射射综合网 | 免费男女羞羞的视频网站中文字幕 | av在线电影网站 | 五月婷婷播播 | 国产精品久久久久久69 | 国色天香第二季 | 黄色一级动作片 | 九九久久影视 | 免费成人黄色 | 色综合久久久久综合体 | 日韩视频1区 | a级免费观看 | 国产精品久久久久久久久久久久久久 | 91成人免费看片 | 91九色最新 | 久久免费视频这里只有精品 | 天天综合网天天综合色 | 99精品视频网 | 999视频在线播放 | 国产高清永久免费 | 亚洲国产电影在线观看 | 亚洲精品电影在线 | 91av蜜桃| 成人午夜电影在线 | 色99网| 九九久久久久久久久激情 | 一区二区视频网站 | 日韩中文在线播放 | 在线观看亚洲电影 | 色婷久久| 中文字幕一区二区三区精华液 | 超碰97人人射妻 | 2023国产精品自产拍在线观看 | 中文字幕在线观看免费高清电影 | 国产精品久久久久久久久久尿 | 成人免费在线电影 | 色婷婷免费视频 | 97超级碰碰碰碰久久久久 | 97在线免费 | 99久久99久久精品国产片果冰 | 成人国产一区二区 | 国产精品午夜av | 中文字幕丰满人伦在线 | 久影院 | 91aaa在线观看 | 色网站视频| 国产综合在线观看视频 | 日韩午夜三级 | 国产免费久久精品 | 国产精彩视频一区 | 国产久视频 | 免费在线电影网址大全 | 国产精品电影在线 | 色是在线视频 | 日韩欧美视频一区二区 | 久久综合九色综合久久久精品综合 | 黄色a级片在线观看 | 国产成人在线网站 | 黄色av电影一级片 | 激情五月六月婷婷 | 日韩视频一区二区 | 国产成人免费观看 | 最近中文字幕免费视频 | 成人福利av | 97香蕉超级碰碰久久免费软件 | 91av小视频 | 日韩资源在线播放 | 国产精品av电影 | 日本xxxxav | 国产专区欧美专区 | 久久综合狠狠狠色97 | 黄色片软件网站 | 丝袜精品视频 | 久久久久久高潮国产精品视 | 国产精品女同一区二区三区久久夜 | 久青草国产在线 | 亚洲视频,欧洲视频 | 久久人人爽人人爽人人 | 在线观看视频日韩 | 最近中文字幕国语免费高清6 | 国产一级在线观看视频 | 成人在线视频免费看 | 成人免费观看大片 | 激情综合五月网 | 久久影院中文字幕 | 国产精品久久99综合免费观看尤物 | 欧美最新大片在线看 | 国产美女视频免费观看的网站 | 国产精品欧美日韩 | 免费看污片 | 亚洲综合激情小说 | 成人午夜精品久久久久久久3d | 2018好看的中文在线观看 | 久草网站| av免费在线播放 | 久久精品综合视频 | 国产亚洲免费观看 | 在线免费黄色 | 亚洲精品合集 | 久久国产热视频 | 婷婷综合亚洲 | 中文字幕在线观看av | 日韩视频免费在线 | 美女网站在线免费观看 | 91视频免费观看 | 国产精品自拍在线 | 亚洲成色777777在线观看影院 | 亚洲国产精品久久久 | 色天天天| 69av视频在线 | 国产精品9999久久久久仙踪林 | 五月综合色 | 欧美综合在线视频 | 欧美久久久久久久久久久久久 | 国产黄网站在线观看 | 欧美久久久久久久久久久久 | 成人午夜网 | 久久蜜臀av | 人人超碰97 | 日韩成人免费在线 | 国产精品18久久久久vr手机版特色 | 国产九九精品视频 | 国产亚洲亚洲 | 婷婷精品在线视频 | 激情开心色 | 欧美精品在线观看一区 | 五月天久久综合网 | 久久精品香蕉 | 视频一区二区在线 | 91亚瑟视频 | 黄色片免费在线 | 天堂va在线观看 | 亚洲成av人片一区二区梦乃 | 超碰国产在线 | 国偷自产视频一区二区久 | 99在线看 | 国产在线1区 | 日本一区二区三区免费看 | 国产美女在线免费观看 | a级国产乱理论片在线观看 伊人宗合网 | 欧美一区日韩精品 | 天堂av网址 | 在线观看国产亚洲 | 日产乱码一二三区别在线 | 色综合天天色综合 | 日韩在线观看中文字幕 | 久色 网 | 九九免费视频 | 成人在线观看你懂的 | 国产婷婷vvvv激情久 | 在线观看 国产 | 水蜜桃亚洲一二三四在线 | 精品久久久久久久久亚洲 | 日韩高清一区 | 西西444www大胆高清视频 | 久久国产精品99久久久久久老狼 | 久草视频一区 | 国产高清在线精品 | 夜夜澡人模人人添人人看 | 国产视频97 | 在线播放 亚洲 | 17婷婷久久www | 国产亚洲精品成人av久久ww | 国产最新在线 | 中文字幕在线看视频 | 国产精品日韩 | 不卡av在线 | 91看片在线免费观看 | 亚洲精品综合在线观看 | 91精品国产91久久久久福利 | 天堂网一区二区三区 | 日韩欧美中文 | 国产精品成人a免费观看 | 国产精品av免费在线观看 | 国产麻豆传媒 | 91九色老| 男女啪啪网站 | 国产永久免费观看 | 亚洲电影久久 | 亚洲美女在线国产 | 九九九热精品免费视频观看 | 日韩国产在线观看 | 香蕉久久久久 | 国产精品久久嫩一区二区免费 | 欧美一二区视频 | 日本久久成人中文字幕电影 | 91热精品 | 九色视频网址 | 久99久在线视频 | 日韩在线视频精品 | 久久黄色免费观看 | 精品久久91| 美女视频黄在线观看 | 97超视频在线观看 | 久久精品视频99 | 日b视频国产 | 日韩一二三 | 国产区欧美| 高清一区二区 | 日韩激情影院 | 日韩国产在线观看 | 天天干天天摸天天操 | 狠狠狠狠狠狠干 | 国产精品美女久久久网av | 日本中文不卡 | 超级碰碰碰视频 | 毛片一级免费一级 | 亚洲精品乱码久久久久久高潮 | 国产又粗又硬又爽的视频 | 国产精品 国内视频 | 在线91视频| 亚洲区色 | 成人观看 | 最近中文字幕视频完整版 | aaa免费毛片 | 婷婷中文字幕 | 五月婷婷在线视频观看 | 黄色视屏免费在线观看 | 深夜免费福利在线 | 天天曰天天干 | 黄色免费视频在线观看 | www操操操| 成人九九视频 | 欧美在线视频第一页 | 久久久久久久久影视 | 午夜久久美女 | 亚洲欧洲xxxx | 日韩激情一二三区 | 亚洲综合色丁香婷婷六月图片 | 日韩中文字幕免费在线观看 | 久久久精品国产免费观看一区二区 | 91麻豆看国产在线紧急地址 | 99精品国产一区二区三区不卡 | 偷拍精偷拍精品欧洲亚洲网站 | 国产精品久久久久久久久久不蜜月 | 蜜桃av人人夜夜澡人人爽 | 欧美一级片免费在线观看 | 久久嗨| 国产区网址 | 久久国产精品免费 | 午夜视频在线网站 | 国内精品久久久久久久久 | 久久经典视频 | 免费合欢视频成人app | 国际精品网 | 中文字幕成人 | 又黄又爽又湿又无遮挡的在线视频 | 亚洲电影网站 | 久草视频2| 免费黄av| 黄网站色视频免费观看 | 午夜av在线播放 | 视频一区在线免费观看 | 婷婷色综合网 | 五月天天色 | 久久久精品国产免费观看一区二区 | 欧美日韩国产成人 | 91人人澡人人爽人人精品 | 亚洲综合视频在线播放 | 精品国产一区二区在线 | 日韩成人精品一区二区三区 | 一区二区精品视频 | 在线观看中文字幕dvd播放 | 欧美污网站| 欧美在线1区 | 中文字幕影视 | www.久久视频 | 色噜噜日韩精品一区二区三区视频 | 97精品欧美91久久久久久 | 日本高清中文字幕有码在线 | www视频在线播放 | 色窝资源 | 精品99视频 | 日批网站免费观看 | 中文资源在线官网 | 日日操网| 国产精品免费在线 | 国产成人三级一区二区在线观看一 | 青草草在线| 91精品在线观看视频 | 亚洲免费一级 | 狠狠插天天干 | 永久免费精品视频网站 | 久草视频国产 | 五月婷婷综合久久 | 黄色a一级视频 | 久久久免费精品国产一区二区 | 久久少妇 | 国产高清日韩 | 久久天天躁夜夜躁狠狠85麻豆 | 免费高清男女打扑克视频 | 97精品国自产拍在线观看 | 国内精品久久久久影院男同志 | 91桃色免费视频 | 黄色国产高清 | www婷婷| 中文字幕网站视频在线 | 亚洲天堂自拍视频 | 在线播放视频一区 | 亚洲免费黄色 | 亚洲精品999 | 91视频啪 | 中文字幕中文字幕 | 国产成人精品电影久久久 | 久久精品国产亚洲a | 国产精品成人自产拍在线观看 | 在线观看视频国产一区 | 国产精品久久伊人 | 91视频大全| 免费观看性生活大片3 | 99在线免费观看 | 亚洲精品网站在线 | 欧美精品久久久久久久 | 2018精品视频| 五月婷婷在线视频观看 | 天天操导航 | a级国产片 | 香蕉视频在线免费 | 成人h视频在线 | 亚洲欧美一区二区三区孕妇写真 | 又黄又爽又无遮挡的视频 | 免费在线色电影 | 蜜臀精品久久久久久蜜臀 | 日韩在线一区二区免费 | 91成人在线网站 | 狠狠操导航 | 成人av一区二区三区 | 丁香五月缴情综合网 | 狠狠狠色丁香婷婷综合久久五月 | 国产在线观看a | 亚洲精品午夜久久久久久久 | 久久午夜羞羞影院 | 成人免费观看电影 | 婷婷综合在线 | 国产精品久久久久久久久久 | 91中文字幕在线播放 | 国产伦理久久精品久久久久_ | 久久精品精品电影网 | 国产免费一区二区三区最新 | 成人黄色电影在线播放 | 日韩av电影网站在线观看 | 久久亚洲福利 | 日日干激情五月 | 国产五月婷婷 | 国产色在线 | 999久久久免费精品国产 | 久久精品成人热国产成 | 欧美视频xxx | 国产精品黑丝在线观看 | 久久香蕉电影网 | 色94色欧美 | 手机在线中文字幕 | 超碰电影在线观看 | 久久久久观看 | 亚洲电影毛片 | 免费一级片在线观看 | 欧美性生活免费 | 日韩在线观看第一页 | 色综合五月 | 美女免费视频网站 | 免费黄色在线网站 | 黄色高清视频在线观看 | 91影视成人 | 国产成人综合在线观看 | 免费看成人a | 久久国产午夜精品理论片最新版本 | 久久久精品二区 | 91精品国产成人观看 | 91丨九色丨国产在线 | 中文字幕免费成人 | 91麻豆文化传媒在线观看 | 日韩欧美区 | 视频在线91 | 天天插天天射 | 免费成人在线视频网站 | 日韩网站在线观看 | 黄在线免费看 | 国产黄色片久久 | www.国产高清 | 永久精品视频 | 在线免费观看国产黄色 | 久久精品小视频 | 黄色国产高清 | 久久久久亚洲国产精品 | 天天天操天天天干 | 久久精彩视频 | 丁香五月亚洲综合在线 | 亚洲高清av | 亚洲色图美腿丝袜 | 久久久久久99精品 | 在线之家免费在线观看电影 | 四虎小视频 | 视频精品一区二区三区 | 日本少妇高清做爰视频 | 狂野欧美激情性xxxx欧美 | 亚洲午夜精品久久久久久久久久久久 | 久久理论电影 | 亚洲成人一二三 | 91一区在线观看 | 手机看片中文字幕 | 日韩精品一卡 | 日韩精品一区二区三区免费观看 | 久久99热国产 | 成人看片 | 久久99久久99精品免费看小说 | 九色91av| 国产粉嫩在线 | 亚洲综合狠狠干 | 日韩av中文在线观看 | 天天天天天天操 | 天海翼一区二区三区免费 | 天天操夜夜操国产精品 | 人人盈棋牌| 最近最新最好看中文视频 | 狂野欧美激情性xxxx欧美 | 午夜婷婷在线播放 | 四虎免费在线观看 | 成人黄色片免费 | 麻豆一区二区 | 久久av在线播放 | 久草在线资源观看 | 久久综合色天天久久综合图片 | 日韩免费成人av | 久久a免费视频 | 日韩av一区二区三区在线观看 | 久久97视频| 91在线视频一区 | 91欧美国产 | 亚洲天堂网站视频 | 一区二区三区在线观看中文字幕 | 国产精品久久久视频 | 五月天综合网站 | 日韩精品中文字幕在线播放 | 丁香九月激情 | 九九热re | 中文字幕超清在线免费 | 在线看片一区 | 美女免费电影 | 欧美日韩精品网站 | 久久香蕉一区 | 国产在线观看你懂得 | 91资源在线免费观看 | 日韩精品欧美一区 | 色伊人网| 日韩一区二区免费在线观看 | 午夜精品电影一区二区在线 | 国产精品白浆视频 | 天天草综合网 | 国产精品美女免费 | 久草精品视频在线看网站免费 | 日韩av成人在线观看 | 深夜国产福利 | 69亚洲视频| 免费在线观看av | 91中文字幕在线视频 | 丁香六月久久综合狠狠色 | 天天操比| 日本爱爱免费视频 | 欧美做受高潮电影o | 国产精品mv在线观看 | 久久在草| 精品久久久精品 | 四虎成人精品永久免费av | 日韩欧美在线国产 | 国产视频欧美视频 | 99热亚洲精品 | 免费看的黄色的网站 | 日韩欧美在线影院 | 成人免费视频视频在线观看 免费 | 伊人狠狠 | 久草在线观 | 久久久免费精品国产一区二区 | 亚洲精品视频偷拍 | 伊人精品影院 | 色综合久久综合中文综合网 | 精品欧美在线视频 | 91超级碰 | 激情视频综合网 | 久久久久免费精品 | 成人国产一区二区 | 国产成人精品一区二区三区福利 | 91在线精品视频 | 久久久免费视频播放 | 天天躁日日躁狠狠躁av麻豆 | 亚洲电影久久久 | 丁香婷婷色 | 午夜国产在线观看 | 精品国产一区二区三区久久久蜜月 | 国产精久久久久久妇女av | 日韩在线播放欧美字幕 | 日韩一三区 | 一区在线电影 | 天天干天天玩天天操 | 狠狠色丁香婷婷 | 色综合久久久久久久 | 99热最新地址 | 亚洲人成人在线 | 精品国产一区二区三区四区vr | 国产中文在线视频 | 国产精品高潮呻吟久久av无 | 99视频在线观看视频 | 日韩欧美中文 | 五月天久久狠狠 | 91污污视频在线观看 | 国产成人三级一区二区在线观看一 | 人人舔人人爽 | 97视频网址 | 人人涩 | 亚洲精品一区二区三区高潮 | 五月天狠狠操 | 亚洲久草视频 | 免费观看91 | 色婷婷久久久综合中文字幕 | 国产精品扒开做爽爽的视频 | 亚洲精品88欧美一区二区 | 天天天天天天天操 | 日本中文字幕在线看 | 欧美先锋影音 | 国产精品99爱 | 精品国产电影一区 | 最新av在线播放 | 久草在线高清 | 色综合欧洲 | 亚洲成熟女人毛片在线 | 国产视频观看 | 天天摸天天弄 | 91丨九色丨勾搭 | 97人人澡人人添人人爽超碰 | 99久久久久免费精品国产 | 国产精品乱码一区二区视频 | 国产精品中文在线 | 亚洲精品456在线播放第一页 | 成人在线观看影院 | 成人h视频在线播放 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | 精品国产理论片 | 伊人在线视频 | 国产不卡精品视频 | 久久久影片 | 亚洲精品国产综合久久 | 97超碰超碰久久福利超碰 | 有码中文字幕在线观看 | 国产成人三级在线观看 | 久久免费一 | 欧美一级久久 | 成人av免费网站 | 国产成人黄色在线 | 久久综合狠狠综合久久综合88 | 91私密视频 | 66av99精品福利视频在线 | 一级成人免费视频 | 九九热免费观看 | 96国产精品视频 | 最新国产一区二区三区 | 中文字幕 在线 一 二 | 91免费观看 | 国产一及片| 综合激情伊人 | 日韩三级精品 | 日韩丝袜视频 | 99色视频| 中文字幕av日韩 | 日本久久久亚洲精品 | 91传媒视频在线观看 | 午夜精品久久久久久久久久久久 | 国产精品99久久免费观看 | 久久免费看av | 久久免费在线观看视频 | 日韩精品一区二区三区免费观看视频 | www.夜夜操 | av高清在线| 在线观看免费成人 | 四虎国产精 | 久久久久久久久影院 | 99视频在线 | 黄色av网站在线免费观看 | 五月婷婷六月丁香在线观看 | 中文字幕在线专区 | 免费国产一区二区视频 | 国产成人av一区二区三区在线观看 | a极黄色片 | 成人福利在线观看 | 欧美精品少妇xxxxx喷水 | 成人av日韩 | 亚洲精品小区久久久久久 | 美女福利视频在线 |