之前 分析了裝飾器的語(yǔ)法,由此可以直接推導(dǎo)出其基本框架。但為了寫出一個(gè)功能完整的裝飾器,還需要了解一個(gè)概念——閉包。
創(chuàng)新互聯(lián)建站是一家專業(yè)提供千陽(yáng)企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì)、H5開發(fā)、小程序制作等業(yè)務(wù)。10年已為千陽(yáng)眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)的建站公司優(yōu)惠進(jìn)行中。
閉包(closure) ,是引用了自由變量的函數(shù)。 這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。
看下面的例子
對(duì) f 內(nèi)部的函數(shù) g 來說,參數(shù) a 既不是它的參數(shù),也不是它的局部變量,而是它的自由變量。該自由變量可以
閉包和嵌套函數(shù)的概念有所區(qū)別。閉包當(dāng)然是嵌套函數(shù),但沒有引用自由變量的嵌套函數(shù)卻不是閉包。
Python 的函數(shù)有一個(gè)只讀屬性 __closure__ ,存儲(chǔ)的就是函數(shù)所引用的自由變量,
如果僅僅是嵌套函數(shù),它的 __closure__ 應(yīng)該是 None 。
閉包有個(gè)重要的特性:內(nèi)部函數(shù)只能引用而不能修改外部函數(shù)中定義的自由變量。試圖直接修改只有兩種結(jié)果,要么運(yùn)行出錯(cuò),要么你以為你修改了,實(shí)際并沒有。
不能修改不是因?yàn)?Python 設(shè)計(jì)者故意限制,不給它權(quán)限,而是外部的自由變量被內(nèi)部的局部變量覆蓋了;被覆蓋了也不是閉包獨(dú)有的特性,從普通函數(shù)內(nèi)部同樣也不能直接修改全局變量。Python 命名空間的查找規(guī)則簡(jiǎn)寫為 LEGB,四個(gè)字母分別代表 local、enclosed、global 和 build-in,閉包外層函數(shù)的命名空間就是 enclosed。Python 在檢索變量時(shí),按照 L - E - G - B 的順序依次查找,如果在 L 中找到了變量,就不會(huì)繼續(xù)向后查找。
在示例 1 中,你的本意是修改自由變量 number ,然而并不能:由于存在對(duì) number 的賦值語(yǔ)句( number += 1 ),Python 會(huì)認(rèn)為 number 是 printer 的局部變量,可是在局部變量字典中又查找不到它的定義,只好拋出異常。拋出的異常不是因?yàn)椴荒苄薷淖杂勺兞浚蔷植孔兞窟€沒賦值就被引用了。
在示例 2 中,Python 成功地在 printer 內(nèi)定義了局部變量 number ,并覆蓋了同名自由變量,你可能以為自己成功修改了 print_msg 中的 number ,然而并沒有。
怎么才能修改呢?
一種做法是利用可變類型(mutable)的特性,把變量存放在列表(List)之中。對(duì)可變的列表的修改并不需要對(duì)列表本身賦值, number[0] = 3 只是修改了列表元素。雖然列表發(fā)生了變化,但引用列表的變量卻并沒有改變,巧妙地“瞞”過了 Python。見示例3。
Python 3 引入了 nonlocal 關(guān)鍵字,明確告訴解釋器:這不是局部變量,要找上外頭找去。在示例 4 中, nonlocal 幫助我們實(shí)現(xiàn)了所期望的對(duì)自由變量的修改。
其實(shí),在 Python 2 中,用 global 代替 nonlocal ,也能達(dá)到類似的效果,但由于全局變量的不易控制,這種做法不被提倡。
下面的例子很好地展示了自由變量的特點(diǎn):與引用它的函數(shù)一同存在,而想要修改它,得小心謹(jǐn)慎。
裝飾器 rate_limit 的作用,是限制被裝飾的函數(shù)每秒內(nèi)最多被訪問 max_per_sec 次。為此,需要維護(hù)一個(gè)變量用以記錄上次被調(diào)用的時(shí)刻,它獨(dú)立于函數(shù)之外,和被修飾的函數(shù)一同存在,還能在每次被調(diào)用的時(shí)候更新。 last_time_called 就是這樣的變量。為了正確地更新, last_time_called 以列表的形式存在。如果在 Python 3 中,它也可以直接存為 float ,只要在內(nèi)部函數(shù)中聲明為 nonlocal ,也可以達(dá)到同樣的目的。
在python中,函數(shù)可以被嵌套定義,也就是說,函數(shù)中可以定義函數(shù)。該函數(shù)還可以將其內(nèi)部定義的函數(shù)作為返回值返回。
閉包的定義:一般來說,我們可以認(rèn)為,如果一個(gè)函數(shù)可以讀取其他函數(shù)中的局部變量,那么它們就構(gòu)成了閉包。
注意 :閉包的定義不是特別清晰,但大體上的意思是這樣的。
我們知道,普通的函數(shù)是可以使用全局變量的
類似的,函數(shù)中定義的函數(shù),也是可以使用外部函數(shù)的變量的。因此,滿足了函數(shù)讀取了其他函數(shù)局部變量的這一條件,他們因此構(gòu)成了閉包。
在閉包的使用中,我們可以先給外部的函數(shù)賦予不同的局部變量,然后再調(diào)用其中內(nèi)部的函數(shù)時(shí),就可以讀取到這些不同的局部變量了。
外部變量的使用 在普通函數(shù)中,雖然可以直接使用全局變量,但是不可以直接修改全局變量。從變量的作用域來說,一旦你嘗試修改全局變量,那么就會(huì)嘗試創(chuàng)建并使用一個(gè)同名的局部變量。因此,如果你需要在普通函數(shù)中修改全局變量,需要使用global
同樣的,如果你希望通過定義在內(nèi)部的函數(shù)去修改其外部函數(shù)的變量,那么必須使用nonlocal
自省就是面向?qū)ο蟮恼Z(yǔ)言所寫的程序在運(yùn)行時(shí),所能知道對(duì)象的類型.
也就是運(yùn)行時(shí)能夠獲得對(duì)象的類型.比如type(),dir(),getattr(),hasattr(),isinstance()
a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print(type(a),isinstance(b,dict))
2 Python 中單下劃線和雙下劃線
_xxx 不能用'from module import *'導(dǎo)入 以單下劃線開頭的表示的是保護(hù)類型的變量。即保護(hù)類型只能允許其本身與子類進(jìn)行訪問。
_ _xxx__ 系統(tǒng)定義的是特列方法。像__init__之類的
__xxx 類中的私有變量名 雙下劃線的表示的是私有類型的變量。只能是允許這個(gè)類本身進(jìn)行訪問了。連子類也不可以
3 python中的不定長(zhǎng)參數(shù) *args and **kwargs
不確定函數(shù)里傳遞參數(shù)個(gè)數(shù)時(shí)可以用*args
def getall(*args):
for item in args:
print(item)
getall('a','b','c')
getall(*['a','b','c']) # 打散
使用沒有事先定義的參數(shù)名kwargs**
def get_undifined(**kwargs):
for item in kwargs.items():
print(item[0],item[1])
get_undifined(name='amy',age=14) # 打散
get_undifined(**{'a':1,'b':'hello'}) # 打散
注意兩者共同使用的時(shí)候 位置參數(shù) 要在前
4 python中裝飾器 與 AOP 面向切面編程
切面需求: 較為經(jīng)典的有插入日志、性能測(cè)試、事務(wù)處理等
而裝飾器這種設(shè)計(jì)模式 是解決這類問題的絕佳途徑,可以為已經(jīng)存在的對(duì)象添加額外的功能
5 python中鴨子模型
會(huì)鴨子叫的就是鴨子,不關(guān)心對(duì)象是什么類型,只關(guān)心對(duì)象的特性。
比如眾多的文件句柄,可迭代對(duì)象等具有同一類特征的不同數(shù)據(jù)對(duì)象
鴨子類型在動(dòng)態(tài)語(yǔ)言中經(jīng)常使用,非常靈活
6 Python中的作用域
python中,一個(gè)變量的作用域總是由在代碼中被賦值的地方所決定的。
Python變量的搜索順序:
本地作用域(Local)→當(dāng)前作用域被嵌入的本地作用域(Enclosing locals)→全局/模塊作用域(Global)→內(nèi)置作用域(Built-in)
7 python中的語(yǔ)法結(jié)構(gòu)閉包
閉包(closure)是函數(shù)式編程的重要的 語(yǔ)法結(jié)構(gòu)。閉包也是一種組織代碼的結(jié)構(gòu),它同樣提高了代碼的可重復(fù)使用性。
當(dāng)一個(gè)內(nèi)嵌函數(shù)引用其外部作作用域的變量,我們就會(huì)得到一個(gè)閉包. 總結(jié)一下,創(chuàng)建一個(gè)閉包必須滿足以下幾點(diǎn):
1 必須有一個(gè)內(nèi)嵌函數(shù)
2 內(nèi)嵌函數(shù)必須引用外部函數(shù)中的變量
3 外部函數(shù)的返回值必須是內(nèi)嵌函數(shù)
8 python 的 垃圾回收機(jī)制
Python垃圾回收機(jī)制
Python GC主要使用 引用計(jì)數(shù)(reference counting)來跟蹤和回收垃圾。在引用計(jì)數(shù)的基礎(chǔ)上,通過“標(biāo)記-清除”(mark and sweep)
解決容器對(duì)象可能產(chǎn)生的循環(huán)引用問題,通過“分代回收”(generation collection)以空間換時(shí)間的方法提高垃圾回收效率。
1 引用計(jì)數(shù)
PyObject是每個(gè)對(duì)象必有的內(nèi)容,其中ob_refcnt就是做為引用計(jì)數(shù)。當(dāng)一個(gè)對(duì)象有新的引用時(shí),它的ob_refcnt就會(huì)增加,
當(dāng)引用它的對(duì)象被刪除,它的ob_refcnt就會(huì)減少.引用計(jì)數(shù)為0時(shí),該對(duì)象生命就結(jié)束了。
2 標(biāo)記-清除機(jī)制
基本思路是先按需分配,等到?jīng)]有空閑內(nèi)存的時(shí)候從寄存器和程序棧上的引用出發(fā),遍歷以對(duì)象為節(jié)點(diǎn)、以引用為邊構(gòu)成的圖,
把所有可以訪問到的對(duì)象打上標(biāo)記,然后清掃一遍內(nèi)存空間,把所有沒標(biāo)記的對(duì)象釋放。
3 分代技術(shù)
分代回收的整體思想是:將系統(tǒng)中的所有內(nèi)存塊根據(jù)其存活時(shí)間劃分為不同的集合,每個(gè)集合就成為一個(gè)“代”,
垃圾收集頻率隨著“代”的存活時(shí)間的增大而減小,存活時(shí)間通常利用經(jīng)過幾次垃圾回收來度量。
Python默認(rèn)定義了三代對(duì)象集合,索引數(shù)越大,對(duì)象存活時(shí)間越長(zhǎng)。
9 python種類以及與其他計(jì)算機(jī)語(yǔ)言類型對(duì)比
C語(yǔ)言: 代碼編譯得到機(jī)器碼 ,機(jī)器碼在處理器上直接執(zhí)行,每一條指令控制CPU工作–速度較快
Java,c#,python,php 其他語(yǔ)言: 代碼編譯得到字節(jié)碼 ,虛擬機(jī)執(zhí)行字節(jié)碼并轉(zhuǎn)換成機(jī)器碼再后在處理器上執(zhí)行—-速度慢
python的種類
Cpython
Python的官方版本,使用C語(yǔ)言實(shí)現(xiàn),使用最為廣泛,
CPython實(shí)現(xiàn)會(huì)將源文件(py文件)轉(zhuǎn)換成字節(jié)碼文件
(pyc文件),然后運(yùn)行在Python虛擬機(jī)上。
Jyhton
Python的Java實(shí)現(xiàn),Jython會(huì)將Python代碼動(dòng)態(tài)編譯成Java
字節(jié)碼,然后在JVM上運(yùn)行。
IronPython
Python的C#實(shí)現(xiàn),IronPython將Python代碼編譯成C#字節(jié)
碼,然后在CLR上運(yùn)行。(與Jython類似)
PyPy(特殊)
Python實(shí)現(xiàn)的Python,將Python的字節(jié)碼字節(jié)碼再編譯成機(jī)器
碼。
9 python PEP8 編碼規(guī)范
一 代碼編排
1 縮進(jìn)。4個(gè)空格的縮進(jìn)(編輯器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格。
2 每行最大長(zhǎng)度79,換行可以使用反斜杠,最好使用圓括號(hào)。換行點(diǎn)要在操作符的后邊敲回車。
3 類和top-level函數(shù)定義之間空兩行;類中的方法定義之間空一行;函數(shù)內(nèi)邏輯無關(guān)段落之間空一行;其他地方盡量不要再空行。
二 文檔編排
1 模塊內(nèi)容的順序:模塊說明和docstring—import—globalsconstants—其他定義。其中import部分,又按標(biāo)準(zhǔn)、三方和自己編寫順序依次排放,之間空一行。
2 不要在一句import中多個(gè)庫(kù),比如import os, sys不推薦。
3 如果采用from XX import XX引用庫(kù),可以省略‘module.’,都是可能出現(xiàn)命名沖突,這時(shí)就要采用import XX。
三 空格的使用
總體原則,避免不必要的空格。
1 各種右括號(hào)前不要加空格。
2 逗號(hào)、冒號(hào)、分號(hào)前不要加空格。
3 函數(shù)的左括號(hào)前不要加空格。如Func(1)。
4 序列的左括號(hào)前不要加空格。如list[2]。
5 操作符左右各加一個(gè)空格,不要為了對(duì)齊增加空格。
6 函數(shù)默認(rèn)參數(shù)使用的賦值符左右省略空格。
7 不要將多句語(yǔ)句寫在同一行,盡管使用‘;’允許。
8 if/for/while語(yǔ)句中,即使執(zhí)行語(yǔ)句只有一句,也必須另起一行。
四 注釋
注釋必須使用英文,最好是完整的句子,首字母大寫,句后要有結(jié)束符,結(jié)束符后跟兩個(gè)空格,開始下一句。如果是短語(yǔ),可以省略結(jié)束符。
1 塊注釋,在一段代碼前增加的注釋。在‘#’后加一空格。段落之間以只有‘#’的行間隔。比如:
2 行注釋,在一句代碼后加注釋。比如:x = x + 1 # Increment x但是這種方式盡量少使用。
3 避免無謂的注釋。
五 文檔描述
1 為所有的共有模塊、函數(shù)、類、方法寫docstrings;非共有的沒有必要,但是可以寫注釋(在def的下一行)。
2 如果docstring要換行,參考如下例子,詳見PEP 257
六 命名規(guī)范
新編代碼必須按下面命名風(fēng)格進(jìn)行,現(xiàn)有庫(kù)的編碼盡量保持風(fēng)格。
1 盡量單獨(dú)使用小寫字母‘l’,大寫字母‘O’等容易混淆的字母。
2 模塊命名盡量短小,使用全部小寫的方式,可以使用下劃線。
3 包命名盡量短小,使用全部小寫的方式,不可以使用下劃線。
4 類的命名使用CapWords的方式,模塊內(nèi)部使用的類采用_CapWords的方式。
5 異常命名使用CapWords+Error后綴的方式。
6 全局變量盡量只在模塊內(nèi)有效,類似C語(yǔ)言中的static。實(shí)現(xiàn)方法有兩種,一是__all__機(jī)制;二是前綴一個(gè)下劃線。
7 函數(shù)命名使用全部小寫的方式,可以使用下劃線。
8 常量命名使用全部大寫的方式,可以使用下劃線。
9 類的屬性(方法和變量)命名使用全部小寫的方式,可以使用下劃線。
9 類的屬性有3種作用域public、non-public和subclass API,可以理解成C++中的public、private、protected,non-public屬性前,前綴一條下劃線。
11 類的屬性若與關(guān)鍵字名字沖突,后綴一下劃線,盡量不要使用縮略等其他方式。
12 為避免與子類屬性命名沖突,在類的一些屬性前,前綴兩條下劃線。比如:類Foo中聲明__a,訪問時(shí),只能通過Foo._Foo__a,避免歧義。如果子類也叫Foo,那就無能為力了。
13 類的方法第一個(gè)參數(shù)必須是self,而靜態(tài)方法第一個(gè)參數(shù)必須是cls。
七 編碼建議
1 編碼中考慮到其他python實(shí)現(xiàn)的效率等問題,比如運(yùn)算符‘+’在CPython(Python)中效率很高,都是Jython中卻非常低,所以應(yīng)該采用.join()的方式。
2 盡可能使用‘is’‘is not’取代‘==’,比如if x is not None 要優(yōu)于if x。
3 使用基于類的異常,每個(gè)模塊或包都有自己的異常類,此異常類繼承自Exception。
4 異常中不要使用裸露的except,except后跟具體的exceptions。
5 異常中try的代碼盡可能少。比如:
6 使用startswith() and endswith()代替切片進(jìn)行序列前綴或后綴的檢查。比如
7 使用isinstance()比較對(duì)象的類型。比如
8 判斷序列空或不空,有如下規(guī)則
9 字符串不要以空格收尾。
10 二進(jìn)制數(shù)據(jù)判斷使用 if boolvalue的方式。
在Python語(yǔ)言中,可以在函數(shù)中定義函數(shù)。 這種在函數(shù)中嵌套定義的函數(shù)也叫內(nèi)部函數(shù)。我們來看下面的代碼:
上述代碼中,定義了函數(shù)greet,在函數(shù)greet內(nèi)部又定義了一個(gè)函數(shù)inner_func, 并調(diào)用該函數(shù)打印了一串字符。
我們可以看到,內(nèi)部函數(shù)inner_func的定義和使用與普通函數(shù)基本相同。需要注意的是變量的作用域,在上述代碼中,函數(shù)參數(shù)name對(duì)于全局函數(shù)greet是局部變量,對(duì)內(nèi)部函數(shù)inner_func來說則是非局部變量。內(nèi)部函數(shù)對(duì)于非局部變量的訪問規(guī)則類似于標(biāo)準(zhǔn)的外部函數(shù)訪問全局變量。
從這個(gè)例子我們還可以看到內(nèi)部函數(shù)的一個(gè)作用,就是通過定義內(nèi)部函數(shù)的方式將一些功能隱藏起來,防止外部直接調(diào)用。常見的場(chǎng)景是,在一個(gè)復(fù)雜邏輯的函數(shù)中,將一些小的任務(wù)定義成內(nèi)部函數(shù),然后由這個(gè)外層函數(shù)使用,這樣可以使代碼更為清晰,易于維護(hù)。這些內(nèi)部函數(shù)只會(huì)在這個(gè)外層函數(shù)中使用,不能被其他函數(shù)或模塊使用。
在Python語(yǔ)言中, 函數(shù)也是對(duì)象,它可以被創(chuàng)建、賦值給變量,或者作為函數(shù)的返回值。我們來看下面這個(gè)例子。
在上述代碼中,在函數(shù)gen_greet內(nèi)部定義了inner_func函數(shù),并返回了一個(gè)inner_func函數(shù)對(duì)象。外部函數(shù)gen_greet返回了一個(gè)函數(shù)對(duì)象,所以像gen_greet這樣的函數(shù)也叫工廠函數(shù)。
在內(nèi)部函數(shù)inner_func中,使用了外部函數(shù)的傳參greet_words(非局部變量),以及函數(shù)的參數(shù)name(局部變量),來打印一個(gè)字符串。
接下來,調(diào)用gen_greet("Hello")創(chuàng)建一個(gè)函數(shù)對(duì)象say_hello,緊接著調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hello, Mr. Zhang!
同樣的,調(diào)用gen_greet("Hi")創(chuàng)建一個(gè)函數(shù)對(duì)象say_hi,調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hi,Tony!
我們可以發(fā)現(xiàn),gen_greet返回的函數(shù)對(duì)象具有記憶功能,它能夠把所需使用的非局部變量保存下來,用于后續(xù)被調(diào)用的時(shí)候使用。這種保存了非局部變量的函數(shù)對(duì)象被稱作閉包(closure)。
那么閉包是如何實(shí)現(xiàn)的呢?其實(shí)并不復(fù)雜,函數(shù)對(duì)象中有一個(gè)屬性__closure__,它就是在創(chuàng)建函數(shù)對(duì)象時(shí)用來保存這些非局部變量的。
__closure__屬性是一個(gè)元組或者None類型。在上述代碼中,我們可以通過下面方式查看:
函數(shù)的嵌套所實(shí)現(xiàn)的功能大都可以通過定義類的方式來實(shí)現(xiàn),而且類是更加面向?qū)ο蟮拇a編寫方式。
嵌套函數(shù)的一個(gè)主要用途是實(shí)現(xiàn)函數(shù)的裝飾器。我們看下面的代碼:
在上述代碼中,logger函數(shù)返回函數(shù)with_logging,with_logging則是打印了函數(shù)func的名稱及傳入的參數(shù),然后調(diào)用func, 并將參數(shù)傳遞給func。其中的@wraps(func)語(yǔ)句用于復(fù)制函數(shù)func的名稱、注釋文檔、參數(shù)列表等等,使得with_logging函數(shù)具有被裝飾的函數(shù)func相同的屬性。
代碼中接下來用@logger對(duì)函數(shù)power_func進(jìn)行修飾,它的作用等同于下面的代碼:
可見,裝飾器@符其實(shí)就是上述代碼的精簡(jiǎn)寫法。
通過了解了嵌套函數(shù)和閉包的工作原理,我們?cè)谑褂眠^程中就能夠更加得心應(yīng)手了。