摘要:初步認(rèn)識(shí)裝飾器函數(shù)裝飾器用于在源代碼中標(biāo)記函數(shù),以某種方式增強(qiáng)函數(shù)的行為。函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。只有涉及嵌套函數(shù)時(shí)才有閉包問題。如果想保留函數(shù)原本的屬性,可以使用標(biāo)準(zhǔn)庫中的裝飾器。
《流暢的Python》筆記1. 初步認(rèn)識(shí)裝飾器本篇將從最簡(jiǎn)單的裝飾器開始,逐漸深入到閉包的概念,然后實(shí)現(xiàn)參數(shù)化裝飾器,最后介紹標(biāo)準(zhǔn)庫中常用的裝飾器。
函數(shù)裝飾器用于在源代碼中“標(biāo)記”函數(shù),以某種方式增強(qiáng)函數(shù)的行為。裝飾器就是函數(shù),或者說是可調(diào)用對(duì)象,它以另一個(gè)函數(shù)為參數(shù),最后返回一個(gè)函數(shù),但這個(gè)返回的函數(shù)并不一定是原函數(shù)。
1.1 裝飾器基礎(chǔ)用法以下是裝飾器最基本的用法:
# 代碼1 #裝飾器用法 @decorate def target(): pass # 上述代碼等價(jià)于以下代碼 def target(): pass target = decorate(target)
即,最終的target函數(shù)是由decorate(target)返回的函數(shù)。下面這個(gè)例子說明了這一點(diǎn):
# 代碼2 def deco(func): def inner(): print("running inner()") return inner @deco def target(): print("running target()") target() print(target) # 結(jié)果 running inner() # 輸出的是裝飾器內(nèi)部定義的函數(shù)的調(diào)用結(jié)果.inner at 0x000001AF32547D90>
從上面可看出,裝飾器的一大特性是能把被裝飾的函數(shù)替換成其他函數(shù)。但嚴(yán)格說來,裝飾器只是語法糖(語法糖:在編程語言中添加某種語法,但這種語法對(duì)語言的功能沒有影響,只是更方便程序員使用)。
裝飾器還可以疊加。下面是一個(gè)說明,具體例子見后面章節(jié):
# 代碼3 @d1 @d2 def f(): pass #上述代碼等價(jià)于以下代碼: def f(): pass f = d1(d2(f))1.2 Python何時(shí)執(zhí)行裝飾器
裝飾器的另一個(gè)關(guān)鍵特性是,它在被裝飾的函數(shù)定義后立即運(yùn)行,這通常是在導(dǎo)入時(shí),即Python加載模塊時(shí):
# 代碼4 registry = [] def register(func): print("running register(%s)" % func) registry.append(func) return func @register def f1(): print("running f1()") def f2(): print("running f2()") if __name__ == "__main__": print("running in main") print("registry ->", registry) f1() f2() # 結(jié)果 running register() running in main # 進(jìn)入到主程序 registry -> [ ] running f1() running f2()
裝飾器register在加載模塊時(shí)就對(duì)f1()進(jìn)行了注冊(cè),所以當(dāng)運(yùn)行主程序時(shí),列表registry并不為空。
函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。這突出了Python程序員常說的導(dǎo)入時(shí)和運(yùn)行時(shí)之間的區(qū)別。
裝飾器在真實(shí)代碼中的使用方式與代碼4中有所不同:
裝飾器和被裝飾函數(shù)一般不在一個(gè)模塊中,通常裝飾器定義在一個(gè)模塊中,然后應(yīng)用到其他模塊中的函數(shù)上;
大多數(shù)裝飾器會(huì)在內(nèi)部定義一個(gè)函數(shù),然后將其返回。
代碼4中的裝飾器原封不動(dòng)地返回了傳入的函數(shù)。這種裝飾器并不是沒有用,正如代碼4中的裝飾器的名字一樣,這類裝飾器常充當(dāng)了注冊(cè)器,很多Web框架就使用了這種方法。下一小節(jié)也是該類裝飾器的一個(gè)例子。
1.3 使用裝飾器改進(jìn)策略模式上一篇中我們用Python函數(shù)改進(jìn)了傳統(tǒng)的策略模式,其中,我們定義了一個(gè)promos列表來記錄有哪些具體策略,當(dāng)時(shí)的做法是用globals()函數(shù)來獲取具體的策略函數(shù),現(xiàn)在我們用裝飾器來改進(jìn)這一做法:
# 代碼5,對(duì)之前的代碼進(jìn)行了簡(jiǎn)略 promos = [] def promotion(promo_func): # 只充當(dāng)了注冊(cè)器 promos.append(promo_func) return promo_func @promotion def fidelity(order): pass @promotion def bulk_item(order): pass @promotion def large_order(order): pass def best_promo(order): return max(promo(order) for promo in promos)
該方案相比之前的方案,有以下三個(gè)優(yōu)點(diǎn):
促銷策略函數(shù)無需使用特殊名字,即不用再以_promo結(jié)尾
@promotion裝飾器突出了被裝飾函數(shù)的作用,還便于臨時(shí)禁用某個(gè)促銷策略(只需將裝飾器注釋掉)
促銷策略函數(shù)在任何地方定義都行,只要加上裝飾器即可。
2. 閉包正如前文所說,多數(shù)裝飾器會(huì)在內(nèi)部定義函數(shù),并將其返回,已替換掉傳入的函數(shù)。這個(gè)機(jī)制的實(shí)現(xiàn)就要靠閉包,但在理解閉包之前,先來看看Python中的變量作用域。
2.1 變量作用域規(guī)則通過下述例子來解釋局部變量和全局變量:
# 代碼6 >>> def f1(a): ... print(a) ... print(b) >>> f1(3) 3 Traceback (most recent call last): -- snip -- NameError: name "b" is not defined
當(dāng)代碼運(yùn)行到print(a)時(shí),Python查找變量a,發(fā)現(xiàn)變量a存在于局部作用域中,于是順利執(zhí)行;當(dāng)運(yùn)行到print(b)時(shí),python查找變量b,發(fā)現(xiàn)局部作用域中并沒有變量b,便接著查找全局作用域,發(fā)現(xiàn)也沒有變量b,最終報(bào)錯(cuò)。正確的調(diào)用方式相信大家也知道,就是在調(diào)用f1(3)之前給變量b賦值。
我們?cè)倏慈缦麓a:
# 代碼7 >>> b = 6 >>> def f2(a): ... print(a) ... print(b) ... b = 9 >>> f2(3) 3 Traceback (most recent call last): -- snip -- UnboundLocalError: local variable "b" referenced before assignment
按理說不應(yīng)該報(bào)錯(cuò),并且b的值應(yīng)該打印為6,但結(jié)果卻不是這樣。
事實(shí)是:變量b本來是全局變量,但由于在f2()中我們?yōu)樽兞?b>b賦了值,于是Python在局部作用域中也注冊(cè)了一個(gè)名為b的變量(全局變量b依然存在,有編程基礎(chǔ)的同學(xué)應(yīng)該知道,這叫做“覆蓋”)。當(dāng)Python執(zhí)行到print(b)語句時(shí),Python先搜索局部作用域,發(fā)現(xiàn)其中有變量b,但是b此時(shí)還沒有被賦值(全局變量b被覆蓋,而局部變量b的賦值語句在該句后面),于是Python報(bào)錯(cuò)。
如果不想代碼7報(bào)錯(cuò),則需要使用global語句,將變量b聲明為全局變量:
# 代碼8 >>> b = 6 >>> def f2(a): ... global b ... -- snip --2.2 閉包的概念
現(xiàn)在開始真正接觸閉包。閉包指延伸了作用域的函數(shù),它包含函數(shù)定義體中引用,但不在定義體中定義的非全局變量,即這類函數(shù)能訪問定義體之外的非全局變量。只有涉及嵌套函數(shù)時(shí)才有閉包問題。
下面用一個(gè)例子來說明閉包以及非全局變量。定義一個(gè)計(jì)算某商品一段時(shí)間內(nèi)均價(jià)的函數(shù)avg,它的表現(xiàn)如下:
# 代碼9 >>> avg(10) 10.0 >>> avg(11) 10.5 >>> avg(12) 11.0
假定商品價(jià)格每天都在變化,因此需要一個(gè)變量來保存這些值。如果用類的思想,我們可以定義一個(gè)可調(diào)用對(duì)象,把這些值存到內(nèi)部屬性中,然后實(shí)現(xiàn)__call__方法,讓其表現(xiàn)得像函數(shù);但如果按裝飾器的思想,可以定義一個(gè)如下的嵌套函數(shù):
# 代碼10 def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager
然后以如下方式使用這個(gè)函數(shù):
# 代碼11 >>> avg = make_averager() >>> avg(10) 10.0 -- snip --
不知道大家剛接觸這個(gè)內(nèi)部的averager()函數(shù)時(shí)有沒有疑惑:代碼11中,當(dāng)執(zhí)行avg(10)時(shí),它是到哪里去找的變量series?series是函數(shù)make_averager()的局部變量,當(dāng)make_averager()返回了averager()后,它的局部作用域就消失了,所以按理說series也應(yīng)該跟著消失,并且上述代碼應(yīng)該報(bào)錯(cuò)才對(duì)。
事實(shí)上,在averager函數(shù)中,series是自由變量(free variable),即未在局部作用域中綁定的變量。這里,自由變量series和內(nèi)部函數(shù)averager共同組成了閉包,參考下圖:
實(shí)際上,Python在averager的__code__屬性中保存了局部變量和自由變量的名稱,在__closure__屬性中保存了自由變量的值:
# 代碼12,注意這些變量的單詞含義,一目了然 >>> avg.__code__.co_varnames # co_varnames保存局部變量的名稱 ("new_value", "total") >>> avg.__code__.co_freevars # co_freevars保存自由變量的名稱 ("series",) >>> avg.__closure__ # 單詞closure就是閉包的意思 # __closure__是一個(gè)cell對(duì)象列表,其中的元素和co_freevars元組一一對(duì)應(yīng) (,) >>> avg.__closure__[0].cell_contents [10, 11, 12] # cell對(duì)象的cell_contents屬性才是真正保存自由變量的值的地方 |
綜上:閉包是一種函數(shù),它會(huì)保存定義函數(shù)時(shí)存在的自由變量的綁定,這樣調(diào)用函數(shù)時(shí),雖然外層函數(shù)的局部作用域不可用了,但仍能使用那些綁定。
注意:只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中的外部變量。
2.3 nonlocal聲明代碼10中的make_averager函數(shù)并不高效,因?yàn)槿绻挥?jì)算均值的話,其實(shí)不用保存每次的價(jià)格,我們可按如下方式改寫代碼10:
# 代碼13 def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager
但此時(shí)直接運(yùn)行代碼11的話,則會(huì)報(bào)代碼7中的錯(cuò)誤:UnboundLocalError。
問題在于:由于count是不可變類型,在執(zhí)行count += 1時(shí),該語句等價(jià)于count = count + 1,而這就成了賦值語句,count不再是自由變量,而變成了averager的局部變量。total也是一樣的情況。而在之前的代碼10中沒有這個(gè)問題,因?yàn)?b>series是個(gè)可變類型,我們只是調(diào)用series.append,以及把它傳給了sum和len,它并沒有變?yōu)榫植孔兞俊?/p>
對(duì)于不可變類型來說,只能讀取,不能更新,否則會(huì)隱式創(chuàng)建局部變量。為了解決這個(gè)問題,Python3引入了nonlocal聲明。它的作用是把變量顯式標(biāo)記為自由變量:
# 代碼14 def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total -- snip --3. 裝飾器
了解了閉包后,現(xiàn)在開始正式使用嵌套函數(shù)來實(shí)現(xiàn)裝飾器。首先來認(rèn)識(shí)標(biāo)準(zhǔn)庫中三個(gè)重要的裝飾器。
3.1 標(biāo)準(zhǔn)庫中的裝飾器 3.1.1 functools.wraps裝飾器來看一個(gè)簡(jiǎn)單的裝飾器:
# 代碼15 def deco(func): def test(): func() return test @deco def Test(): """This is a test""" print("This is a test") print(Test.__name__) print(Test.__doc__) # 結(jié)果 test None
我們想讓裝飾器來自動(dòng)幫我們做一些額外的操作,但像改變函數(shù)屬性這樣的操作并不一定是我們想要的:從上面可以看出,Test現(xiàn)在指向了內(nèi)部函數(shù)test,Test自身的屬性被遮蓋。如果想保留函數(shù)原本的屬性,可以使用標(biāo)準(zhǔn)庫中的functools.wraps裝飾器。下面以一個(gè)更復(fù)雜的裝飾器為例,它會(huì)在每次調(diào)用被裝飾函數(shù)時(shí)計(jì)時(shí),并將經(jīng)過的時(shí)間,傳入的參數(shù)和調(diào)用的結(jié)果打印出來:
# 代碼16 # clockdeco.py import time, functools def clock(func): # 兩層嵌套 @functools.wraps(func) # 綁定屬性 def clocked(*args, **kwargs): t0 = time.time() result = func(*args, **kwargs) elapsed = time.time() - t0 name = func.__name__ arg_lst = [] # 參數(shù)列表 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
它的使用將和下一個(gè)裝飾器一起展示。
3.1.2 functools.lru_cache裝飾器functools.lru_cache實(shí)現(xiàn)了備忘(memoization)功能,這是一項(xiàng)優(yōu)化技術(shù),他把耗時(shí)的函數(shù)的結(jié)果保存起來,避免傳入相同參數(shù)時(shí)重復(fù)計(jì)算。以斐波那契函數(shù)為例,我們知道以遞歸形式實(shí)現(xiàn)的斐波那契函數(shù)會(huì)出現(xiàn)很多重復(fù)計(jì)算,此時(shí),就可以使用這個(gè)裝飾器。以下代碼是沒使用該裝飾器時(shí)的運(yùn)行情況:
# 代碼17 from clockdeco import clock @clock def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == "__main__": print(fibonacci.__name__) print(fibonacci.__doc__) print(fibonacci(6)) # 結(jié)果: fibonacci # fibonacci原本的屬性得到了保留 None [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(2) -> 1 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(2) -> 1 [0.00000000s] fibonacci(3) -> 2 [0.00000000s] fibonacci(4) -> 3 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(2) -> 1 [0.00000000s] fibonacci(3) -> 2 [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(2) -> 1 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00049996s] fibonacci(2) -> 1 [0.00049996s] fibonacci(3) -> 2 [0.00049996s] fibonacci(4) -> 3 [0.00049996s] fibonacci(5) -> 5 [0.00049996s] fibonacci(6) -> 8 8
可以看出,fibonacci(1)調(diào)用了8次,下面我們用functools.lru_cache來改進(jìn)上述代碼:
# 代碼18 import functools from clockdeco import clock @functools.lru_cache() # 注意此處有個(gè)括號(hào)!該裝飾器就收參數(shù)!不能?。?@clock def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1) if __name__ == "__main__": print(fibonacci(6)) # 結(jié)果: [0.00000000s] fibonacci(0) -> 0 [0.00000000s] fibonacci(1) -> 1 [0.00000000s] fibonacci(2) -> 1 [0.00000000s] fibonacci(3) -> 2 [0.00000000s] fibonacci(4) -> 3 [0.00000000s] fibonacci(5) -> 5 [0.00000000s] fibonacci(6) -> 8 8
functools.lru_cache裝飾器可以接受參數(shù),并且此代碼還疊放了裝飾器。
lru_cache有兩個(gè)參數(shù):functools.lru_cache(maxsize=128, typed=False)
maxsize指定存儲(chǔ)多少個(gè)調(diào)用的結(jié)果,該參數(shù)最好是2的冪。當(dāng)緩存滿后,根據(jù)LRU算法替換緩存中的內(nèi)容,這也是為什么這個(gè)函數(shù)叫lru_cache。
type如果設(shè)置為True,它將把不同參數(shù)類型下得到的結(jié)果分開保存,即把通常認(rèn)為相等的浮點(diǎn)數(shù)和整數(shù)參數(shù)分開(比如區(qū)分1和1.0)。
lru_cache使用字典存儲(chǔ)結(jié)果,字典的鍵是傳入的參數(shù),所以被lru_cache裝飾的函數(shù)的所有參數(shù)都必須是可散列的!
3.1.3 functools.singledispatch裝飾器我們知道,C++支持函數(shù)重載,同名函數(shù)可以根據(jù)參數(shù)類型的不同而調(diào)用相應(yīng)的函數(shù)。以Python代碼為例,我們希望下面這個(gè)函數(shù)表現(xiàn)出如下行為:
# 代碼19 def myprint(obj): return "Hello~~~" # 以下是我們希望它擁有的行為: >>> myprint(1) Hello~~~ >>> myprint([]) Hello~~~ >>> myprint("hello") # 即,當(dāng)我們傳入特定類型的參數(shù)時(shí),函數(shù)返回特定的結(jié)果 This is a str
單憑這一個(gè)myprint還無法實(shí)現(xiàn)上述要求,因?yàn)镻ython不支持方法或函數(shù)的重載。為了實(shí)現(xiàn)類似的功能,一種常見的做法是將函數(shù)變?yōu)橐粋€(gè)分派函數(shù),使用一串if/elif/elif來判斷參數(shù)類型,再調(diào)用專門的函數(shù)(如myprint_str),但這種方式不利于代碼的擴(kuò)展和維護(hù),還顯得沒有B格。。。
為解決這個(gè)問題,從Python3.4開始,可以使用functools.singledispath裝飾器,把整體方案拆分成多個(gè)模塊,甚至可以為無法修改的類提供專門的函數(shù)。被@singledispatch裝飾的函數(shù)會(huì)變成泛函數(shù)(generic function),它會(huì)根據(jù)第一個(gè)參數(shù)的不同而調(diào)用響應(yīng)的專門函數(shù),具體用法如下:
# 代碼20 from functools import singledispatch import numbers @singledispatch def myprint(obj): return "Hello~~~" # 可以疊放多個(gè)register,讓同一函數(shù)支持不同類型 @myprint.register(str) # 注冊(cè)的專門函數(shù)最好處理抽象基類,而不是具體實(shí)現(xiàn),這樣代碼支持的兼容類型更廣泛 @myprint.register(numbers.Integral) def _(text): # 專門函數(shù)的名稱無所謂,使用 _ 可以避免起名字的麻煩 return "Special types"
對(duì)泛函數(shù)的補(bǔ)充:根據(jù)參數(shù)類型的不同,以不同方式執(zhí)行相同操作的一組函數(shù)。如果依據(jù)是第一個(gè)參數(shù),則是單分派;如果依據(jù)是多個(gè)參數(shù),則是多分派。
3.2 參數(shù)化裝飾器 3.2.1 簡(jiǎn)單版參數(shù)化裝飾器從上面諸多例子我們可以看到兩大類裝飾器:不帶參數(shù)的裝飾器(調(diào)用時(shí)最后沒有括號(hào))和帶參數(shù)的裝飾器(帶括號(hào))。Python將被裝飾的函數(shù)作為第一個(gè)參數(shù)傳給了裝飾器函數(shù),那裝飾器函數(shù)如何接受其他參數(shù)呢?做法是:創(chuàng)建一個(gè)裝飾器工廠函數(shù),在這個(gè)工廠函數(shù)內(nèi)部再定義其它函數(shù)作為真正的裝飾器。工廠函數(shù)代為接受參數(shù),這些參數(shù)作為自由變量供裝飾器使用。然后工廠函數(shù)返回裝飾器,裝飾器再應(yīng)用到被裝飾函數(shù)上。
我們把1.2中代碼4的@register裝飾器改為帶參數(shù)的版本,以active參數(shù)來指示裝飾器是否注冊(cè)某函數(shù)(雖然這么做有點(diǎn)多余)。這里只給出@register裝飾器的實(shí)現(xiàn),其余代碼參考代碼4:
# 代碼21 registry = set() def register(active=True): def decorate(func): # 變量active對(duì)于decorate函數(shù)來說是自由變量 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) # 即使不傳參數(shù)也要作為函數(shù)調(diào)用@register() def f():pass # 上述用法相當(dāng)于如下代碼: # register(active=False)(f)3.2.2 多層嵌套版參數(shù)化裝飾器
參數(shù)化裝飾器通常會(huì)把被裝飾函數(shù)替換掉,而且結(jié)構(gòu)上需要多一層嵌套。下面以3.1.1中代碼16里的@clock裝飾器為例,讓它按用戶要求的格式輸出數(shù)據(jù)。為了簡(jiǎn)便,不調(diào)用functools.wraps裝飾器:
# 代碼22 import time DEFAULT_FMT = "[{elapsed:0.8f}s] {name}({args}) -> {result}" def clock(fmt=DEFAULT_FMT): # 裝飾器工廠,fmt是裝飾器的參數(shù) def decorate(func): # 裝飾器 def clocked(*_args): # 最終的函數(shù) 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())) #locals()函數(shù)以字典形式返回clocked的局部變量 return _result return clocked return decorate
可以得到如下結(jié)論:裝飾器函數(shù)有且只有一個(gè)參數(shù),即被裝飾器的函數(shù);如果裝飾器要接受其他參數(shù),請(qǐng)?jiān)谠镜难b飾器外再套一層函數(shù)(工廠函數(shù)),由它來接受其余參數(shù);而你最終使用的函數(shù)應(yīng)該定義在裝飾器函數(shù)中,且它的參數(shù)列表應(yīng)該和被裝飾的函數(shù)一致。
4. 總結(jié)本篇首先介紹了最簡(jiǎn)單裝飾器如何定義和使用,介紹了裝飾器在什么時(shí)候被執(zhí)行,以及用最簡(jiǎn)單的裝飾器改造了上一篇的策略模式;隨后更進(jìn)一步,介紹了與閉包相關(guān)的概念,包括變量作用域,閉包和nonlocal聲明;最后介紹了更復(fù)雜的裝飾器,包括標(biāo)準(zhǔn)庫中的裝飾器的用法,以及如何定義帶參數(shù)的裝飾器。
但上述對(duì)裝飾器的描述都是基本的, 更復(fù)雜、工業(yè)級(jí)的裝飾器還需要更深入的學(xué)習(xí)。
迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41811.html
摘要:變量查找規(guī)則在中一個(gè)變量的查找順序是局部環(huán)境,閉包,全局,內(nèi)建閉包引用了自由變量的函數(shù)。閉包的作用閉包的最大特點(diǎn)是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù),此時(shí)即便生成閉包的環(huán)境父函數(shù)已經(jīng)釋放,閉包仍然存在。 導(dǎo)語:本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之函數(shù)篇的重點(diǎn)知識(shí)及個(gè)人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、掌握裝飾器的本質(zhì)、功...
摘要:函數(shù)裝飾器和閉包嚴(yán)格來說,裝飾器只是語法糖。何時(shí)執(zhí)行裝飾器它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。裝飾器突出了被裝飾的函數(shù)的作用,還便于臨時(shí)禁用某個(gè)促銷策略只需把裝飾器注釋掉。 函數(shù)裝飾器和閉包 嚴(yán)格來說,裝飾器只是語法糖。如前所示,裝飾器可以像常規(guī)的可調(diào)用對(duì)象那樣調(diào)用,其參數(shù)是另一個(gè)函數(shù)。有時(shí),這樣做更方便,尤其是做元編程(在運(yùn)行時(shí)改變程序的行為)時(shí)。 Python何時(shí)執(zhí)行裝飾器 它們?cè)?..
摘要:迭代器迭代是訪問集合元素的一種方式。迭代器是一個(gè)可以記住遍歷的位置的對(duì)象,迭代器對(duì)象從集合的第一個(gè)元素開始訪問,直到所有的元素被訪問完結(jié)束,迭代器只往前不會(huì)往后退。生成器特點(diǎn)保存了一套生成數(shù)值的算法。 迭代器 迭代是訪問集合元素的一種方式。迭代器是一個(gè)可以記住遍歷的位置的對(duì)象,迭代器對(duì)象從集合的第一個(gè)元素開始訪問,直到所有的元素被訪問完結(jié)束,迭代器只往前不會(huì)往后退。 可迭代對(duì)象 以直接...
摘要:本章主要是對(duì)上一章類的補(bǔ)充。對(duì)于多態(tài)的補(bǔ)充子類可以被看成是父類的類型,但父類不能被看成是子類的類型。仍然以類為例,動(dòng)物里有哺乳動(dòng)物,卵生動(dòng)物,有能飛的動(dòng)物和不能飛的動(dòng)物,這是兩種大的分類方式。一般在中,以為結(jié)尾類的都作為接口。 《Python編程:從入門到實(shí)踐》筆記。本章主要是對(duì)上一章Python類的補(bǔ)充。 1. 從一個(gè)類派生出所有類 上一篇文章說道Python類的定義與繼承一般是如下...
摘要:所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。 對(duì)于已經(jīng)對(duì) 閉包 或者 裝飾器有一定概念的,可以直接通過右側(cè)標(biāo)題目錄直接定位到相應(yīng)段落查看所需的內(nèi)容。 什么是裝飾器? 裝飾器(Decorator)相對(duì)簡(jiǎn)單,咱們先介紹它:裝飾器的功能是將被裝飾的函數(shù)當(dāng)作參數(shù)傳遞給與裝飾器對(duì)應(yīng)的函數(shù)(名稱相同的函數(shù)),并返回包裝后的被裝飾的函數(shù),聽起來有點(diǎn)繞,沒關(guān)系,直接看示意圖,...
閱讀 3499·2021-11-18 10:02
閱讀 3749·2021-09-13 10:25
閱讀 1950·2021-07-26 23:38
閱讀 2612·2019-08-30 15:44
閱讀 2311·2019-08-30 13:51
閱讀 1256·2019-08-26 11:35
閱讀 2299·2019-08-26 10:29
閱讀 3474·2019-08-23 14:56