包(lib)聊替、模塊(module)
在Python中,存在包和模塊兩個常見概念培廓。
模塊:編寫Python代碼的py文件
包:用來分門別類存放模塊代碼的文件夾惹悄,【一般存在一個__init__.py文件】
模塊的幾種導(dǎo)入方式:
__init__.py文件:
__all__變量的使用:
__init__.py文件中導(dǎo)入其他模塊文件,推薦使用
from?. import xxx
可以使用別名來簡化導(dǎo)入的模塊名稱【as的使用】肩钠。
注意:
1泣港、當(dāng)一個普通文件夾充當(dāng)包的時候,導(dǎo)入模塊時价匠,一定要指定模塊名稱当纱,因為普通文件夾不是包(包是可以直接導(dǎo)入的)。
2踩窖、當(dāng)導(dǎo)入的包路徑太長的時候坡氯,可以使用as關(guān)鍵字取別名來解決
3、包與普通文件夾的區(qū)別
發(fā)布模塊:
在文件的主目錄創(chuàng)建setup.py文件,編輯如下:
from?????distutils.core???? import ????setup
setup(name=”ljh”,version=”1.0”,description=”描述”,author=”作者”,author_email=”作者郵箱”,py_modules=[“xx.xx.xx.py”,”xx.xx.xxx.py”])
如下圖所示:
執(zhí)行構(gòu)建命令:
python?setup.py build
執(zhí)行打包命令:
python?setup.py sdist
之后項目中結(jié)構(gòu)如下:
執(zhí)行安裝命令:
python?setup.py install
就會將我們打包后的壓縮包安裝到Python對應(yīng)的第三方模塊下:
python36\Lib\site-packages
我們在代碼中就可以導(dǎo)入使用這個模塊了箫柳。
1.2==手形,is的使用
總結(jié)
is是比較兩個引用是否指向了同一個對象(地址引用比較)。
==是比較兩個對象是否相等悯恍。(比較的數(shù)值)
1.3?深拷貝库糠、淺拷貝
1.3.1?賦值
將一個變量賦值給另一個變量,這個過程叫做賦值坪稽。賦值會導(dǎo)致多個變量同時指向一塊內(nèi)存曼玩,所以此時不管是==或者is都返回True
所以當(dāng)一個發(fā)送變量鳞骤,另一個也隨之發(fā)送變化窒百。
1.3.2?淺拷貝(copy)
淺拷貝是對于一個對象的頂層拷貝
通俗的理解是:拷貝了引用,并沒有拷貝內(nèi)容
但是豫尽,當(dāng)a發(fā)送變化時篙梢,b不會變化:
1.3.3?深拷貝
深拷貝是對于一個對象所有層次的拷貝(遞歸)
1.3.4拷貝的其他方
注意常量類型的深淺拷貝問題(如字符串、小整形數(shù)值型美旧、元組)
1.4屬性property
面試題:
1渤滞、你對面向?qū)ο蟮睦斫?/p>
2、 面向?qū)ο蟮奶卣魇鞘裁?/p>
封裝榴嗅、繼承妄呕、多態(tài)
3、對封裝的理解嗽测?
封裝绪励,類本身就是一個封裝,封裝了屬性和方法。方法也是封裝,對一些業(yè)務(wù)邏輯的封裝扬绪。私有也是封裝凿宾,將一些方法和屬性私有化,對外提供可訪問的接口啦粹。
4、對繼承的理解
將共性的內(nèi)容放在父類中,子類只需要關(guān)注自己特有的內(nèi)容只厘,共性的繼承過來就行了。
這樣簡化開發(fā)舅巷,符合邏輯習(xí)慣羔味,利于擴展。
5悄谐、多態(tài)的理解
多態(tài)介评,一個對象在不同的情況下顯示不同的形態(tài)。在python中因為是弱類型語言,對類型沒有限定们陆,所有python中不完全支持多態(tài)寒瓦,但是多態(tài)的思想呢,python也是能體現(xiàn)的坪仇。
property是屬于類的,不能寫到方法中椅文,寫類里喂很,property是一個方法,可以傳遞屬性property(getName,setName)是值傳遞引用傳遞皆刺,不是調(diào)用少辣,把函數(shù)傳遞里面,不是返回值羡蛾,name =?property(getName,setName)
1.4.1私有屬性添加getter和setter方法
????def?__init__(self):
????????self.__money = 0
????def?getMoney(self):
????????return?self.__money
????def?setMoney(self, value):
????????if?isinstance(value, int):
????????????self.__money = value
????????else:
????????????print("error:不是整型數(shù)字")
1.4.2?使用property升級getter和setter方法
class?Money(object):
????def?__init__(self):
????????self.__money = 0
????def?getMoney(self):
????????return?self.__money
????def?setMoney(self, value):
????????if?isinstance(value, int):
????????????self.__money = value
????????else:
????????????print("error:不是整型數(shù)字")
????money = property(getMoney, setMoney)
運行結(jié)果:
In [1]: from?get_set import?Money
In [2]:
In [2]: a = Money()
In [3]:
In [3]: a.money
Out[3]: 0
In [4]: a.money = 100
In [5]: a.money
Out[5]: 100
In [6]: a.getMoney()
Out[6]: 100
1.4.3使用property取代getter和setter方法
@property成為屬性函數(shù)漓帅,可以對屬性賦值時做必要的檢查,并保證代碼的清晰短小痴怨,主要有2個作用
將方法轉(zhuǎn)換為只讀
重新實現(xiàn)一個屬性的設(shè)置和讀取方法,可做邊界判定
class?Money(object):
????def?__init__(self):
????????self.__money = 0
????@property
????def?money(self):
????????return?self.__money
????@money.setter
????def?money(self, value):
????????if?isinstance(value, int):
????????????self.__money = value
????????else:
????????????print("error:不是整型數(shù)字")
運行結(jié)果
In [3]: a = Money()
In [4]:
In [4]:
In [4]: a.money
Out[4]: 0
In [5]: a.money = 100
In [6]: a.money
Out[6]: 100
1.5?生成器
[if !supportLists]1.5.1?[endif]什么是生成器
通過列表生成式忙干,我們可以直接創(chuàng)建一個列表。但是浪藻,受到內(nèi)存限制捐迫,列表容量肯定是有限的。而且爱葵,創(chuàng)建一個包含100萬個元素的列表施戴,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素钧惧,那后面絕大多數(shù)元素占用的空間都白白浪費了暇韧。所以,如果列表元素可以按照某種算法推算出來浓瞪,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢懈玻?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間乾颁。在Python中涂乌,這種一邊循環(huán)一邊計算的機制,稱為生成器:generator英岭。
[if !supportLists]1.5.2?[endif]創(chuàng)建生成器方法1
要創(chuàng)建一個生成器湾盒,有很多種方法。第一種方法很簡單诅妹,只要把一個列表生成式的[ ]改成 ( )
In [15]: L = [ x*2?for?x in?range(5)]
In [16]: L
Out[16]: [0, 2, 4, 6, 8]
In [17]: G = ( x*2?for?x in?range(5))
In [18]: G
Out[18]: at 0x7f626c132db0>
In [19]:
創(chuàng)建L和 G 的區(qū)別僅在于最外層的 [ ] 和 ( ) 罚勾, L 是一個列表毅人,而 G 是一個生成器。我們可以直接打印出L的每一個元素尖殃,但我們怎么打印出G的每一個元素呢丈莺?如果要一個一個打印出來,可以通過next()函數(shù)獲得生成器的下一個返回值:
In [19]: next(G)
Out[19]: 0
In [20]: next(G)
Out[20]: 2
In [21]: next(G)
Out[21]: 4
In [22]: next(G)
Out[22]: 6
In [23]: next(G)
Out[23]: 8
In [24]: next(G)
---------------------------------------------------------------------------
StopIteration?????????????????????????????Traceback (most recent call last)
in ()
----> 1 next(G)
StopIteration:
In [25]:
In [26]: G = ( x*2?for?x in?range(5))
In [27]: for?x in?G:
???....: ????print(x)
???....: ????
0
2
4
6
8
In [28]:
生成器保存的是算法送丰,每次調(diào)用next(G)缔俄,就計算出 G 的下一個元素的值,直到計算到最后一個元素器躏,沒有更多的元素時俐载,拋出 StopIteration 的異常。當(dāng)然登失,這種不斷調(diào)用 next() 實在是太變態(tài)了遏佣,正確的方法是使用 for 循環(huán),因為生成器也是可迭代對象壁畸。所以贼急,我們創(chuàng)建了一個生成器后,基本上永遠(yuǎn)不會調(diào)用 next() 捏萍,而是通過 for 循環(huán)來迭代它,并且不需要關(guān)心 StopIteration 異常空闲。
[if !supportLists]1.5.3?[endif]創(chuàng)建生成器方法2
generator非常強大令杈。如果推算的算法比較復(fù)雜,用類似列表生成式的 for 循環(huán)無法實現(xiàn)的時候碴倾,還可以用函數(shù)來實現(xiàn)逗噩。
比如,著名的斐波拉契數(shù)列(Fibonacci)跌榔,除第一個和第二個數(shù)外异雁,任意一個數(shù)都可由前兩個數(shù)相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契數(shù)列用列表生成式寫不出來,但是僧须,用函數(shù)把它打印出來卻很容易:
In [28]: def?fib(times):
???....: ????n = 0
???....: ????a,b = 0,1
???....: ????while?n
???....: ????????print(b)
???....: ????????a,b = b,a+b
???....: ????????n+=1
???....: ????return?'done'
???....:
In [29]: fib(5)
1
1
2
3
5
Out[29]: 'done'
仔細(xì)觀察纲刀,可以看出,fib函數(shù)實際上是定義了斐波拉契數(shù)列的推算規(guī)則担平,可以從第一個元素開始示绊,推算出后續(xù)任意的元素,這種邏輯其實非常類似generator暂论。
也就是說面褐,上面的函數(shù)和generator僅一步之遙。要把fib函數(shù)變成generator取胎,只需要把print(b)改為yield b就可以了:
In [30]: def fib(times):
???....: ????n = 0
???....: ????a,b = 0,1
???....: ????while n
???....: ????????yield b
???....: ????????a,b = b,a+b
???....: ????????n+=1
???....: ????return 'done'
???....:
In [31]: F = fib(5)
In [32]: next(F)
Out[32]: 1
In [33]: next(F)
Out[33]: 1
In [34]: next(F)
Out[34]: 2
In [35]: next(F)
Out[35]: 3
In [36]: next(F)
Out[36]: 5
In [37]: next(F)
---------------------------------------------------------------------------
StopIteration ????????????????????????????Traceback (most recent call last)
in ()
----> 1 next(F)
StopIteration: done
在上面fib的例子展哭,我們在循環(huán)過程中不斷調(diào)用 yield ,就會不斷中斷。當(dāng)然要給循環(huán)設(shè)置一個條件來退出循環(huán)匪傍,不然就會產(chǎn)生一個無限數(shù)列出來坝咐。同樣的,把函數(shù)改成generator后析恢,我們基本上從來不會用 next() 來獲取下一個返回值墨坚,而是直接使用 for 循環(huán)來迭代:
In [38]: for?n in?fib(5):
???....: ????print(n)
???....: ????
1
1
2
3
5
In [39]:
但是用for循環(huán)調(diào)用generator時,發(fā)現(xiàn)拿不到generator的return語句的返回值映挂。如果想要拿到返回值泽篮,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
In [39]: g = fib(5)
In [40]: while?True:
???....: ????try:
???....: ????????x = next(g)
???....: ????????print("value:%d"%x) ?????
???....: ????except?StopIteration as?e:
???....: ????????print("生成器返回值:%s"%e.value)
???....: ????????break
???....: ????
value:1
value:1
value:2
value:3
value:5
生成器返回值:done
In [41]:
[if !supportLists]1.5.4?[endif]send
例子:執(zhí)行到y(tǒng)ield時柑船,gen函數(shù)作用暫時保存帽撑,返回i的值;temp接收下次c.send("python"),send發(fā)送過來的值鞍时,c.next()等價c.send(None)
In [10]:def?gen():
???....: ????i = 0
???....: ????while?i<5:
???....: ????????temp = yield?i
???....: ????????print(temp)
???....: ????????i+=1
???....:
使用next函數(shù)
In [11]: f = gen()
In [12]: next(f)
Out[12]: 0
In [13]: next(f)
None
Out[13]: 1
In [14]: next(f)
None
Out[14]: 2
In [15]: next(f)
None
Out[15]: 3
In [16]: next(f)
None
Out[16]: 4
In [17]: next(f)
None
---------------------------------------------------------------------------
StopIteration ????????????????????????????Traceback (most recent call last)
in ()
----> 1 next(f)
StopIteration:
使用__next__()方法
In [18]: f = gen()
In [19]: f.__next__()
Out[19]: 0
In [20]: f.__next__()
None
Out[20]: 1
In [21]: f.__next__()
None
Out[21]: 2
In [22]: f.__next__()
None
Out[22]: 3
In [23]: f.__next__()
None
Out[23]: 4
In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration ????????????????????????????Traceback (most recent call last)
in ()
----> 1 f.__next__()
StopIteration:
使用send
In [43]: f = gen()
In [44]: f.__next__()
Out[44]: 0
In [45]: f.send('haha')
haha
Out[45]: 1
In [46]: f.__next__()
None
Out[46]: 2
In [47]: f.send('haha')
haha
Out[47]: 3
In [48]:
[if !supportLists]1.5.5?[endif]實現(xiàn)多任務(wù)
模擬多任務(wù)(進程亏拉,線程,協(xié)程)實現(xiàn)方式之一:協(xié)程
????while True:
????????print("--1--")
????????yield None
def test2():
????while True:
????????print("--2--")
????????yield None
t1 = test1()
t2 = test2()
while True:
????t1.__next__()
????t2.__next__()
?
?
總結(jié)
生成器是這樣一個函數(shù)逆巍,它記住上一次返回時在函數(shù)體中的位置及塘。對生成器函數(shù)的第二次(或第n次)調(diào)用跳轉(zhuǎn)至該函數(shù)中間,而上次調(diào)用的所有局部變量都保持不變锐极。
生成器不僅“記住”了它數(shù)據(jù)狀態(tài)笙僚;生成器還“記住”了它在流控制構(gòu)造(在命令式編程中,這種構(gòu)造不只是數(shù)據(jù)值)中的位置灵再。
生成器的特點:
[if !supportLists]1.?[endif]節(jié)約內(nèi)存
[if !supportLists]2.?[endif]迭代到下一次的調(diào)用時肋层,所使用的參數(shù)都是第一次所保留下的,即是說翎迁,在整個所有函數(shù)調(diào)用的參數(shù)都是第一次所調(diào)用時保留的栋猖,而不是新創(chuàng)建的
[if !supportLists]1.6?[endif]迭代器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象汪榔。迭代器對象從集合的第一個元素開始訪問蒲拉,直到所有的元素被訪問完結(jié)束。迭代器只能往前不會后退揍异。
[if !supportLists]1.6.1?[endif]可迭代對象
以直接作用于for循環(huán)的數(shù)據(jù)類型有以下幾種:
一類是集合數(shù)據(jù)類型全陨,如list、 tuple 衷掷、 dict 辱姨、 set 、 str 等戚嗅;
一類是generator雨涛,包括生成器和帶 yield 的generator function枢舶。
這些可以直接作用于for循環(huán)的對象統(tǒng)稱為可迭代對象: Iterable 。
[if !supportLists]1.6.2?[endif]判斷是否可以迭代
可以使用isinstance()判斷一個對象是否是 Iterable 對象:
In [50]: from?collections import?Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [54]: isinstance((x for?x in?range(10)), Iterable)
Out[54]: True
In [55]: isinstance(100, Iterable)
Out[55]: False
而生成器不但可以作用于for循環(huán)替久,還可以被 next() 函數(shù)不斷調(diào)用并返回下一個值凉泄,直到最后拋出 StopIteration 錯誤表示無法繼續(xù)返回下一個值了。
[if !supportLists]1.6.3?[endif]迭代器
可以被next()函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器:Iterator蚯根。
可以使用isinstance()判斷一個對象是否是 Iterator 對象:
In [56]: from?collections import?Iterator
In [57]: isinstance((x for?x in?range(10)), Iterator)
Out[57]: True
In [58]: isinstance([], Iterator)
Out[58]: False
In [59]: isinstance({}, Iterator)
Out[59]: False
In [60]: isinstance('abc', Iterator)
Out[60]: False
In [61]: isinstance(100, Iterator)
Out[61]: False
[if !supportLists]1.6.4?[endif]iter()函數(shù)
生成器都是Iterator對象后众,但 list 、 dict 颅拦、 str 雖然是 Iterable 蒂誉,卻不是 Iterator 。
把list距帅、 dict 右锨、 str 等 Iterable 變成 Iterator 可以使用 iter() 函數(shù):
In [62]: isinstance(iter([]), Iterator)
Out[62]: True
In [63]: isinstance(iter('abc'), Iterator)
Out[63]: True
總結(jié)
[if !supportLists]·?[endif]凡是可作用于for循環(huán)的對象都是 Iterable 類型;
[if !supportLists]·?[endif]凡是可作用于next()函數(shù)的對象都是 Iterator 類型
[if !supportLists]·?[endif]集合數(shù)據(jù)類型如list碌秸、 dict 绍移、 str 等是 Iterable 但不是 Iterator ,不過可以通過 iter() 函數(shù)獲得一個 Iterator 對象讥电。
[if !supportLists]·?[endif]目的是在使用迭代器的時候蹂窖,減少內(nèi)存的占用。
[if !supportLists]1.7?[endif]函數(shù)
遞歸允趟、函數(shù)變量賦值恼策、參數(shù)中的函數(shù)、匿名函數(shù)潮剪、閉包、偏函數(shù)
遞歸:
函數(shù)的遞歸分唾,就是讓在函數(shù)的內(nèi)部調(diào)用函數(shù)自身的情況抗碰,這個函數(shù)就是遞歸函數(shù)。
注意:遞歸一定要有結(jié)束條件绽乔,否則就會成為一個死循環(huán)弧蝇。
函數(shù)變量賦值:
我們可以將一個函數(shù)賦值給一個變量(注意,不是調(diào)用哦)折砸,這個這個變量也會指向該函數(shù)看疗。
參數(shù)中的函數(shù):
函數(shù)作為一個對象,我們同樣可以將函數(shù)當(dāng)成一個實際參數(shù)傳遞給另一個函數(shù)進行處理睦授,這個是動態(tài)語言才用的特性两芳,也使得動態(tài)語言變得相當(dāng)?shù)撵`活和好用哦~~~
匿名函數(shù):
例如:前面我們使用的在一個函數(shù)中,參數(shù)是另外一個函數(shù)去枷。常規(guī)方式當(dāng)我們調(diào)用的時候怖辆,需要創(chuàng)建一個函數(shù)是复,昨晚調(diào)用函數(shù)的參數(shù)。但是我們也可以使用匿名函數(shù)來完成竖螃。
匿名函數(shù)的格式如下:
lambda??[參數(shù)列表]://代碼【返回值不需要寫return】
這樣寫的好處就是淑廊,代碼簡化,壞處就是降低了閱讀性特咆。
偏函數(shù):
常規(guī)函數(shù)操作中季惩,我們在函數(shù)的參數(shù)中可以添加參數(shù)的默認(rèn)值來簡化函數(shù)的操作,偏函數(shù)也可以做到這一點腻格,偏函數(shù)可以在一定程度上更加方便的管理我們的函數(shù)操作
偏函數(shù)通過內(nèi)置模塊functools的partial函數(shù)進行定義和處理画拾。
# functools.partial()函數(shù)語法結(jié)構(gòu)
新函數(shù)名稱= functools.partial(函數(shù)名稱, 默認(rèn)賦值參數(shù))
# 原始的2進制數(shù)據(jù)轉(zhuǎn)換 int("111", base=2)
~執(zhí)行結(jié)果:7
# 引入我們需要的模塊functools import functools
# 通過偏函數(shù)擴展一個新的函數(shù)int2
int2 = functools.partial(int, base=2)
# 使用新的函數(shù),新的函數(shù)等價于上面的
int("111", base=2)
int2("111")
~執(zhí)行結(jié)果:7
閉包(closure):
javascript也是解釋型語言荒叶,也有閉包的概念
[if !supportLists]1.7.1?[endif]函數(shù)引用
def?test1():
????print("--- in test1 func----")
#調(diào)用函數(shù)
test1()
#引用函數(shù)
ret = test1
print(id(ret))
print(id(test1))
#通過引用調(diào)用函數(shù)
ret()
運行結(jié)果:
--- in?test1 func----
140212571149040
140212571149040
--- in?test1 func----
[if !supportLists]1.7.2?[endif]什么是閉包
def?test(number):
????#在函數(shù)內(nèi)部再定義一個函數(shù)碾阁,并且這個函數(shù)用到了外邊函數(shù)的變量,那么將這個函數(shù)以及用到的一些變量稱之為閉包
????def?test_in(number_in):
????????print("in test_in函數(shù), number_in is %d"%number_in)
????????return?number+number_in
????#其實這里返回的就是閉包的結(jié)果
????return?test_in
#給test函數(shù)賦值些楣,這個20就是給參數(shù)number
ret = test(20)
#注意這里的100其實給參數(shù)number_in
print(ret(100))
#注意這里的200其實給參數(shù)number_in
print(ret(200))
運行結(jié)果:
intest_in函數(shù), number_in is?100
120
intest_in函數(shù), number_in is?200
220
[if !supportLists]1.7.3?[endif]看一個閉包的實際例子:
????def?line(x):
????????return?a*x + b
????return?line
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
這個例子中脂凶,函數(shù)line與變量a,b構(gòu)成閉包。在創(chuàng)建閉包的時候愁茁,我們通過line_conf的參數(shù)a,b說明了這兩個變量的取值橘券,這樣,我們就確定了函數(shù)的最終形式(y = x + 1和y = 4x + 5)因悲。我們只需要變換參數(shù)a,b厚者,就可以獲得不同的直線表達(dá)函數(shù)。由此促煮,我們可以看到邮屁,閉包也具有提高代碼可復(fù)用性的作用。
如果沒有閉包菠齿,我們需要每次創(chuàng)建直線函數(shù)的時候同時說明a,b,x佑吝。這樣,我們就需要更多的參數(shù)傳遞绳匀,也減少了代碼的可移植性芋忿。
閉包思考:
1.閉包似優(yōu)化了變量,原來需要類對象完成的工作疾棵,閉包也可以完成
2.由于閉包引用了外部函數(shù)的局部變量戈钢,則外部函數(shù)的局部變量沒有及時釋放,消耗內(nèi)存
[if !supportLists]1.8?[endif]裝飾器
裝飾器是程序開發(fā)中經(jīng)常會用到的一個功能是尔,用好了裝飾器殉了,開發(fā)效率如虎添翼,所以這也是Python面試中必問的問題嗜历,但對于好多初次接觸這個知識的人來講宣渗,這個功能有點繞抖所,自學(xué)時直接繞過去了,然后面試問到了就掛了痕囱,因為裝飾器是程序開發(fā)的基礎(chǔ)知識田轧,這個都不會,別跟人家說你會Python, 看了下面的文章鞍恢,保證你學(xué)會裝飾器傻粘。
裝飾器,功能就是在運行原來功能基礎(chǔ)上帮掉,加上一些其它功能弦悉,比如權(quán)限的驗證,比如日志的記錄等等蟆炊。不修改原來的代碼稽莉,進行功能的擴展。
比如java中的動態(tài)代理涩搓,python的注解裝飾器
其實python的裝飾器污秆,是修改了代碼。
[if !supportLists]1.8.1?[endif]裝飾器的理解
1昧甘、先明白這段代碼
####第一波 ####
def?foo():
????print('foo')
foo ????#表示是函數(shù)
foo() ??#表示執(zhí)行foo函數(shù)
####第二波 ####
def?foo():
????print('foo')
foo = lambda?x: x + 1
foo() ??#執(zhí)行下面的lambda表達(dá)式良拼,而不再是原來的foo函數(shù),因為foo這個名字被重新指向了另外一個匿名函數(shù)
?
2充边、需求來了
初創(chuàng)公司有N個業(yè)務(wù)部門庸推,1個基礎(chǔ)平臺部門,基礎(chǔ)平臺負(fù)責(zé)提供底層的功能浇冰,如:數(shù)據(jù)庫操作贬媒、redis調(diào)用、監(jiān)控API等功能肘习。業(yè)務(wù)部門使用基礎(chǔ)功能時掖蛤,只需調(diào)用基礎(chǔ)平臺提供的功能即可。如下:
###############基礎(chǔ)平臺提供的功能如下 ###############
def?f1():
????print('f1')
def?f2():
????print('f2')
def?f3():
????print('f3')
def?f4():
????print('f4')
###############業(yè)務(wù)部門A 調(diào)用基礎(chǔ)平臺提供的功能 ###############
f1()
f2()
f3()
f4()
###############業(yè)務(wù)部門B 調(diào)用基礎(chǔ)平臺提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有條不紊的進行著井厌,但是,以前基礎(chǔ)平臺的開發(fā)人員在寫代碼時候沒有關(guān)注驗證相關(guān)的問題致讥,即:基礎(chǔ)平臺的提供的功能可以被任何人使用〗銎停現(xiàn)在需要對基礎(chǔ)平臺的所有功能進行重構(gòu),為平臺提供的所有功能添加驗證機制垢袱,即:執(zhí)行功能前墓拜,先進行驗證。
老大把工作交給Low B请契,他是這么做的:
跟每個業(yè)務(wù)部門交涉咳榜,每個業(yè)務(wù)部門自己寫代碼夏醉,調(diào)用基礎(chǔ)平臺的功能之前先驗證。誒涌韩,這樣一來基礎(chǔ)平臺就不需要做任何修改了畔柔。太棒了,有充足的時間泡妹子...
當(dāng)天Low B被開除了…
老大把工作交給Low BB臣樱,他是這么做的:
###############基礎(chǔ)平臺提供的功能如下 ###############
def?f1():
????#驗證1
????#驗證2
????#驗證3
????print('f1')
def?f2():
????#驗證1
????#驗證2
????#驗證3
????print('f2')
def?f3():
????#驗證1
????#驗證2
????#驗證3
????print('f3')
def?f4():
????#驗證1
????#驗證2
????#驗證3
????print('f4')
###############業(yè)務(wù)部門不變 ###############
###業(yè)務(wù)部門A 調(diào)用基礎(chǔ)平臺提供的功能###
f1()
f2()
f3()
f4()
###業(yè)務(wù)部門B 調(diào)用基礎(chǔ)平臺提供的功能 ###
f1()
f2()
f3()
f4()
過了一周Low BB被開除了…
老大把工作交給Low BBB靶擦,他是這么做的:
只對基礎(chǔ)平臺的代碼進行重構(gòu),其他業(yè)務(wù)部門無需做任何修改
###############基礎(chǔ)平臺提供的功能如下 ###############
def?check_login():
????#驗證1
????#驗證2
????#驗證3
????pass
def?f1():
????check_login()
????print('f1')
def?f2():
????check_login()
????print('f2')
def?f3():
????check_login()
????print('f3')
def?f4():
????check_login()
????print('f4')
老大看了下Low BBB的實現(xiàn)雇毫,嘴角漏出了一絲的欣慰的笑玄捕,語重心長的跟Low BBB聊了個天:
老大說:
寫代碼要遵循開放封閉原則(OCP),雖然在這個原則是用的面向?qū)ο箝_發(fā)棚放,但是也適用于函數(shù)式編程枚粘,簡單來說,它規(guī)定已經(jīng)實現(xiàn)的功能代碼不允許被修改飘蚯,但可以被擴展馍迄,即:
[if !supportLists]·?[endif]封閉:已實現(xiàn)的功能代碼塊
[if !supportLists]·?[endif]開放:對擴展開發(fā)
[if !supportLists]·?[endif]OCP原則,即開放關(guān)閉原則
[if !supportLists]·?[endif]對已有代碼的修改關(guān)閉
[if !supportLists]·?[endif]對已有代碼的增加開放
如果將開放封閉原則應(yīng)用在上述需求中孝冒,那么就不允許在函數(shù)f1柬姚、f2、f3庄涡、f4的內(nèi)部進行修改代碼量承,老板就給了Low BBB一個實現(xiàn)方案:
def?w1(func):
????def?inner():
????????#驗證1
????????#驗證2
????????#驗證3
????????func()
????return?inner
@w1
def?f1():
????print('f1')
@w1
def?f2():
????print('f2')
@w1
def?f3():
????print('f3')
@w1
def?f4():
????print('f4')
對于上述代碼,也是僅僅對基礎(chǔ)平臺的代碼進行修改穴店,就可以實現(xiàn)在其他人調(diào)用函數(shù)f1 f2 f3 f4之前都進行【驗證】操作撕捍,并且其他業(yè)務(wù)部門無需做任何操作。
Low BBB心驚膽戰(zhàn)的問了下泣洞,這段代碼的內(nèi)部執(zhí)行原理是什么呢忧风?
老大正要生氣,突然Low BBB的手機掉到地上球凰,恰巧屏保就是Low BBB的女友照片狮腿,老大一看一緊一抖,喜笑顏開呕诉,決定和Low BBB交個好朋友缘厢。
詳細(xì)的開始講解了:
單獨以f1為例:
def?w1(func):
????def?inner():
????????#驗證1
????????#驗證2
????????#驗證3
????????func()
????return?inner
@w1
def?f1():
????print('f1')
python解釋器就會從上到下解釋代碼,步驟如下:
[if !supportLists]1.?[endif]def w1(func): ==>將w1函數(shù)加載到內(nèi)存
[if !supportLists]2.?[endif]@w1
沒錯甩挫,從表面上看解釋器僅僅會解釋這兩句代碼贴硫,因為函數(shù)在沒有被調(diào)用之前其內(nèi)部代碼不會被執(zhí)行。
從表面上看解釋器著實會執(zhí)行這兩句,但是@w1這一句代碼里卻有大文章英遭, @函數(shù)名 是python的一種語法糖间护。
上例@w1內(nèi)部會執(zhí)行一下操作:
執(zhí)行w1函數(shù)
執(zhí)行w1函數(shù) ,并將 @w1 下面的函數(shù)作為w1函數(shù)的參數(shù)挖诸,即:@w1等價于 w1(f1)?所以汁尺,內(nèi)部就會去執(zhí)行:
def?inner():?
????#驗證 1
????#驗證 2
????#驗證 3
????f1() ????# func是參數(shù),此時 func 等于 f1
return?inner#返回的 inner税灌,inner代表的是函數(shù)均函,非執(zhí)行函數(shù) ,其實就是將原來的 f1 函數(shù)塞進另外一個函數(shù)中
w1的返回值
將執(zhí)行完的w1函數(shù)返回值 賦值 給@w1下面的函數(shù)的函數(shù)名f1 即將w1的返回值再重新賦值給 f1,即:
新f1 =def?inner():?
????????????#驗證 1
????????????#驗證 2
????????????#驗證 3
原來f1()
????????return?inner
所以菱涤,以后業(yè)務(wù)部門想要執(zhí)行f1函數(shù)時苞也,就會執(zhí)行 新f1 函數(shù),在新f1 函數(shù)內(nèi)部先執(zhí)行驗證粘秆,再執(zhí)行原來的f1函數(shù)如迟,然后將原來f1 函數(shù)的返回值返回給了業(yè)務(wù)調(diào)用者。
如此一來攻走,即執(zhí)行了驗證的功能殷勘,又執(zhí)行了原來f1函數(shù)的內(nèi)容,并將原f1函數(shù)返回值 返回給業(yè)務(wù)調(diào)用著
Low BBB你明白了嗎昔搂?要是沒明白的話玲销,我晚上去你家?guī)湍憬鉀Q吧!U贤斜!
[if !supportLists]1.8.2?[endif]多個裝飾器
#定義函數(shù):完成包裹數(shù)據(jù)
def?makeBold(fn):
????def?wrapped():
????????return?""?+ fn() + ""
????return?wrapped
#定義函數(shù):完成包裹數(shù)據(jù)
def?makeItalic(fn):
????def?wrapped():
????????return?""?+ fn() + ""
????return?wrapped
def?test1():
????return?"hello world-1"
@makeItalic
def?test2():
????return?"hello world-2"
@makeBold
@makeItalic
def?test3():
????return?"hello world-3"
print(test1()))
print(test2()))
print(test3()))
運行結(jié)果:
hello world-1
hello world-2
hello world-3
[if !supportLists]1.8.3?[endif]裝飾器(decorator)功能
[if !supportLists]1.?[endif]引入日志
[if !supportLists]2.?[endif]函數(shù)執(zhí)行時間統(tǒng)計
[if !supportLists]3.?[endif]執(zhí)行函數(shù)前預(yù)備處理
[if !supportLists]4.?[endif]執(zhí)行函數(shù)后清理功能
[if !supportLists]5.?[endif]權(quán)限校驗等場景
[if !supportLists]6.?[endif]異常的處理
[if !supportLists]7.?[endif]緩存
[if !supportLists]1.8.4?[endif]裝飾器示例
[if !supportLists]1.8.4.1?[endif]例1:無參數(shù)的函數(shù)
def?timefun(func):
????def?wrappedfunc():
????????print("%s called at %s"%(func.__name__, ctime()))
????????func()
????return?wrappedfunc
@timefun
def?foo():
????print("I am foo")
foo()
sleep(2)
foo()
上面代碼理解裝飾器執(zhí)行行為可理解成
foo = timefun(foo)
#foo先作為參數(shù)賦值給func后,foo接收指向timefun返回的wrappedfunc
foo()
#調(diào)用foo(),即等價調(diào)用wrappedfunc()
#內(nèi)部函數(shù)wrappedfunc被引用,所以外部函數(shù)的func變量(自由變量)并沒有釋放
#func里保存的是原foo函數(shù)對象
[if !supportLists]1.8.4.2?[endif]例2:被裝飾的函數(shù)有參數(shù)
def?timefun(func):
????def?wrappedfunc(a, b):
????????print("%s called at %s"%(func.__name__, ctime()))
????????print(a, b)
????????func(a, b)
????return?wrappedfunc
@timefun
def?foo(a, b):
????print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
[if !supportLists]1.8.4.3?[endif]例3:被裝飾的函數(shù)有不定長參數(shù)
from?time import?ctime, sleep
def?timefun(func):
????def?wrappedfunc(*args, **kwargs):
????????print("%s called at %s"%(func.__name__, ctime()))
????????func(*args, **kwargs)
????return?wrappedfunc
@timefun
def?foo(a, b, c):
????print(a+b+c)
foo(3,5,7)
sleep(2)
foo(2,4,9)
[if !supportLists]1.8.4.4?[endif]例4:裝飾器中的return
def?timefun(func):
????def?wrappedfunc():
????????print("%s called at %s"%(func.__name__, ctime()))
????????func()
????return?wrappedfunc
@timefun
def?foo():
????print("I am foo")
@timefun
def?getInfo():
????return?'----hahah---'
foo()
sleep(2)
foo()
print(getInfo())
執(zhí)行結(jié)果:
foo called at Fri Nov ?4?21:55:35?2016
I am foo
foo called at Fri Nov ?4?21:55:37?2016
I am foo
getInfo called at Fri Nov ?4?21:55:37?2016
None
如果修改裝飾器為return func()逛裤,則運行結(jié)果:
foo called at Fri Nov ?4?21:55:57?2016
I am foo
foo called at Fri Nov ?4?21:55:59?2016
I am foo
getInfo called at Fri Nov ?4?21:55:59?2016
----hahah---
總結(jié):
[if !supportLists]·?[endif]一般情況下為了讓裝飾器更通用瘩绒,可以有return
[if !supportLists]1.8.4.5?[endif]例5:裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上,設(shè)置外部變量
#decorator2.py
from?time import?ctime, sleep
def?timefun_arg(pre="hello"):
????def?timefun(func):
????????def?wrappedfunc():
????????????print("%s called at %s %s"%(func.__name__, ctime(), pre))
????????????return?func()
????????return?wrappedfunc
????return?timefun
@timefun_arg("wangcai")
def?foo():
????print("I am foo")
@timefun_arg("python")
def?too():
????print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
可以理解為
foo()==timefun_arg("wangcai")(foo)()
[if !supportLists]1.8.4.6?[endif]例6:類裝飾器(擴展带族,非重點)
裝飾器函數(shù)其實是這樣一個接口約束锁荔,它必須接受一個callable對象作為參數(shù),然后返回一個callable對象蝙砌。在Python中一般callable對象都是函數(shù)阳堕,但也有例外。只要某個對象重寫(overide)了?__call__()?方法择克,那么這個對象就是callable的嘱丢。
class?Test():
????def?__call__(self):
????????print('call me!')
t = Test()
t() ?# call me
類裝飾器demo
????def?__init__(self, func):
????????print("---初始化---")
????????print("func name is %s"%func.__name__)
????????self.__func = func
????def?__call__(self):
????????print("---裝飾器中的功能---")
????????self.__func()
#說明:
#1.當(dāng)用Test來裝作裝飾器對test函數(shù)進行裝飾的時候,首先會創(chuàng)建Test的實例對象
#并且會把test這個函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中
#即在__init__方法中的func變量指向了test函數(shù)體
#
#2. test函數(shù)相當(dāng)于指向了用Test創(chuàng)建出來的實例對象
#
#3.當(dāng)在使用test()進行調(diào)用時祠饺,就相當(dāng)于讓這個對象(),因此會調(diào)用這個對象的__call__方法
#
#4.為了能夠在__call__方法中調(diào)用原來test指向的函數(shù)體汁政,所以在__init__方法中就需要一個實例屬性來保存這個函數(shù)體的引用
#所以才有了self.__func = func這句代碼道偷,從而在調(diào)用__call__方法中能夠調(diào)用到test之前的函數(shù)體
@Test
????print("----test---")
test()
showpy()#如果把這句話注釋缀旁,重新運行程序,依然會看到"--初始化--"
運行結(jié)果如下:
---初始化---
func name is?test
---裝飾器中的功能---
----test---
[if !supportLists]1.9?[endif]python是動態(tài)語言
[if !supportLists]1.9.1?[endif]動態(tài)語言的定義
動態(tài)編程語言是高級程序設(shè)計語言的一個類別勺鸦,在計算機科學(xué)領(lǐng)域已被廣泛應(yīng)用并巍。它是一類在運行時可以改變其結(jié)構(gòu)的語言:例如新的函數(shù)、對象换途、甚至代碼可以被引進懊渡,已有的函數(shù)可以被刪除或是其他結(jié)構(gòu)上的變化。動態(tài)語言目前非常具有活力军拟。例如JavaScript便是一個動態(tài)語言剃执,除此之外如 PHP 、 Ruby 懈息、 Python 等也都屬于動態(tài)語言肾档,而 C 、 C++ 等語言則不屬于動態(tài)語言辫继。 ----來自維基百科
[if !supportLists]1.9.2?[endif]運行的過程中給對象綁定(添加)屬性
>>> class?Person(object):
????def?__init__(self, name = None, age = None):
????????self.name = name
????????self.age = age
>>> P = Person("小明", "24")
>>>
在這里怒见,我們定義了1個類Person,在這個類里姑宽,定義了兩個初始屬性name和age遣耍,但是人還有性別啊炮车!如果這個類不是你寫的是不是你會嘗試訪問性別這個屬性呢舵变?
>>> P.sex = "male"
>>> P.sex
'male'
>>>
這時候就發(fā)現(xiàn)問題了,我們定義的類里面沒有sex這個屬性笆狙棋傍!怎么回事呢? 這就是動態(tài)語言的魅力和坑难审! 這里 實際上就是 動態(tài)給實例綁定屬性瘫拣!
[if !supportLists]1.9.3?[endif]運行的過程中給類綁定(添加)屬性
>>> P1 = Person("小麗", "25")
>>> P1.sex
Traceback (most recent call last):
??File "", line 1, in?
????P1.sex
AttributeError: Person instance has no attribute 'sex'
>>>
我們嘗試打印P1.sex,發(fā)現(xiàn)報錯告喊,P1沒有sex這個屬性麸拄!---- 給P這個實例綁定屬性對P1這個實例不起作用! 那我們要給所有的Person的實例加上 sex屬性怎么辦呢黔姜? 答案就是直接給Person綁定屬性拢切!
>>>> Person.sex = None?#給類Person添加一個屬性
>>> P1 = Person("小麗", "25")
>>> print(P1.sex) #如果P1這個實例對象中沒有sex屬性的話,那么就會訪問它的類屬性
None?#可以看到?jīng)]有出現(xiàn)異常
>>>
[if !supportLists]1.9.4?[endif]運行的過程中給類綁定(添加)方法
我們直接給Person綁定sex這個屬性秆吵,重新實例化P1后淮椰,P1就有sex這個屬性了! 那么function呢?怎么綁定主穗?
>>> class?Person(object):
????def?__init__(self, name = None, age = None):
????????self.name = name
????????self.age = age
????def?eat(self):
????????print("eat food")
>>> def?run(self, speed):
????print("%s在移動, 速度是 %d km/h"%(self.name, speed))
>>> P = Person("老王", 24)
>>> P.eat()
eat food
>>>
>>> P.run()
Traceback (most recent call last):
??File "", line 1, in?
????P.run()
AttributeError: Person instance has no attribute 'run'
>>>
>>>
>>> import?types
>>> P.run = types.MethodType(run, P)
>>> P.run(180)
老王在移動,速度是 180?km/h
既然給類添加方法泻拦,是使用類名.方法名 = xxxx,那么給對象添加一個方法也是類似的對象.方法名 = xxxx
完整的代碼如下:
import?types
#定義了一個類
class?Person(object):
????num = 0
????def?__init__(self, name = None, age = None):
????????self.name = name
????????self.age = age
????def?eat(self):
????????print("eat food")
#定義一個實例方法
def?run(self, speed):
????print("%s在移動, 速度是 %d km/h"%(self.name, speed))
#定義一個類方法
@classmethod
def?testClass(cls):
????cls.num = 100
#定義一個靜態(tài)方法
@staticmethod
def?testStatic():
????print("---static method----")
#創(chuàng)建一個實例對象
P = Person("老王", 24)
#調(diào)用在class中的方法
P.eat()
#給這個對象添加實例方法
P.run = types.MethodType(run, P)
#調(diào)用實例方法
P.run(180)
#給Person類綁定類方法
Person.testClass = testClass
#調(diào)用類方法
print(Person.num)
Person.testClass()
print(Person.num)
#給Person類綁定靜態(tài)方法
Person.testStatic = testStatic
#調(diào)用靜態(tài)方法
Person.testStatic()
[if !supportLists]1.9.5?[endif]運行的過程中刪除屬性忽媒、方法
刪除的方法:
[if !supportLists]1.?[endif]del對象.屬性名
[if !supportLists]2.?[endif]delattr(對象, "屬性名")
通過以上例子可以得出一個結(jié)論:相對于動態(tài)語言争拐,靜態(tài)語言具有嚴(yán)謹(jǐn)性!所以晦雨,玩動態(tài)語言的時候架曹,小心動態(tài)的坑!
那么怎么避免這種情況呢闹瞧?請使用__slots__绑雄,
[if !supportLists]1.9.6?[endif]__slots__
現(xiàn)在我們終于明白了,動態(tài)語言與靜態(tài)語言的不同
動態(tài)語言:可以在運行的過程中夹抗,修改代碼
靜態(tài)語言:編譯時已經(jīng)確定好代碼绳慎,運行過程中不能修改
如果我們想要限制實例的屬性怎么辦?比如漠烧,只允許對Person實例添加name和age屬性杏愤。只能限定實例對象的添加屬性和方法
為了達(dá)到限制的目的,Python允許在定義class的時候已脓,定義一個特殊的__slots__變量珊楼,來限制該class實例能添加的屬性:
????__slots__ = ("name", "age")
>>> P = Person()
>>> P.name = "老王"
>>> P.age = 20
>>> P.score = 100
Traceback (most recent call last):
??File "", line 1, in?
AttributeError: Person instance has no attribute 'score'
>>>
注意:
[if !supportLists]·?[endif]使用__slots__要注意,__slots__定義的屬性僅對當(dāng)前類實例起作用度液,對繼承的子類是不起作用的
In [67]: class?Test(Person):
????...: ????pass
????...:
In [68]: t = Test()
In [69]: t.score = 100
[if !supportLists]1.10?[endif]元類(了解)
[if !supportLists]1.10.1?[endif]類也是對象
在大多數(shù)編程語言中厕宗,類就是一組用來描述如何生成一個對象的代碼段。在Python中這一點仍然成立:
>>> class?ObjectCreator(object):
… ??????pass
…
>>> my_object = ObjectCreator()
>>> print?my_object
<__main__.ObjectCreator object at 0x8974f2c>
但是,Python中的類還遠(yuǎn)不止如此。類同樣也是一種對象苞尝。是的,沒錯佑惠,就是對象。只要你使用關(guān)鍵字class齐疙,Python解釋器在執(zhí)行的時候就會創(chuàng)建一個對象膜楷。
下面的代碼段:
>>> class?ObjectCreator(object):
… ??????pass
…
將在內(nèi)存中創(chuàng)建一個對象,名字就是ObjectCreator贞奋。這個對象(類對象ObjectCreator)擁有創(chuàng)建對象(實例對象)的能力赌厅。但是,它的本質(zhì)仍然是一個對象轿塔,于是乎你可以對它做如下的操作:
[if !supportLists]1.?[endif]你可以將它賦值給一個變量
[if !supportLists]2.?[endif]你可以拷貝它
[if !supportLists]3.?[endif]你可以為它增加屬性
[if !supportLists]4.?[endif]你可以將它作為函數(shù)參數(shù)進行傳遞
下面是示例:
>>> print ObjectCreator ????#你可以打印一個類特愿,因為它其實也是一個對象
>>> def echo(o):
… ??????print o
…
>>> echo(ObjectCreator) ????????????????#你可以將類做為參數(shù)傳給函數(shù)
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #你可以為類增加屬性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator #你可以將類賦值給一個變量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
[if !supportLists]1.10.2?[endif]動態(tài)地創(chuàng)建類
因為類也是對象仲墨,你可以在運行時動態(tài)的創(chuàng)建它們,就像其他任何對象一樣洽议。首先宗收,你可以在函數(shù)中創(chuàng)建類,使用class關(guān)鍵字即可亚兄。
>>> def choose_class(name):
… ??????if name == 'foo':
… ??????????class Foo(object):
… ??????????????pass
… ??????????return Foo ????#返回的是類,不是類的實例
… ??????else:
… ??????????class Bar(object):
… ??????????????pass
… ??????????return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass ?????????????#函數(shù)返回的是類采驻,不是類的實例
>>> print MyClass() ???????????#你可以通過這個類創(chuàng)建類實例审胚,也就是對象
<__main__.Foo object at 0x89c6d4c>
但這還不夠動態(tài),因為你仍然需要自己編寫整個類的代碼礼旅。由于類也是對象膳叨,所以它們必須是通過什么東西來生成的才對。當(dāng)你使用class關(guān)鍵字時痘系,Python解釋器自動創(chuàng)建這個對象菲嘴。但就和Python中的大多數(shù)事情一樣,Python仍然提供給你手動處理的方法汰翠。
還記得內(nèi)建函數(shù)type嗎龄坪?這個古老但強大的函數(shù)能夠讓你知道一個對象的類型是什么,就像這樣:
>>> print type(1) #數(shù)值的類型
>>> print type("1") #字符串的類型
>>> print type(ObjectCreator()) #實例對象的類型
>>> print type(ObjectCreator) #類的類型
仔細(xì)觀察上面的運行結(jié)果复唤,發(fā)現(xiàn)使用type對ObjectCreator查看類型是健田,答案為type, 是不是有些驚訝佛纫。妓局。〕视睿看下面
[if !supportLists]1.10.3?[endif]使用type創(chuàng)建類
type還有一種完全不同的功能好爬,動態(tài)的創(chuàng)建類。
type可以接受一個類的描述作為參數(shù)甥啄,然后返回一個類存炮。(要知道,根據(jù)傳入?yún)?shù)的不同型豁,同一個函數(shù)擁有兩種完全不同的用法是一件很傻的事情僵蛛,但這在Python中是為了保持向后兼容性)
type可以像這樣工作:
type(類名, 由父類名稱組成的元組(針對繼承的情況,可以為空)迎变,包含屬性的字典(名稱和值))
比如下面的代碼:
In [2]: class?Test:?#定義了一個Test類
???...: ????pass
???...:
In [3]: Test() #創(chuàng)建了一個Test類的實例對象
Out[3]: <__main__.Test at 0x10d3f8438>
可以手動像這樣創(chuàng)建:
Test2 = type("Test2",(),{}) #定了一個Test2類
In [5]: Test2() #創(chuàng)建了一個Test2類的實例對象
Out[5]: <__main__.Test2 at 0x10d406b38>
我們使用"Test2"作為類名充尉,并且也可以把它當(dāng)做一個變量來作為類的引用。類和變量是不同的衣形,這里沒有任何理由把事情弄的復(fù)雜驼侠。即type函數(shù)中第1個實參姿鸿,也可以叫做其他的名字,這個名字表示類的名字
In [23]: MyDogClass = type('MyDog', (), {})
In [24]: print MyDogClass
使用help來測試這2個類
In [10]: help(Test) #用help查看Test類
Help on class Test in module __main__:
class Test(builtins.object)
?| ?Data descriptors defined here:
?|
?| ?__dict__
?| ?????dictionary for instance variables (if defined)
?|
?| ?__weakref__
?| ?????list of weak references to the object (if defined)
In [8]: help(Test2) #用help查看Test2類
Help on class Test2 in module __main__:
class Test2(builtins.object)
?| ?Data descriptors defined here:
?|
?| ?__dict__
?| ?????dictionary for instance variables (if defined)
?|
?| ?__weakref__
?| ?????list of weak references to the object (if defined)
[if !supportLists]1.10.4?[endif]使用type創(chuàng)建帶有屬性的類
type接受一個字典來為類定義屬性倒源,因此
>>> Foo = type('Foo', (), {'bar':True})
可以翻譯為:
>>> class?Foo(object):
… ??????bar = True
并且可以將Foo當(dāng)成一個普通的類一樣使用:
>>> print Foo
>>> print Foo.bar
True
>>> f = Foo()
>>> print f
<__main__.Foo object at 0x8a9b84c>
>>> print f.bar
True
當(dāng)然苛预,你可以向這個類繼承,所以笋熬,如下的代碼:
>>> class?FooChild(Foo):
… ??????pass
就可以寫成:
>>> FooChild = type('FooChild', (Foo,),{})
>>> print FooChild
>>> print FooChild.bar ??# bar屬性是由Foo繼承而來
True
注意:
[if !supportLists]·?[endif]type的第2個參數(shù)热某,元組中是父類的名字,而不是字符串
[if !supportLists]·?[endif]添加的屬性是類屬性胳螟,并不是實例屬性
[if !supportLists]1.10.5?[endif]使用type創(chuàng)建帶有方法的類
最終你會希望為你的類增加方法昔馋。只需要定義一個有著恰當(dāng)簽名的函數(shù)并將其作為屬性賦值就可以了。
添加實例方法
In [46]: def?echo_bar(self):?#定義了一個普通的函數(shù)
????...: ????print(self.bar)
????...:
In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) #讓FooChild類中的echo_bar屬性糖耸,指向了上面定義的函數(shù)
In [48]: hasattr(Foo, 'echo_bar') #判斷Foo類中秘遏,是否有echo_bar這個屬性
Out[48]: False
In [49]:
In [49]: hasattr(FooChild, 'echo_bar') #判斷FooChild類中,是否有echo_bar這個屬性
Out[49]: True
In [50]: my_foo = FooChild()
In [51]: my_foo.echo_bar()
True
添加靜態(tài)方法
In [36]: @staticmethod
????...: def?testStatic():
????...: ????print("static method ....")
????...:
In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":
????...: testStatic})
In [38]: fooclid = Foochild()
In [39]: fooclid.testStatic
Out[39]:
In [40]: fooclid.testStatic()
static method ....
In [41]: fooclid.echo_bar()
True
添加類方法
In [42]: @classmethod
????...: def?testClass(cls):
????...: ????print(cls.bar)
????...:
In [43]:
In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":
????...: testStatic, "testClass":testClass})
In [44]:
In [44]: fooclid = Foochild()
In [45]: fooclid.testClass()
True
你可以看到嘉竟,在Python中邦危,類也是對象,你可以動態(tài)的創(chuàng)建類舍扰。這就是當(dāng)你使用關(guān)鍵字class時Python在幕后做的事情倦蚪,而這就是通過元類來實現(xiàn)的。
[if !supportLists]1.10.6?[endif]到底什么是元類
元類就是用來創(chuàng)建類的“東西”妥粟。你創(chuàng)建類就是為了創(chuàng)建類的實例對象审丘,不是嗎?但是我們已經(jīng)學(xué)習(xí)到了Python中的類也是對象勾给。
元類就是用來創(chuàng)建這些類(對象)的滩报,元類就是類的類,你可以這樣理解為:
MyClass = MetaClass() #使用元類創(chuàng)建出一個對象播急,這個對象稱為“類”
MyObject = MyClass() #使用“類”來創(chuàng)建出實例對象
你已經(jīng)看到了type可以讓你像這樣做:
MyClass = type('MyClass', (), {})
這是因為函數(shù)type實際上是一個元類脓钾。type就是Python在背后用來創(chuàng)建所有類的元類。現(xiàn)在你想知道那為什么type會全部采用小寫形式而不是Type呢桩警?好吧可训,我猜這是為了和str保持一致性,str是用來創(chuàng)建字符串對象的類捶枢,而int是用來創(chuàng)建整數(shù)對象的類握截。type就是創(chuàng)建類對象的類。你可以通過檢查__class__屬性來看到這一點烂叔。Python中所有的東西谨胞,注意,我是指所有的東西——都是對象蒜鸡。這包括整數(shù)胯努、字符串牢裳、函數(shù)以及類。它們?nèi)慷际菍ο笠杜妫宜鼈兌际菑囊粋€類創(chuàng)建而來蒲讯,這個類就是type。
>>> age = 35
>>> age.__class__
>>> name = 'bob'
>>> name.__class__
>>> def foo(): pass
>>>foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
現(xiàn)在灰署,對于任何一個__class__的__class__屬性又是什么呢判帮?
>>> a.__class__.__class__
>>> age.__class__.__class__
>>> foo.__class__.__class__
>>> b.__class__.__class__
因此,元類就是創(chuàng)建類這種對象的東西溉箕。type就是Python的內(nèi)建元類脊另,當(dāng)然了,你也可以創(chuàng)建自己的元類约巷。
[if !supportLists]1.10.7?[endif]__metaclass__屬性
你可以在定義一個類的時候為其添加__metaclass__屬性。
class?Foo(object):
????__metaclass__ = something…
...省略...
如果你這么做了旱捧,Python就會用元類來創(chuàng)建類Foo独郎。小心點,這里面有些技巧枚赡。你首先寫下class Foo(object)氓癌,但是類Foo還沒有在內(nèi)存中創(chuàng)建。Python會在類的定義中尋找__metaclass__屬性贫橙,如果找到了贪婉,Python就會用它來創(chuàng)建類Foo,如果沒有找到卢肃,就會用內(nèi)建的type來創(chuàng)建這個類疲迂。把下面這段話反復(fù)讀幾次。當(dāng)你寫如下代碼時 :
class?Foo(Bar):
????pass
Python做了如下的操作:
[if !supportLists]1.?[endif]Foo中有__metaclass__這個屬性嗎莫湘?如果是尤蒿,Python會通過__metaclass__創(chuàng)建一個名字為Foo的類(對象)
[if !supportLists]2.?[endif]如果Python沒有找到__metaclass__,它會繼續(xù)在Bar(父類)中尋找__metaclass__屬性幅垮,并嘗試做和前面同樣的操作腰池。
[if !supportLists]3.?[endif]如果Python在任何父類中都找不到__metaclass__,它就會在模塊層次中去尋找__metaclass__忙芒,并嘗試做同樣的操作示弓。
[if !supportLists]4.?[endif]如果還是找不到__metaclass__,Python就會用內(nèi)置的type來創(chuàng)建這個類對象。
現(xiàn)在的問題就是呵萨,你可以在__metaclass__中放置些什么代碼呢奏属?答案就是:可以創(chuàng)建一個類的東西。那么什么可以用來創(chuàng)建一個類呢甘桑?type拍皮,或者任何使用到type或者子類化type的東東都可以歹叮。
[if !supportLists]1.10.8?[endif]自定義元類
元類的主要目的就是為了當(dāng)創(chuàng)建類時能夠自動地改變類。通常铆帽,你會為API做這樣的事情咆耿,你希望可以創(chuàng)建符合當(dāng)前上下文的類。
假想一個很傻的例子爹橱,你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式萨螺。有好幾種方法可以辦到,但其中一種就是通過在模塊級別設(shè)定__metaclass__愧驱。采用這種方法慰技,這個模塊中的所有類都會通過這個元類來創(chuàng)建,我們只需要告訴元類把所有的屬性都改成大寫形式就萬事大吉了组砚。
幸運的是吻商,__metaclass__實際上可以被任意調(diào)用,它并不需要是一個正式的類糟红。所以艾帐,我們這里就先以一個簡單的函數(shù)作為例子開始。
python2中
#-*- coding:utf-8 -*-
def?upper_attr(future_class_name, future_class_parents, future_class_attr):
????#遍歷屬性字典盆偿,把不是__開頭的屬性名字變?yōu)榇髮?/p>
????newAttr = {}
????for?name,value in?future_class_attr.items():
????????if?not?name.startswith("__"):
????????????newAttr[name.upper()] = value
????#調(diào)用type來創(chuàng)建一個類
????return?type(future_class_name, future_class_parents, newAttr)
class?Foo(object):
????__metaclass__ = upper_attr #設(shè)置Foo類的元類為upper_attr
????bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
python3中
#-*- coding:utf-8 -*-
def?upper_attr(future_class_name, future_class_parents, future_class_attr):
????#遍歷屬性字典柒爸,把不是__開頭的屬性名字變?yōu)榇髮?/p>
????newAttr = {}
????for?name,value in?future_class_attr.items():
????????if?not?name.startswith("__"):
????????????newAttr[name.upper()] = value
????#調(diào)用type來創(chuàng)建一個類
????return?type(future_class_name, future_class_parents, newAttr)
class?Foo(object, metaclass=upper_attr):
????bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
現(xiàn)在讓我們再做一次,這一次用一個真正的class來當(dāng)做元類事扭。
#coding=utf-8
class?UpperAttrMetaClass(type):
????# __new__是在__init__之前被調(diào)用的特殊方法
????# __new__是用來創(chuàng)建對象并返回之的方法
????#而__init__只是用來將傳入的參數(shù)初始化給對象
????#你很少用到__new__捎稚,除非你希望能夠控制對象的創(chuàng)建
????#這里,創(chuàng)建的對象是類求橄,我們希望能夠自定義它今野,所以我們這里改寫__new__
????#如果你希望的話,你也可以在__init__中做些事情
????#還有一些高級的用法會涉及到改寫__call__特殊方法谈撒,但是我們這里不用
????def?__new__(cls, future_class_name, future_class_parents, future_class_attr):
????????#遍歷屬性字典腥泥,把不是__開頭的屬性名字變?yōu)榇髮?/p>
????????newAttr = {}
????????for?name,value in?future_class_attr.items():
????????????if?not?name.startswith("__"):
????????????????newAttr[name.upper()] = value
????????#方法1:通過'type'來做類對象的創(chuàng)建
????????# return type(future_class_name, future_class_parents, newAttr)
????????#方法2:復(fù)用type.__new__方法
????????#這就是基本的OOP編程,沒什么魔法
????????# return type.__new__(cls, future_class_name, future_class_parents, newAttr)
????????#方法3:使用super方法
????????return?super(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)
#python2的用法
class?Foo(object):
????__metaclass__ = UpperAttrMetaClass
????bar = 'bip'
# python3的用法
# class Foo(object, metaclass = UpperAttrMetaClass):
# ????bar = 'bip'
print(hasattr(Foo, 'bar'))
#輸出: False
print(hasattr(Foo, 'BAR'))
#輸出:True
f = Foo()
print(f.BAR)
#輸出:'bip'
就是這樣啃匿,除此之外蛔外,關(guān)于元類真的沒有別的可說的了。但就元類本身而言溯乒,它們其實是很簡單的:
[if !supportLists]1.?[endif]攔截類的創(chuàng)建
[if !supportLists]2.?[endif]修改類
[if !supportLists]3.?[endif]返回修改之后的類
[if !supportLists]1.10.9?[endif]究竟為什么要使用元類夹厌?
現(xiàn)在回到我們的大主題上來,究竟是為什么你會去使用這樣一種容易出錯且晦澀的特性裆悄?好吧矛纹,一般來說,你根本就用不上它:
“元類就是深度的魔法光稼,99%的用戶應(yīng)該根本不必為此操心或南。如果你想搞清楚究竟是否需要用到元類孩等,那么你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什么采够,而且根本不需要解釋為什么要用元類肄方。” —— Python界的領(lǐng)袖 Tim Peters
[if !supportLists]1.11?[endif]垃圾回收(了解)
[if !supportLists]1.11.1?[endif]小整數(shù)對象池
整數(shù)在程序中的使用非常廣泛蹬癌,Python為了優(yōu)化速度权她,使用了小整數(shù)對象池, 避免為整數(shù)頻繁申請和銷毀內(nèi)存空間逝薪。
同理步清,單個字母也是這樣的。
但是當(dāng)定義2個相同的字符串時虏肾,引用計數(shù)為0尼啡,觸發(fā)垃圾回收
[if !supportLists]1.11.2?[endif]大整數(shù)對象池
每一個大整數(shù),均創(chuàng)建一個新的對象询微。
[if !supportLists]1.11.3?[endif]intern機制
a1 = "HelloWorld"
a2 = "HelloWorld"
a3 = "HelloWorld"
a4 = "HelloWorld"
a5 = "HelloWorld"
a6 = "HelloWorld"
a7 = "HelloWorld"
a8 = "HelloWorld"
a9 = "HelloWorld"
python會不會創(chuàng)建9個對象呢?在內(nèi)存中會不會開辟9個”HelloWorld”的內(nèi)存空間呢狂巢? 想一下撑毛,如果是這樣的話,我們寫10000個對象唧领,比如a1=”HelloWorld”…..a1000=”HelloWorld”藻雌, 那他豈不是開辟了1000個”HelloWorld”所占的內(nèi)存空間了呢?如果真這樣斩个,內(nèi)存不就爆了嗎胯杭?所以python中有這樣一個機制——intern機制,讓他只占用一個”HelloWorld”所占的內(nèi)存空間受啥∽龈觯靠引用計數(shù)去維護何時釋放。
總結(jié)
[if !supportLists]·?[endif]小整數(shù)[-5,257)共用對象滚局,常駐內(nèi)存
[if !supportLists]·?[endif]單個字符共用對象居暖,常駐內(nèi)存
[if !supportLists]·?[endif]單個單詞,不可修改藤肢,默認(rèn)開啟intern機制太闺,共用對象,引用計數(shù)為0嘁圈,則銷毀?
字符串(含有空格)省骂,不可修改蟀淮,沒開啟intern機制,不共用對象钞澳,引用計數(shù)為0怠惶,銷毀?
大整數(shù)不共用內(nèi)存,引用計數(shù)為0略贮,銷毀
數(shù)值類型和字符串類型在Python中都是不可變的甚疟,這意味著你無法修改這個對象的值,每次對變量的修改逃延,實際上是創(chuàng)建一個新的對象?
[if !supportLists]1.11.4?[endif]Garbage collection(GC垃圾回收)
現(xiàn)在的高級語言如java政冻,c#等万哪,都采用了垃圾收集機制,而不再是c,c++里用戶自己管理維護內(nèi)存的方式拉队。自己管理內(nèi)存極其自由,可以任意申請內(nèi)存蝇棉,但如同一把雙刃劍喜最,為大量內(nèi)存泄露,懸空指針等bug埋下隱患料按。 對于一個字符串奄侠、列表、類甚至數(shù)值都是對象载矿,且定位簡單易用的語言垄潮,自然不會讓用戶去處理如何分配回收內(nèi)存的問題。 python里也同java一樣采用了垃圾收集機制闷盔,不過不一樣的是: python采用的是引用計數(shù)機制為主弯洗,標(biāo)記-清除和分代收集兩種機制為輔的策略
引用計數(shù)機制:
python里每一個東西都是對象,它們的核心就是一個結(jié)構(gòu)體:PyObject
typedef?struct_object {
????int?ob_refcnt;
????struct_typeobject *ob_type;
} PyObject;
PyObject是每個對象必有的內(nèi)容逢勾,其中ob_refcnt就是做為引用計數(shù)牡整。當(dāng)一個對象有新的引用時,它的ob_refcnt就會增加溺拱,當(dāng)引用它的對象被刪除逃贝,它的ob_refcnt就會減少
#define?Py_INCREF(op) ??((op)->ob_refcnt++) //增加計數(shù)
#define?Py_DECREF(op) \ //減少計數(shù)
????if?(--(op)->ob_refcnt != 0) \
????????; \
????else?\
????????__Py_Dealloc((PyObject *)(op))
當(dāng)引用計數(shù)為0時,該對象生命就結(jié)束了迫摔。
引用計數(shù)機制的優(yōu)點:
[if !supportLists]·?[endif]簡單
[if !supportLists]·?[endif]實時性:一旦沒有引用秋泳,內(nèi)存就直接釋放了。不用像其他機制等到特定時機攒菠。實時性還帶來一個好處:處理回收內(nèi)存的時間分?jǐn)偟搅似綍r迫皱。
引用計數(shù)機制的缺點:
[if !supportLists]·?[endif]維護引用計數(shù)消耗資源
[if !supportLists]·?[endif]循環(huán)引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1與list2相互引用,如果不存在其他對象對它們的引用,list1與list2的引用計數(shù)也仍然為1卓起,所占用的內(nèi)存永遠(yuǎn)無法被回收和敬,這將是致命的。 對于如今的強大硬件戏阅,缺點1尚可接受昼弟,但是循環(huán)引用導(dǎo)致內(nèi)存泄露,注定python還將引入新的回收機制奕筐。(標(biāo)記清除和分代收集)
[if !supportLists]1.11.5?[endif]話說Ruby與 Python 垃圾回收
英文原文:?visualizing garbage collection in ruby and python
[if !supportLists]1.11.5.1?[endif]應(yīng)用程序那顆躍動的心
GC系統(tǒng)所承擔(dān)的工作遠(yuǎn)比"垃圾回收"多得多舱痘。實際上,它們負(fù)責(zé)三個重要任務(wù)离赫。它們
[if !supportLists]·?[endif]為新生成的對象分配內(nèi)存
[if !supportLists]·?[endif]識別那些垃圾對象芭逝,并且
[if !supportLists]·?[endif]從垃圾對象那回收內(nèi)存。
如果將應(yīng)用程序比作人的身體:所有你所寫的那些優(yōu)雅的代碼渊胸,業(yè)務(wù)邏輯旬盯,算法,應(yīng)該就是大腦翎猛。以此類推胖翰,垃圾回收機制應(yīng)該是那個身體器官呢?(我從RuPy聽眾那聽到了不少有趣的答案:腰子切厘、白血球 :) )
我認(rèn)為垃圾回收就是應(yīng)用程序那顆躍動的心萨咳。像心臟為身體其他器官提供血液和營養(yǎng)物那樣,垃圾回收器為你的應(yīng)該程序提供內(nèi)存和對象疫稿。如果心臟停跳某弦,過不了幾秒鐘人就完了。如果垃圾回收器停止工作或運行遲緩,像動脈阻塞,你的應(yīng)用程序效率也會下降而克,直至最終死掉。
[if !supportLists]1.11.5.2?[endif]一個簡單的例子
運用實例一貫有助于理論的理解怔毛。下面是一個簡單類员萍,分別用Python和Ruby寫成,我們今天就以此為例:
順便提一句拣度,兩種語言的代碼竟能如此相像:Ruby和 Python 在表達(dá)同一事物上真的只是略有不同碎绎。但是在這兩種語言的內(nèi)部實現(xiàn)上是否也如此相似呢?
順便提一句抗果,兩種語言的代碼竟能如此相像:Ruby和 Python 在表達(dá)同一事物上真的只是略有不同筋帖。但是在這兩種語言的內(nèi)部實現(xiàn)上是否也如此相似呢?
[if !supportLists]1.11.5.3?[endif]Ruby的對象分配
當(dāng)我們執(zhí)行上面的Node.new(1)時冤馏,Ruby到底做了什么日麸?Ruby是如何為我們創(chuàng)建新的對象的呢? 出乎意料的是它做的非常少。實際上代箭,早在代碼開始執(zhí)行前墩划,Ruby就提前創(chuàng)建了成百上千個對象,并把它們串在鏈表上嗡综,名曰:可用列表乙帮。下圖所示為可用列表的概念圖:
想象一下每個白色方格上都標(biāo)著一個"未使用預(yù)創(chuàng)建對象"。當(dāng)我們調(diào)用 Node.new ,Ruby只需取一個預(yù)創(chuàng)建對象給我們使用即可:
上圖中左側(cè)灰格表示我們代碼中使用的當(dāng)前對象极景,同時其他白格是未使用對象察净。(請注意:無疑我的示意圖是對實際的簡化。實際上盼樟,Ruby會用另一個對象來裝載字符串"ABC",另一個對象裝載Node類定義氢卡,還有一個對象裝載了代碼中分析出的抽象語法樹,等等)
如果我們再次調(diào)用Node.new恤批,Ruby將遞給我們另一個對象:
這個簡單的用鏈表來預(yù)分配對象的算法已經(jīng)發(fā)明了超過50年异吻,而發(fā)明人這是赫赫有名的計算機科學(xué)家John McCarthy,一開始是用Lisp實現(xiàn)的喜庞。Lisp不僅是最早的函數(shù)式編程語言诀浪,在計算機科學(xué)領(lǐng)域也有許多創(chuàng)舉。其一就是利用垃圾回收機制自動化進行程序內(nèi)存管理的概念延都。
標(biāo)準(zhǔn)版的Ruby雷猪,也就是眾所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法與McCarthy在1960年的實現(xiàn)方式很類似。無論好壞晰房,Ruby的垃圾回收機制已經(jīng)53歲高齡了求摇。像Lisp一樣,Ruby預(yù)先創(chuàng)建一些對象殊者,然后在你分配新對象或者變量的時候供你使用与境。
[if !supportLists]1.11.5.4?[endif]Python的對象分配
我們已經(jīng)了解了Ruby預(yù)先創(chuàng)建對象并將它們存放在可用列表中。那Python又怎么樣呢猖吴?
盡管由于許多原因Python也使用可用列表(用來回收一些特定對象比如 list)摔刁,但在為新對象和變量分配內(nèi)存的方面Python和Ruby是不同的。
例如我們用Pyhon來創(chuàng)建一個Node對象:
與Ruby不同海蔽,當(dāng)創(chuàng)建對象時Python立即向操作系統(tǒng)請求內(nèi)存共屈。(Python實際上實現(xiàn)了一套自己的內(nèi)存分配系統(tǒng),在操作系統(tǒng)堆之上提供了一個抽象層党窜。但是我今天不展開說了拗引。)
當(dāng)我們創(chuàng)建第二個對象的時候,再次像OS請求內(nèi)存:
看起來夠簡單吧幌衣,在我們創(chuàng)建對象的時候矾削,Python會花些時間為我們找到并分配內(nèi)存。
[if !supportLists]1.11.5.5?[endif]Ruby開發(fā)者住在凌亂的房間里
Ruby把無用的對象留在內(nèi)存里,直到下一次GC執(zhí)行
回過來看Ruby怔软。隨著我們創(chuàng)建越來越多的對象垦细,Ruby會持續(xù)尋可用列表里取預(yù)創(chuàng)建對象給我們。因此挡逼,可用列表會逐漸變短:
...然后更短:
請注意我一直在為變量n1賦新值括改,Ruby把舊值留在原處。"ABC","JKL"和"MNO"三個Node實例還滯留在內(nèi)存中家坎。Ruby不會立即清除代碼中不再使用的舊對象嘱能!Ruby開發(fā)者們就像是住在一間凌亂的房間,地板上摞著衣服虱疏,要么洗碗池里都是臟盤子惹骂。作為一個Ruby程序員,無用的垃圾對象會一直環(huán)繞著你做瞪。
[if !supportLists]1.11.5.6?[endif]Python開發(fā)者住在衛(wèi)生之家庭
用完的垃圾對象會立即被Python打掃干凈
Python與Ruby的垃圾回收機制頗為不同对粪。讓我們回到前面提到的三個Python Node對象:
在內(nèi)部,創(chuàng)建一個對象時装蓬,Python總是在對象的C結(jié)構(gòu)體里保存一個整數(shù)著拭,稱為?引用數(shù)。期初牍帚,Python將這個值設(shè)置為1:
值為1說明分別有個一個指針指向或是引用這三個對象儡遮。假如我們現(xiàn)在創(chuàng)建一個新的Node實例,JKL:
與之前一樣暗赶,Python設(shè)置JKL的引用數(shù)為1鄙币。然而,請注意由于我們改變了n1指向了JKL蹂随,不再指向ABC十嘿,Python就把ABC的引用數(shù)置為0了。 此刻岳锁,Python垃圾回收器立刻挺身而出绩衷!每當(dāng)對象的引用數(shù)減為0,Python立即將其釋放浸锨,把內(nèi)存還給操作系統(tǒng):
上面Python回收了ABC Node實例使用的內(nèi)存。記住版姑,Ruby棄舊對象原地于不顧柱搜,也不釋放它們的內(nèi)存。
Python的這種垃圾回收算法被稱為引用計數(shù)剥险。是George-Collins在1960年發(fā)明的聪蘸,恰巧與John McCarthy發(fā)明的可用列表算法在同一年出現(xiàn)。就像Mike-Bernstein在6月份哥譚市Ruby大會杰出的垃圾回收機制演講中說的: "1960年是垃圾收集器的黃金年代..."
Python開發(fā)者工作在衛(wèi)生之家,你可以想象,有個患有輕度OCD(一種強迫癥)的室友一刻不停地跟在你身后打掃健爬,你一放下臟碟子或杯子控乾,有個家伙已經(jīng)準(zhǔn)備好把它放進洗碗機了!
現(xiàn)在來看第二例子娜遵。加入我們讓n2引用n1:
上圖中左邊的DEF的引用數(shù)已經(jīng)被Python減少了蜕衡,垃圾回收器會立即回收DEF實例。同時JKL的引用數(shù)已經(jīng)變?yōu)榱? 设拟,因為n1和n2都指向它慨仿。
[if !supportLists]1.11.5.7?[endif]標(biāo)記-清除
最終那間凌亂的房間充斥著垃圾,再不能歲月靜好了纳胧。在Ruby程序運行了一陣子以后镰吆,可用列表最終被用光光了:
此刻所有Ruby預(yù)創(chuàng)建對象都被程序用過了(它們都變灰了),可用列表里空空如也(沒有白格子了)跑慕。
此刻Ruby祭出另一McCarthy發(fā)明的算法万皿,名曰:標(biāo)記-清除。首先Ruby把程序停下來核行,Ruby用"地球停轉(zhuǎn)垃圾回收大法"牢硅。之后Ruby輪詢所有指針,變量和代碼產(chǎn)生別的引用對象和其他值钮科。同時Ruby通過自身的虛擬機便利內(nèi)部指針唤衫。標(biāo)記出這些指針引用的每個對象。我在圖中使用M表示绵脯。
上圖中那三個被標(biāo)M的對象是程序還在使用的佳励。在內(nèi)部,Ruby實際上使用一串位值蛆挫,被稱為:可用位圖(譯注:還記得《編程珠璣》里的為突發(fā)排序嗎赃承,這對離散度不高的有限整數(shù)集合具有很強的壓縮效果,用以節(jié)約機器的資源悴侵。)瞧剖,來跟蹤對象是否被標(biāo)記了。
如果說被標(biāo)記的對象是存活的可免,剩下的未被標(biāo)記的對象只能是垃圾抓于,這意味著我們的代碼不再會使用它了。我會在下圖中用白格子表示垃圾對象:
接下來Ruby清除這些無用的垃圾對象浇借,把它們送回到可用列表中:
在內(nèi)部這一切發(fā)生得迅雷不及掩耳捉撮,因為Ruby實際上不會吧對象從這拷貝到那。而是通過調(diào)整內(nèi)部指針妇垢,將其指向一個新鏈表的方式巾遭,來將垃圾對象歸位到可用列表中的肉康。
現(xiàn)在等到下回再創(chuàng)建對象的時候Ruby又可以把這些垃圾對象分給我們使用了。在Ruby里灼舍,對象們六道輪回吼和,轉(zhuǎn)世投胎,享受多次人生骑素。
[if !supportLists]1.11.5.8?[endif]標(biāo)記-刪除 vs. 引用計數(shù)
乍一看炫乓,Python的GC算法貌似遠(yuǎn)勝于Ruby的:寧舍潔宇而居穢室乎?為什么Ruby寧愿定期強制程序停止運行砂豌,也不使用Python的算法呢厢岂?
然而,引用計數(shù)并不像第一眼看上去那樣簡單阳距。有許多原因使得不許多語言不像Python這樣使用引用計數(shù)GC算法:
首先塔粒,它不好實現(xiàn)。Python不得不在每個對象內(nèi)部留一些空間來處理引用數(shù)筐摘。這樣付出了一小點兒空間上的代價卒茬。但更糟糕的是,每個簡單的操作(像修改變量或引用)都會變成一個更復(fù)雜的操作咖熟,因為Python需要增加一個計數(shù)圃酵,減少另一個,還可能釋放對象馍管。
第二點郭赐,它相對較慢。雖然Python隨著程序執(zhí)行GC很穩(wěn)饺贩小(一把臟碟子放在洗碗盆里就開始洗啦)捌锭,但這并不一定更快。Python不停地更新著眾多引用數(shù)值罗捎。特別是當(dāng)你不再使用一個大數(shù)據(jù)結(jié)構(gòu)的時候观谦,比如一個包含很多元素的列表,Python可能必須一次性釋放大量對象桨菜。減少引用數(shù)就成了一項復(fù)雜的遞歸過程了豁状。
最后,它不是總奏效的倒得。引用計數(shù)不能處理環(huán)形數(shù)據(jù)結(jié)構(gòu)--也就是含有循環(huán)引用的數(shù)據(jù)結(jié)構(gòu)泻红。
[if !supportLists]1.11.6?[endif]Python中的循環(huán)數(shù)據(jù)結(jié)構(gòu)以及引用計數(shù)
[if !supportLists]1.11.6.1?[endif]循環(huán)引用
通過上篇,我們知道在Python中霞掺,每個對象都保存了一個稱為引用計數(shù)的整數(shù)值谊路,來追蹤到底有多少引用指向了這個對象。無論何時根悼,如果我們程序中的一個變量或其他對象引用了目標(biāo)對象凶异,Python將會增加這個計數(shù)值,而當(dāng)程序停止使用這個對象挤巡,則Python會減少這個計數(shù)值剩彬。一旦計數(shù)值被減到零,Python將會釋放這個對象以及回收相關(guān)內(nèi)存空間矿卑。
從六十年代開始喉恋,計算機科學(xué)界就面臨了一個嚴(yán)重的理論問題,那就是針對引用計數(shù)這種算法來說母廷,如果一個數(shù)據(jù)結(jié)構(gòu)引用了它自身轻黑,即如果這個數(shù)據(jù)結(jié)構(gòu)是一個循環(huán)數(shù)據(jù)結(jié)構(gòu),那么某些引用計數(shù)值是肯定無法變成零的茅逮。為了更好地理解這個問題旺嬉,讓我們舉個例子诚隙。下面的代碼展示了一些上周我們所用到的節(jié)點類:
我們有一個"構(gòu)造器"(在Python中叫做 __init__ ),在一個實例變量中存儲一個單獨的屬性抖拦。在類定義之后我們創(chuàng)建兩個節(jié)點,ABC以及DEF舷暮,在圖中為左邊的矩形框态罪。兩個節(jié)點的引用計數(shù)都被初始化為1,因為各有兩個引用指向各個節(jié)點(n1和n2)下面。
現(xiàn)在复颈,讓我們在節(jié)點中定義兩個附加的屬性,next以及prev:
跟Ruby不同的是沥割,Python中你可以在代碼運行的時候動態(tài)定義實例變量或?qū)ο髮傩院睦病_@看起來似乎有點像Ruby缺失了某些有趣的魔法。(聲明下我不是一個Python程序員驯遇,所以可能會存在一些命名方面的錯誤)芹彬。我們設(shè)置 n1.next 指向 n2,同時設(shè)置 n2.prev 指回 n1〔媛現(xiàn)在舒帮,我們的兩個節(jié)點使用循環(huán)引用的方式構(gòu)成了一個雙向鏈表。同時請注意到ABC以及 DEF 的引用計數(shù)值已經(jīng)增加到了2陡叠。這里有兩個指針指向了每個節(jié)點:首先是 n1 以及 n2玩郊,其次就是 next 以及 prev。
現(xiàn)在枉阵,假定我們的程序不再使用這兩個節(jié)點了译红,我們將n1和 n2 都設(shè)置為null(Python中是None)。
好了兴溜,Python會像往常一樣將每個節(jié)點的引用計數(shù)減少到1侦厚。
[if !supportLists]1.11.6.2?[endif]在Python中的零代(Generation Zero)
請注意在以上剛剛說到的例子中耻陕,我們以一個不是很常見的情況結(jié)尾:我們有一個“孤島”或是一組未使用的、互相指向的對象刨沦,但是誰都沒有外部引用诗宣。換句話說,我們的程序不再使用這些節(jié)點對象了想诅,所以我們希望Python的垃圾回收機制能夠足夠智能去釋放這些對象并回收它們占用的內(nèi)存空間召庞。但是這不可能,因為所有的引用計數(shù)都是1而不是0来破。Python的引用計數(shù)算法不能夠處理互相指向自己的對象篮灼。
這就是為什么Python要引入Generational GC算法的原因!正如Ruby使用一個鏈表(free list)來持續(xù)追蹤未使用的徘禁、自由的對象一樣诅诱,Python使用一種不同的鏈表來持續(xù)追蹤活躍的對象。而不將其稱之為“活躍列表”送朱,Python的內(nèi)部C代碼將其稱為零代(Generation Zero)逢艘。每次當(dāng)你創(chuàng)建一個對象或其他什么值的時候,Python會將其加入零代鏈表:
從上邊可以看到當(dāng)我們創(chuàng)建ABC節(jié)點的時候骤菠,Python將其加入零代鏈表它改。請注意到這并不是一個真正的列表,并不能直接在你的代碼中訪問商乎,事實上這個鏈表是一個完全內(nèi)部的Python運行時央拖。 相似的,當(dāng)我們創(chuàng)建DEF節(jié)點的時候鹉戚,Python將其加入同樣的鏈表:
現(xiàn)在零代包含了兩個節(jié)點對象鲜戒。(他還將包含Python創(chuàng)建的每個其他值,與一些Python自己使用的內(nèi)部值抹凳。)
[if !supportLists]1.11.6.3?[endif]檢測循環(huán)引用
隨后遏餐,Python會循環(huán)遍歷零代列表上的每個對象,檢查列表中每個互相引用的對象赢底,根據(jù)規(guī)則減掉其引用計數(shù)失都。在這個過程中,Python會一個接一個的統(tǒng)計內(nèi)部引用的數(shù)量以防過早地釋放對象幸冻。
為了便于理解粹庞,來看一個例子:
從上面可以看到ABC和 DEF 節(jié)點包含的引用數(shù)為1.有三個其他的對象同時存在于零代鏈表中,藍(lán)色的箭頭指示了有一些對象正在被零代鏈表之外的其他對象所引用洽损。(接下來我們會看到庞溜,Python中同時存在另外兩個分別被稱為一代和二代的鏈表)。這些對象有著更高的引用計數(shù)因為它們正在被其他指針?biāo)赶蛑?/p>
接下來你會看到Python的GC是如何處理零代鏈表的碑定。
通過識別內(nèi)部引用流码,Python能夠減少許多零代鏈表對象的引用計數(shù)又官。在上圖的第一行中你能夠看見ABC和DEF的引用計數(shù)已經(jīng)變?yōu)榱懔耍@意味著收集器可以釋放它們并回收內(nèi)存空間了漫试。剩下的活躍的對象則被移動到一個新的鏈表:一代鏈表赏胚。
從某種意義上說,Python的GC算法類似于Ruby所用的標(biāo)記回收算法商虐。周期性地從一個對象到另一個對象追蹤引用以確定對象是否還是活躍的,正在被程序所使用的崖疤,這正類似于Ruby的標(biāo)記過程秘车。
[if !supportLists]1.11.6.4?[endif]Python中的GC閾值
Python什么時候會進行這個標(biāo)記過程?隨著你的程序運行劫哼,Python解釋器保持對新創(chuàng)建的對象叮趴,以及因為引用計數(shù)為零而被釋放掉的對象的追蹤。從理論上說权烧,這兩個值應(yīng)該保持一致眯亦,因為程序新建的每個對象都應(yīng)該最終被釋放掉。
當(dāng)然般码,事實并非如此妻率。因為循環(huán)引用的原因,并且因為你的程序使用了一些比其他對象存在時間更長的對象板祝,從而被分配對象的計數(shù)值與被釋放對象的計數(shù)值之間的差異在逐漸增長宫静。一旦這個差異累計超過某個閾值,則Python的收集機制就啟動了券时,并且觸發(fā)上邊所說到的零代算法孤里,釋放“浮動的垃圾”,并且將剩下的對象移動到一代列表橘洞。
隨著時間的推移捌袜,程序所使用的對象逐漸從零代列表移動到一代列表。而Python對于一代列表中對象的處理遵循同樣的方法炸枣,一旦被分配計數(shù)值與被釋放計數(shù)值累計到達(dá)一定閾值虏等,Python會將剩下的活躍對象移動到二代列表。
通過這種方法适肠,你的代碼所長期使用的對象博其,那些你的代碼持續(xù)訪問的活躍對象,會從零代鏈表轉(zhuǎn)移到一代再轉(zhuǎn)移到二代迂猴。通過不同的閾值設(shè)置慕淡,Python可以在不同的時間間隔處理這些對象。Python處理零代最為頻繁沸毁,其次是一代然后才是二代峰髓。
弱代假說
來看看代垃圾回收算法的核心行為:垃圾回收器會更頻繁的處理新對象傻寂。一個新的對象即是你的程序剛剛創(chuàng)建的,而一個來的對象則是經(jīng)過了幾個時間周期之后仍然存在的對象携兵。Python會在當(dāng)一個對象從零代移動到一代疾掰,或是從一代移動到二代的過程中提升(promote)這個對象。
為什么要這么做徐紧?這種算法的根源來自于弱代假說(weak generational hypothesis)静檬。這個假說由兩個觀點構(gòu)成:首先是年親的對象通常死得也快,而老對象則很有可能存活更長的時間并级。
假定現(xiàn)在我用Python或是Ruby創(chuàng)建一個新對象:
根據(jù)假說拂檩,我的代碼很可能僅僅會使用ABC很短的時間。這個對象也許僅僅只是一個方法中的中間結(jié)果嘲碧,并且隨著方法的返回這個對象就將變成垃圾了稻励。大部分的新對象都是如此般地很快變成垃圾。然而愈涩,偶爾程序會創(chuàng)建一些很重要的望抽,存活時間比較長的對象-例如web應(yīng)用中的session變量或是配置項。
通過頻繁的處理零代鏈表中的新對象履婉,Python的垃圾收集器將把時間花在更有意義的地方:它處理那些很快就可能變成垃圾的新對象煤篙。同時只在很少的時候,當(dāng)滿足閾值的條件毁腿,收集器才回去處理那些老變量舰蟆。
[if !supportLists]1.11.7?[endif]gc模塊
[if !supportLists]1.11.7.1?[endif]垃圾回收機制
Python中的垃圾回收是以引用計數(shù)為主,分代收集為輔狸棍。
[if !supportLists]1.11.7.1.1?[endif]導(dǎo)致引用計數(shù)+1的情況
[if !supportLists]·?[endif]對象被創(chuàng)建身害,例如a=23
[if !supportLists]·?[endif]對象被引用,例如b=a
[if !supportLists]·?[endif]對象被作為參數(shù)草戈,傳入到一個函數(shù)中塌鸯,例如func(a)
[if !supportLists]·?[endif]對象作為一個元素,存儲在容器中唐片,例如list1=[a,a]
[if !supportLists]1.11.7.1.2?[endif]導(dǎo)致引用計數(shù)-1的情況
[if !supportLists]·?[endif]對象的別名被顯式銷毀丙猬,例如del a
[if !supportLists]·?[endif]對象的別名被賦予新的對象,例如a=24
[if !supportLists]·?[endif]一個對象離開它的作用域费韭,例如f函數(shù)執(zhí)行完畢時茧球,func函數(shù)中的局部變量(全局變量不會)
[if !supportLists]·?[endif]對象所在的容器被銷毀,或從容器中刪除對象
[if !supportLists]1.11.7.1.3?[endif]查看一個對象的引用計數(shù)
import?sys
a = "hello world"
sys.getrefcount(a)
可以查看a對象的引用計數(shù)星持,但是比正常計數(shù)大1抢埋,因為調(diào)用函數(shù)的時候傳入a,這會讓a的引用計數(shù)+1
[if !supportLists]1.11.7.2?[endif]循環(huán)引用導(dǎo)致內(nèi)存泄露
引用計數(shù)的缺陷是循環(huán)引用的問題
class?ClassA():
????def?__init__(self):
????????print('object born,id:%s'%str(hex(id(self))))
def?f2():
????while?True:
????????c1 = ClassA()
????????c2 = ClassA()
????????c1.t = c2
????????c2.t = c1
????????del?c1
????????del?c2
#把python的gc關(guān)閉
gc.disable()
f2()
執(zhí)行f2(),進程占用的內(nèi)存會不斷增大揪垄。
[if !supportLists]·?[endif]創(chuàng)建了c1穷吮,c2后這兩塊內(nèi)存的引用計數(shù)都是1,執(zhí)行c1.t=c2和c2.t=c1后饥努,這兩塊內(nèi)存的引用計數(shù)變成2.
[if !supportLists]·?[endif]在del c1后捡鱼,內(nèi)存1的對象的引用計數(shù)變?yōu)?,由于不是為0酷愧,所以內(nèi)存1的對象不會被銷毀驾诈,所以內(nèi)存2的對象的引用數(shù)依然是2,在del c2后溶浴,同理乍迄,內(nèi)存1的對象,內(nèi)存2的對象的引用數(shù)都是1戳葵。
[if !supportLists]·?[endif]雖然它們兩個的對象都是可以被銷毀的,但是由于循環(huán)引用汉匙,導(dǎo)致垃圾回收器都不會回收它們拱烁,所以就會導(dǎo)致內(nèi)存泄露。
[if !supportLists]1.11.7.3?[endif]垃圾回收
#coding=utf-8
class?ClassA():
????def?__init__(self):
????????print('object born,id:%s'%str(hex(id(self))))
????# def __del__(self):
????# ????print('object del,id:%s'%str(hex(id(self))))
def?f3():
????print("-----0------")
????# print(gc.collect())
????c1 = ClassA()
????c2 = ClassA()
????c1.t = c2
????c2.t = c1
????print("-----1------")
????del?c1
????del?c2
????print("-----2------")
????print(gc.garbage)
????print("-----3------")
????print(gc.collect()) #顯式執(zhí)行垃圾回收
????print("-----4------")
????print(gc.garbage)
????print("-----5------")
if?__name__ == '__main__':
????gc.set_debug(gc.DEBUG_LEAK) #設(shè)置gc模塊的日志
????f3()
python2運行結(jié)果:
-----0------
object born,id:0x724b20
object born,id:0x724b48
-----1------
-----2------
[]
-----3------
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
-----4------
[<__main__.ClassA instance at 0x724b20>, <__main__.ClassA instance at 0x724b48>, {'t': <__main__.ClassA instance at 0x724b48>}, {'t': <__main__.ClassA instance at 0x724b20>}]
-----5------
說明:
[if !supportLists]·?[endif]垃圾回收后的對象會放在gc.garbage列表里面
[if !supportLists]·?[endif]gc.collect()會返回不可達(dá)的對象數(shù)目噩翠,4等于兩個對象以及它們對應(yīng)的dict
有三種情況會觸發(fā)垃圾回收:
[if !supportLists]1.?[endif]調(diào)用gc.collect(),
[if !supportLists]2.?[endif]當(dāng)gc模塊的計數(shù)器達(dá)到閥值的時候戏自。
[if !supportLists]3.?[endif]程序退出的時候
[if !supportLists]1.11.7.4?[endif]gc模塊常用功能解析
gc模塊提供一個接口給開發(fā)者設(shè)置垃圾回收的選項。上面說到伤锚,采用引用計數(shù)的方法管理內(nèi)存的一個缺陷是循環(huán)引用擅笔,而gc模塊的一個主要功能就是解決循環(huán)引用的問題。
[if !supportLists]1.11.7.4.1?[endif]常用函數(shù):
1屯援、gc.set_debug(flags) 設(shè)置gc的debug日志猛们,一般設(shè)置為gc.DEBUG_LEAK
2、gc.collect([generation]) 顯式進行垃圾回收狞洋,可以輸入?yún)?shù)弯淘,0代表只檢查第一代的對象,1代表檢查一吉懊,二代的對象庐橙,2代表檢查一,二借嗽,三代的對象态鳖,如果不傳參數(shù),執(zhí)行一個full collection恶导,也就是等于傳2浆竭。 返回不可達(dá)(unreachable objects)對象的數(shù)目
3、gc.get_threshold() 獲取的gc模塊中自動執(zhí)行垃圾回收的頻率。
4兆蕉、gc.set_threshold(threshold0[, threshold1[, threshold2]) 設(shè)置自動執(zhí)行垃圾回收的頻率羽戒。
5、gc.get_count() 獲取當(dāng)前自動執(zhí)行垃圾回收的計數(shù)器虎韵,返回一個長度為3的列表
[if !supportLists]1.11.7.4.2?[endif]gc模塊的自動垃圾回收機制
必須要import gc模塊易稠,并且is_enable()=True才會啟動自動垃圾回收。
這個機制的主要作用就是發(fā)現(xiàn)并處理不可達(dá)的垃圾對象包蓝。
垃圾回收=垃圾檢查+垃圾回收
在Python中驶社,采用分代收集的方法。把對象分為三代测萎,一開始亡电,對象在創(chuàng)建的時候,放在一代中硅瞧,如果在一次一代的垃圾檢查中份乒,改對象存活下來,就會被放到二代中腕唧,同理在一次二代的垃圾檢查中或辖,該對象存活下來,就會被放到三代中枣接。
gc模塊里面會有一個長度為3的列表的計數(shù)器颂暇,可以通過gc.get_count()獲取。
例如(488,3,0)但惶,其中488是指距離上一次一代垃圾檢查耳鸯,Python分配內(nèi)存的數(shù)目減去釋放內(nèi)存的數(shù)目,注意是內(nèi)存分配膀曾,而不是引用計數(shù)的增加县爬。例如:
print?gc.get_count() # (590, 8, 0)
a = ClassA()
print?gc.get_count() # (591, 8, 0)
del?a
print?gc.get_count() # (590, 8, 0)
3是指距離上一次二代垃圾檢查,一代垃圾檢查的次數(shù)添谊,同理捌省,0是指距離上一次三代垃圾檢查,二代垃圾檢查的次數(shù)碉钠。
gc母倩海快有一個自動垃圾回收的閥值,即通過gc.get_threshold函數(shù)獲取到的長度為3的元組喊废,例如(700,10,10) 每一次計數(shù)器的增加祝高,gc模塊就會檢查增加后的計數(shù)是否達(dá)到閥值的數(shù)目,如果是污筷,就會執(zhí)行對應(yīng)的代數(shù)的垃圾檢查工闺,然后重置計數(shù)器
例如乍赫,假設(shè)閥值是(700,10,10):
當(dāng)計數(shù)器從(699,3,0)增加到(700,3,0),gc模塊就會執(zhí)行g(shù)c.collect(0),即檢查一代對象的垃圾陆蟆,并重置計數(shù)器為(0,4,0)
當(dāng)計數(shù)器從(699,9,0)增加到(700,9,0)雷厂,gc模塊就會執(zhí)行g(shù)c.collect(1),即檢查一、二代對象的垃圾叠殷,并重置計數(shù)器為(0,0,1)
當(dāng)計數(shù)器從(699,9,9)增加到(700,9,9)改鲫,gc模塊就會執(zhí)行g(shù)c.collect(2),即檢查一、二林束、三代對象的垃圾像棘,并重置計數(shù)器為(0,0,0)
注意點
gc模塊唯一處理不了的是循環(huán)引用的類都有__del__方法,所以項目中要避免定義__del__方法
import?gc
class?ClassA():
????pass
????# def __del__(self):
????# ????print('object born,id:%s'%str(hex(id(self))))
gc.set_debug(gc.DEBUG_LEAK)
a = ClassA()
b = ClassA()
a.next = b
b.prev = a
print?"--1--"
print?gc.collect()
print?"--2--"
del?a
print?"--3--"
del?b
print?"--3-1--"
print?gc.collect()
print?"--4--"
運行結(jié)果:
--1--
0
--2--
--3--
--3-1--
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
--4--
如果把del打開壶冒,運行結(jié)果為:
--1--
0
--2--
--3--
--3-1--
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
4
--4--
[if !supportLists]1.12?[endif]內(nèi)建屬性
"teachclass.py"
class?Person(object):
????pass
python3.5中類的內(nèi)建屬性和方法?
經(jīng)典類(舊式類),早期如果沒有要繼承的父類,繼承里空著不寫的類
#py2中無繼承父類缕题,稱之經(jīng)典類,py3中已默認(rèn)繼承object
class?Person:
????pass
子類沒有實現(xiàn)__init__方法時,默認(rèn)自動調(diào)用父類的胖腾。如定義__init__方法時烟零,需自己手動調(diào)用父類的__init__方法
__getattribute__例子:
????def?__init__(self,subject1):
????????self.subject1 = subject1
????????self.subject2 = 'cpp'
????#屬性訪問時攔截器,打log
????def?__getattribute__(self,obj):
????????if?obj == 'subject1':
????????????print('log subject1')
????????????return?'redirect python'
????????else: ??#測試時注釋掉這2行咸作,將找不到subject2
????????????return?object.__getattribute__(self,obj)
????def?show(self):
????????print('this is Test')
s = Test("python")
print(s.subject1)
print(s.subject2)
運行結(jié)果:
log subject1
redirect python
cpp
__getattribute__的坑
????class?Person(object):
????????def?__getattribute__(self,obj):
????????????print("---test---")
????????????if?obj.startswith("a"):
????????????????return?"hahha"
????????????else:
????????????????return?self.test
????????def?test(self):
????????????print("heihei")
????t = Person()
????t.a #返回hahha
????t.b #會讓程序死掉
????????#原因是:當(dāng)t.b執(zhí)行時锨阿,會調(diào)用Person類中定義的__getattribute__方法,但是在這個方法的執(zhí)行過程中
????????#if條件不滿足性宏,所以 程序執(zhí)行else里面的代碼群井,即return self.test ?問題就在這,因為return 需要把
????????#self.test的值返回,那么首先要獲取self.test的值败匹,因為self此時就是t這個對象挟憔,所以self.test就是
????????#t.test此時要獲取t這個對象的test屬性,那么就會跳轉(zhuǎn)到__getattribute__方法去執(zhí)行家妆,即此時產(chǎn)
????????#生了遞歸調(diào)用,由于這個遞歸過程中 沒有判斷什么時候推出,所以這個程序會永無休止的運行下去口渔,又因為
????????#每次調(diào)用函數(shù),就需要保存一些數(shù)據(jù)穿撮,那么隨著調(diào)用的次數(shù)越來越多缺脉,最終內(nèi)存吃光,所以程序 崩潰
????????#
????????#注意:以后不要在__getattribute__方法中調(diào)用self.xxxx
[if !supportLists]1.13?[endif]內(nèi)建函數(shù)
Build-in Function,啟動python解釋器悦穿,輸入dir(__builtins__),可以看到很多python解釋器啟動后默認(rèn)加載的屬性和函數(shù)攻礼,這些函數(shù)稱之為內(nèi)建函數(shù), 這些函數(shù)因為在編程時使用較多栗柒,cpython解釋器用c語言實現(xiàn)了這些函數(shù)礁扮,啟動解釋器 時默認(rèn)加載。
這些函數(shù)數(shù)量眾多,不宜記憶太伊,開發(fā)時不是都用到的雇锡,待用到時再help(function),查看如何使用,或結(jié)合百度查詢即可僚焦,在這里介紹些常用的內(nèi)建函數(shù)锰提。
[if !supportLists]1.13.1?[endif]range
????range(stop) -> list of integers
????range(start, stop[, step]) -> list of integers
[if !supportLists]·?[endif]start:計數(shù)從start開始。默認(rèn)是從0開始叠赐。例如range(5)等價于range(0欲账, 5);
[if !supportLists]·?[endif]stop:到stop結(jié)束,但不包括stop.例如:range(0芭概, 5) 是[0, 1, 2, 3, 4]沒有5
[if !supportLists]·?[endif]step:每次跳躍的間距赛不,默認(rèn)為1。例如:range(0罢洲, 5) 等價于 range(0, 5, 1)
python2中range返回列表踢故,python3中range返回一個迭代值。如果想得到列表,可通過list函數(shù)
a = range(5)
list(a)
創(chuàng)建列表的另外一種方法
In [21]: testList = [x+2?for?x in?range(5)]
In [22]: testList
Out[22]: [2, 3, 4, 5, 6]
[if !supportLists]1.13.2?[endif]map函數(shù)
map函數(shù)會根據(jù)提供的函數(shù)對指定序列做映射
????map(...)
????????map(function, sequence[, sequence, ...]) -> list
[if !supportLists]·?[endif]function:是一個函數(shù)
[if !supportLists]·?[endif]sequence:是一個或多個序列,取決于function需要幾個參數(shù)
[if !supportLists]·?[endif]返回值是一個map
參數(shù)序列中的每一個元素分別調(diào)用function函數(shù)惹苗,返回包含每次function函數(shù)返回值的list殿较。
#函數(shù)需要一個參數(shù)
map(lambda?x: x*x, [1, 2, 3])
#結(jié)果為:[1, 4, 9]
#函數(shù)需要兩個參數(shù)
map(lambda?x, y: x+y, [1, 2, 3], [4, 5, 6])
#結(jié)果為:[5, 7, 9]
????return?(x,y)
l1 = [ 0, 1, 2, 3, 4, 5, 6?] ?
l2 = [ 'Sun', 'M', 'T', 'W', 'T', 'F', 'S'?]
l3 = map( f1, l1, l2 )
print(list(l3))
#結(jié)果為:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]
[if !supportLists]1.13.3?[endif]filter函數(shù)
filter函數(shù)會對指定序列執(zhí)行過濾操作
filter(...)
????filter(function or None, sequence) -> list, tuple, or string
????Return those items of sequence for which function(item) is true. ?If
????function is None, return the items that are true. ?If sequence is a tuple
????or string, return the same type, else return a list.
[if !supportLists]·?[endif]function:接受一個參數(shù),返回布爾值True或False
[if !supportLists]·?[endif]sequence:序列可以是str桩蓉,tuple淋纲,list
filter函數(shù)會對序列參數(shù)sequence中的每個元素調(diào)用function函數(shù),最后返回的結(jié)果包含調(diào)用結(jié)果為True的元素院究。
返回值的類型和參數(shù)sequence的類型相同
filter(lambda?x: x%2, [1, 2, 3, 4])
[1, 3]
filter(None, "she")
'she'
[if !supportLists]1.13.4?[endif]reduce函數(shù)
reduce函數(shù)洽瞬,reduce函數(shù)會對參數(shù)序列中元素進行累積
reduce(...)
????reduce(function, sequence[, initial]) -> value
????Apply a function of two arguments cumulatively to the items of a sequence,
????from left to right, so as to reduce the sequence to a single value.
????For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
????((((1+2)+3)+4)+5). ?If initial is present, it is placed before the items
????of the sequence in the calculation, and serves as a default when the
????sequence is empty.
[if !supportLists]·?[endif]function:該函數(shù)有兩個參數(shù)
[if !supportLists]·?[endif]sequence:序列可以是str,tuple业汰,list
[if !supportLists]·?[endif]initial:固定初始值
reduce依次從sequence中取一個元素伙窃,和上一次調(diào)用function的結(jié)果做參數(shù)再次調(diào)用function。 第一次調(diào)用function時样漆,如果提供initial參數(shù)为障,會以sequence中的第一個元素和initial 作為參數(shù)調(diào)用function,否則會以序列sequence中的前兩個元素做參數(shù)調(diào)用function放祟。 注意function函數(shù)不能為None鳍怨。
reduce(lambda?x, y: x+y, [1,2,3,4])
10
reduce(lambda?x, y: x+y, [1,2,3,4], 5)
15
reduce(lambda?x, y: x+y, ['aa', 'bb', 'cc'], 'dd')
'ddaabbcc'
在Python3里,reduce函數(shù)已經(jīng)被從全局名字空間里移除了, 它現(xiàn)在被放置在fucntools模塊里用的話要先引入:?from functools import reduce
[if !supportLists]1.13.5?[endif]sorted函數(shù)
sorted(...)
????sorted(iterable, key=None, reverse=False) --> new sorted list
[if !supportLists]1.14?[endif]functools
functools是python2.5被引人的,一些工具函數(shù)放在此包里。
python2.7中
python3.5中
import?functools
dir(functools)
運行結(jié)果:
['MappingProxyType',
?'RLock',
?'WRAPPER_ASSIGNMENTS',
?'WRAPPER_UPDATES',
?'WeakKeyDictionary',
?'_CacheInfo',
?'_HashedSeq',
?'__all__',
?'__builtins__',
?'__cached__',
?'__doc__',
?'__file__',
?'__loader__',
?'__name__',
?'__package__',
?'__spec__',
?'_c3_merge',
?'_c3_mro',
?'_compose_mro',
?'_convert',
?'_find_impl',
?'_ge_from_gt',
?'_ge_from_le',
?'_ge_from_lt',
?'_gt_from_ge',
?'_gt_from_le',
?'_gt_from_lt',
?'_le_from_ge',
?'_le_from_gt',
?'_le_from_lt',
?'_lru_cache_wrapper',
?'_lt_from_ge',
?'_lt_from_gt',
?'_lt_from_le',
?'_make_key',
?'cmp_to_key',
?'get_cache_token',
?'lru_cache',
?'namedtuple',
?'partial',
?'partialmethod',
?'reduce',
?'singledispatch',
?'total_ordering',
?'update_wrapper',
?'wraps']
python3中增加了更多工具函數(shù)跪妥,做業(yè)務(wù)開發(fā)時大多情況下用不到鞋喇,此處介紹使用頻率較高的2個函數(shù)。
[if !supportLists]1.14.1?[endif]partial函數(shù)(偏函數(shù))
把一個函數(shù)的某些參數(shù)設(shè)置默認(rèn)值骗奖,返回一個新的函數(shù)确徙,調(diào)用這個新函數(shù)會更簡單醒串。
def?showarg(*args, **kw):
????print(args)
????print(kw)
p1=functools.partial(showarg, 1,2,3)
p1()
p1(4,5,6)
p1(a='python', b='test')
p2=functools.partial(showarg, a=3,b='linux')
p2()
p2(1,2)
p2(a='python', b='test')
[if !supportLists]1.14.2?[endif]wraps函數(shù)
使用裝飾器時,有一些細(xì)節(jié)需要被注意鄙皇。例如芜赌,被裝飾后的函數(shù)其實已經(jīng)是另外一個函數(shù)了(函數(shù)名等函數(shù)屬性會發(fā)生改變)。
添加后由于函數(shù)名和函數(shù)的doc發(fā)生了改變伴逸,對測試結(jié)果有一些影響缠沈,例如:
????"note function"
????def?wrapper():
????????"wrapper function"
????????print('note something')
????????return?func()
????return?wrapper
@note
def?test():
????"test function"
????print('I am test')
test()
print(test.__doc__)
運行結(jié)果
note something
I am test
wrapper function
所以,Python的functools包中提供了一個叫wraps的裝飾器來消除這樣的副作用错蝴。例如:
import?functools
def?note(func):
????"note function"
????@functools.wraps(func)
????def?wrapper():
????????"wrapper function"
????????print('note something')
????????return?func()
????return?wrapper
@note
def?test():
????"test function"
????print('I am test')
test()
print(test.__doc__)
運行結(jié)果
note something
I am test
test function
[if !supportLists]1.15?[endif]模塊進階
Python有一套很有用的標(biāo)準(zhǔn)庫(standard library)洲愤。標(biāo)準(zhǔn)庫會隨著Python解釋器,一起安裝在你的電腦中的顷锰。 它是Python的一個組成部分柬赐。這些標(biāo)準(zhǔn)庫是Python為你準(zhǔn)備好的利器,可以讓編程事半功倍官紫。
[if !supportLists]1.15.1?[endif]常用標(biāo)準(zhǔn)庫
[if !supportLists]1.15.1.1?[endif]time
1肛宋、help(time)之后可以知道time有2種時間表示形式:
1、時間戳表示法束世,即以整型或浮點型表示的是一個以秒為單位的時間間隔酝陈。這個時間的基礎(chǔ)值是從1970年的1月1號零點開始算起。2毁涉、元組格式表示法沉帮,即一種Python的數(shù)據(jù)結(jié)構(gòu)表示。這個元組有9個整型內(nèi)容贫堰。分別表示不同的時間含義穆壕。
2、名詞解釋:
UTC(Coordinated Universal Time严嗜,世界協(xié)調(diào)時)亦即格林威治天文時間粱檀,世界標(biāo)準(zhǔn)時間洲敢。在中國為UTC+8漫玄。
DST(Daylight Saving Time)即夏令時。是一種為節(jié)約能源而人為規(guī)定地方時間的制度压彭,一般在天亮早的夏季人為將時間提前一小時睦优。
3、包含的變量:
timezone --當(dāng)?shù)貢r間與標(biāo)準(zhǔn)UTC時間的誤差壮不,以秒計altzone --當(dāng)?shù)叵牧顣r時間與標(biāo)準(zhǔn)UTC時間的誤差汗盘,以秒計daylight --當(dāng)?shù)貢r間是否反映夏令時,默認(rèn)為0zname --關(guān)于(標(biāo)準(zhǔn)時區(qū)名稱, 夏令時時區(qū)名稱)的元組
4询一、包含的函數(shù):
time() --返回當(dāng)前時間戳隐孽,浮點數(shù)形式癌椿。不接受參數(shù)clock() --返回當(dāng)前程序的cpu執(zhí)行時間。unix系統(tǒng)始終返回全部運行時間菱阵;而windows從第二次開始都是以第一次調(diào)用此函數(shù)時的時間戳作為基準(zhǔn)踢俄,而不是程序開始時間為基準(zhǔn)。不接受參數(shù)晴及。sleep() --延遲一個時間段都办,接受整型、浮點型虑稼。gmtime() --將時間戳轉(zhuǎn)換為UTC時間元組格式琳钉。接受一個浮點型時間戳參數(shù),其默認(rèn)值為當(dāng)前時間戳蛛倦。?
localtime() --將時間戳轉(zhuǎn)換為本地時間元組格式歌懒。接受一個浮點型時間戳參數(shù),其默認(rèn)值為當(dāng)前時間戳溯壶。asctime() --將時間元組格式轉(zhuǎn)換為字符串形式歼培。接受一個時間元組,其默認(rèn)值為localtime()返回值ctime() --將時間戳轉(zhuǎn)換為字符串茸塞。接受一個時間戳躲庄,其默認(rèn)值為當(dāng)前時間戳。等價于asctime(localtime(seconds))mktime() --將本地時間元組轉(zhuǎn)換為時間戳钾虐。接受一個時間元組噪窘,必選。strftime() --將時間元組以指定的格式轉(zhuǎn)換為字符串形式效扫。接受字符串格式化串倔监、時間元組。時間元組為可選菌仁,默認(rèn)為localtime()strptime() --將指定格式的時間字符串解析為時間元組浩习,strftime()的逆向過程。接受字符串济丘,時間格式2個參數(shù)谱秽,都是必選。tzset() --改變本地時區(qū)摹迷。
5疟赊、時間字符串支持的格式符號:? ?
格式 含義 備注
%a 本地(locale)簡化星期名稱 ?
%A 本地完整星期名稱 ?
%b 本地簡化月份名稱 ?
%B 本地完整月份名稱 ?
%c 本地相應(yīng)的日期和時間表示 ?
%d 一個月中的第幾天(01 - 31) ?
%H 一天中的第幾個小時(24小時制,00 - 23) ?
%I 第幾個小時(12小時制峡碉,01 - 12) ?
%j 一年中的第幾天(001 - 366) ?
%m 月份(01 - 12) ?
%M 分鐘數(shù)(00 - 59) ?
%p 本地am或者pm的相應(yīng)符
%S 秒(01 - 61)
%U 一年中的星期數(shù)近哟。(00 - 53星期天是一個星期的開始。)第一個星期天之前的所有天數(shù)都放在第0周鲫寄。
%w 一個星期中的第幾天(0 - 6吉执,0是星期天)
%W 和%U基本相同疯淫,不同的是%W以星期一為一個星期的開始。 ?
%x 本地相應(yīng)日期 ?
%X 本地相應(yīng)時間 ?
%y 去掉世紀(jì)的年份(00 - 99) ?
%Y 完整的年份 ?
%Z 時區(qū)的名字(如果不存在為空字符) ?
%% ‘%’字符
[if !supportLists]1.15.1.2?[endif]hashlib
import?hashlib
m = hashlib.md5() ??#創(chuàng)建hash對象戳玫,md5:(message-Digest Algorithm 5)消息摘要算法,得出一個128位的密文
print?m ????????????#
m.update('test'.encode(“utf-8”))??#更新哈希對象以字符串參數(shù)
print?m.hexdigest() #返回十六進制數(shù)字字符串
應(yīng)用實例
用于注冊峡竣、登錄....
import?hashlib
import?datetime
KEY_VALUE = 'test'
now = datetime.datetime.now()
m = hashlib.md5()
str = '%s%s'?% (KEY_VALUE,now.strftime("%Y%m%d"))
m.update(str.encode('utf-8'))
value = m.hexdigest()
print(value)
運行結(jié)果:
8ad2d682e3529dac50e586fee8dc05c0
更多標(biāo)準(zhǔn)庫
http://python.usyiyi.cn/translate/python_352/library/index.html
[if !supportLists]1.15.2?[endif]常用擴展庫
[if !supportLists]1.15.2.1?[endif]SimpleHTTPServer
就可以運行起來靜態(tài)服務(wù)。平時用它預(yù)覽和下載文件太方便了量九。
在終端中輸入命令:
python2中
????python -m SimpleHTTPServer PORT
python3中
????python -m http.server PORT
[if !supportLists]1.15.2.2?[endif]matplotlib
[if !supportLists]1.16?[endif]?編碼風(fēng)格
錯誤認(rèn)知
[if !supportLists]·?[endif]這很浪費時間
[if !supportLists]·?[endif]我是個藝術(shù)家
[if !supportLists]·?[endif]所有人都能穿的鞋不會合任何人的腳
[if !supportLists]·?[endif]我善長制定編碼規(guī)范
正確認(rèn)知
[if !supportLists]·?[endif]促進團隊合作
[if !supportLists]·?[endif]減少bug處理
[if !supportLists]·?[endif]提高可讀性适掰,降低維護成本
[if !supportLists]·?[endif]有助于代碼審查
[if !supportLists]·?[endif]養(yǎng)成習(xí)慣,有助于程序員自身的成長
pep8編碼規(guī)范
Python Enhancement Proposals:python改進方案
https://www.python.org/dev/peps/
pep8官網(wǎng)規(guī)范地址
https://www.python.org/dev/peps/pep-0008/
Guido的關(guān)鍵點之一是:代碼更多是用來讀而不是寫荠列。編碼規(guī)范旨在改善Python代碼的可讀性类浪。
風(fēng)格指南強調(diào)一致性。項目肌似、模塊或函數(shù)保持一致都很重要费就。
[if !supportLists]1.16.1?[endif]每級縮進用4個空格。
括號中使用垂直隱式縮進或使用懸掛縮進川队。后者應(yīng)該注意第一行要沒有參數(shù)力细,后續(xù)行要有縮進。
[if !supportLists]·?[endif]Yes
#對準(zhǔn)左括號
foo = long_function_name(var_one, var_two,
??????????????????????????var_three, var_four)
#不對準(zhǔn)左括號固额,但加多一層縮進眠蚂,以和后面內(nèi)容區(qū)別。
def?long_function_name(
????????var_one, var_two, var_three,
????????var_four):
????print(var_one)
#懸掛縮進必須加多一層縮進.
foo = long_function_name(
????var_one, var_two,
????var_three, var_four)
[if !supportLists]·?[endif]No
#不使用垂直對齊時斗躏,第一行不能有參數(shù)逝慧。
foo = long_function_name(var_one, var_two,
????var_three, var_four)
#參數(shù)的縮進和后續(xù)內(nèi)容縮進不能區(qū)別。
def?long_function_name(
????var_one, var_two, var_three,
????var_four):
????print(var_one)
4個空格的規(guī)則是對續(xù)行可選的啄糙。
#懸掛縮進不一定是4個空格
foo = long_function_name(
??var_one, var_two,
??var_three, var_four)
if語句跨行時笛臣,兩個字符關(guān)鍵字(比如if)加上一個空格,再加上左括號構(gòu)成了很好的縮進隧饼。后續(xù)行暫時沒有規(guī)定沈堡,至少有如下三種格式,建議使用第3種燕雁。
#沒有額外縮進诞丽,不是很好看,個人不推薦.
if?(this_is_one_thing and
????that_is_another_thing):
????do_something()
#添加注釋
if?(this_is_one_thing and
????that_is_another_thing):
????# Since both conditions are true, we can frobnicate.
????do_something()
#額外添加縮進,推薦贵白。
# Add some extra indentation on the conditional continuation line.
if?(this_is_one_thing
????????and?that_is_another_thing):
????do_something()
[if !supportLists]1.16.2?[endif]右邊括號也可以另起一行率拒。有兩種格式崩泡,建議第2種禁荒。
#右括號不回退,個人不推薦
my_list = [
????1, 2, 3,
????4, 5, 6,
]
result = some_function_that_takes_arguments(
????'a', 'b', 'c',
????'d', 'e', 'f',
????)
#右括號回退
my_list = [
????1, 2, 3,
????4, 5, 6,
]
result = some_function_that_takes_arguments(
????'a', 'b', 'c',
????'d', 'e', 'f',
)
[if !supportLists]1.16.3?[endif]空格或Tab?
[if !supportLists]·?[endif]空格是首選的縮進方法角撞。
[if !supportLists]·?[endif]Tab僅僅在已經(jīng)使用tab縮進的代碼中為了保持一致性而使用呛伴。
[if !supportLists]·?[endif]Python 3中不允許混合使用Tab和空格縮進勃痴。
[if !supportLists]·?[endif]Python 2的包含空格與Tab和空格縮進的應(yīng)該全部轉(zhuǎn)為空格縮進。
[if !supportLists]1.16.4?[endif]最大行寬
[if !supportLists]·?[endif]限制所有行的最大行寬為79字符热康。
[if !supportLists]·?[endif]文本長塊沛申,比如文檔字符串或注釋,行長度應(yīng)限制為72個字符姐军。
[if !supportLists]1.16.5?[endif]空行
[if !supportLists]·?[endif]兩行空行分割頂層函數(shù)和類的定義铁材。
[if !supportLists]·?[endif]類的方法定義用單個空行分割。
[if !supportLists]·?[endif]額外的空行可以必要的時候用于分割不同的函數(shù)組奕锌,但是要盡量節(jié)約使用著觉。
[if !supportLists]·?[endif]額外的空行可以必要的時候在函數(shù)中用于分割不同的邏輯塊,但是要盡量節(jié)約使用惊暴。
[if !supportLists]1.16.6?[endif]源文件編碼
[if !supportLists]·?[endif]在核心Python發(fā)布的代碼應(yīng)該總是使用UTF-8(ASCII在Python 2)饼丘。
[if !supportLists]·?[endif]Python 3(默認(rèn)UTF-8)不應(yīng)有編碼聲明。
[if !supportLists]1.16.7?[endif]導(dǎo)入在單獨行
[if !supportLists]·?[endif]Yes:
import?os
import?sys
from?subprocess import?Popen, PIPE
[if !supportLists]·?[endif]No:
import?sys, os
[if !supportLists]·?[endif]導(dǎo)入始終在文件的頂部辽话,在模塊注釋和文檔字符串之后肄鸽,在模塊全局變量和常量之前。
[if !supportLists]·?[endif]導(dǎo)入順序如下:標(biāo)準(zhǔn)庫進口,相關(guān)的第三方庫油啤,本地庫典徘。各組的導(dǎo)入之間要有空行。
[if !supportLists]1.16.8?[endif]禁止使用通配符導(dǎo)入益咬。
通配符導(dǎo)入(from?import *)應(yīng)該避免烂斋,因為它不清楚命名空間有哪些名稱存,混淆讀者和許多自動化的工具础废。
[if !supportLists]1.16.9?[endif]字符串引用
[if !supportLists]·?[endif]Python中單引號字符串和雙引號字符串都是相同的汛骂。注意盡量避免在字符串中的反斜杠以提高可讀性。
[if !supportLists]·?[endif]根據(jù)PEP 257,三個引號都使用雙引號评腺。
[if !supportLists]1.16.10?[endif]括號里邊避免空格
#括號里邊避免空格
# Yes
spam(ham[1], {eggs: 2})
# No
spam( ham[ 1?], { eggs: 2?} )
[if !supportLists]1.16.11?[endif]逗號帘瞭,冒號,分號之前避免空格
#逗號蒿讥,冒號蝶念,分號之前避免空格
# Yes
if?x == 4: print?x, y; x, y = y, x
# No
if?x == 4?: print?x , y ; x , y = y , x
[if !supportLists]1.16.12?[endif]索引操作中的冒號
當(dāng)作操作符處理前后要有同樣的空格(一個空格或者沒有空格,個人建議是沒有芋绸。
# Yes
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
# No
ham[lower + offset:upper + offset]
ham[1: 9], ham[1?:9], ham[1:9?:3]
ham[lower : : upper]
ham[ : upper]
[if !supportLists]1.16.13?[endif]函數(shù)調(diào)用的左括號之前不能有空格
# Yes
spam(1)
dct['key'] = lst[index]
# No
spam (1)
dct ['key'] = lst [index]
[if !supportLists]1.16.14?[endif]賦值等操作符前后
賦值等操作符前后不能因為對齊而添加多個空格
# Yes
x = 1
y = 2
long_variable = 3
# No
x ?????????????= 1
y ?????????????= 2
long_variable = 3
[if !supportLists]1.16.15?[endif]二元運算符兩邊放置一個空格
涉及=媒殉、符合操作符 ( += , -=等)、比較( == , < , > , != , <> , <= , >= , in , not in , is , is not )摔敛、布爾( and , or , not )廷蓉。
優(yōu)先級高的運算符或操作符的前后不建議有空格。
# Yes
i = i + 1
submitted += 1
x = x*2?- 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# No
i=i+1
submitted +=1
x = x * 2?- 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
[if !supportLists]1.16.16?[endif]關(guān)鍵字參數(shù)和默認(rèn)值參數(shù)的前后不要加空格
# Yes
def?complex(real, imag=0.0):
????return?magic(r=real, i=imag)
# No
def?complex(real, imag = 0.0):
????return?magic(r = real, i = imag)
[if !supportLists]1.16.17?[endif]通常不推薦復(fù)合語句
Compound statements:多條語句寫在同一行
# Yes
if?foo == 'blah':
????do_blah_thing()
do_one()
do_two()
do_three()
# No
if?foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
盡管有時可以在if/for/while的同一行跟一小段代碼马昙,但絕不要跟多個子句桃犬,并盡量避免換行刹悴。
# No
if?foo == 'blah': do_blah_thing()
for?x in?lst: total += x
while?t < 10: t = delay()
更不是:
# No
if?foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
?????????????????????????????list, like, this)
if?foo == 'blah': one(); two(); three()
[if !supportLists]1.16.18?[endif]避免采用的名字
決不要用字符'l'(小寫字母el),'O'(大寫字母oh)攒暇,或 'I'(大寫字母eye) 作為單個字符的變量名土匀。一些字體中,這些字符不能與數(shù)字1和0區(qū)別形用。用'L' 代替'l'時就轧。
[if !supportLists]1.16.19?[endif]包和模塊名
模塊名要簡短,全部用小寫字母田度,可使用下劃線以提高可讀性钓丰。包名和模塊名類似,但不推薦使用下劃線每币。
[if !supportLists]1.17?[endif]代碼調(diào)試
[if !supportLists]1.17.0.1?[endif]pycharm
步驟:
[if !supportLists]1携丁、?[endif]設(shè)置斷點
[if !supportLists]2、?[endif]shift+f9 開始調(diào)試
[if !supportLists]3兰怠、?[endif]光標(biāo)就在斷點處停了梦鉴。這一行沒有運行的
[if !supportLists]4、?[endif]下一行:f8
[if !supportLists]5揭保、?[endif]進入方法:f7
[if !supportLists]6肥橙、?[endif]跳到下一個斷點:alt+f9
[if !supportLists]7、?[endif]進入方法秸侣,跳出這一步存筏,shift+f8