一元類
1類也是對象
在大多數(shù)編程語言中,類就是一組用來描述如何生成一個對象的代碼段。在Python中這一點仍然成立。
但是,Python中的類還遠(yuǎn)不止如此。類同樣也是一種對象。
類對象擁有創(chuàng)建對象(實例對象)的能力。但是,它的本質(zhì)仍然是一個對象,于是乎你可以對它做如下的操作:
1.你可以將它賦值給一個變量
2.你可以拷貝它
3.你可以為它增加屬性
4.你可以將它作為函數(shù)參數(shù)進(jìn)行傳遞
2動態(tài)的創(chuàng)建類
3使用type創(chuàng)建類
type還有一種完全不同的功能,動態(tài)的創(chuàng)建類。
type可以接受一個類的描述作為參數(shù),然后返回一個類。(要知道,根據(jù)傳入?yún)?shù)的不同,同一個函數(shù)擁有兩種完全不同的用法是一件很傻的事情,但這在Python中是為了保持向后兼容性)
type可以像這樣工作:
type(類名,由父類名稱組成的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))
4使用type創(chuàng)建帶有屬性的類
Foochild繼承了Test2的bar屬性
·type的第2個參數(shù),元組中是父類的名字,而不是字符串
·添加的屬性是類屬性,并不是實例屬性
5使用type創(chuàng)建帶有方法的類
為type創(chuàng)建的類添加實例方法
為type創(chuàng)建的類添加類方法
為type創(chuàng)建的類添加靜態(tài)方法
6什么是元類?
元類就是創(chuàng)建類的‘東西’,在python中萬物皆對象,類也是對象。
type就是一個元類,可以用來創(chuàng)建類。
Python中所有的東西,注意,我是指所有的東西——都是對象。這包括整數(shù)、字符串、函數(shù)以及類。它們?nèi)慷际菍ο螅宜鼈兌际菑囊粋€類創(chuàng)建而來,這個類就是type。
元類就是創(chuàng)建類這種對象的東西。type就是Python的內(nèi)建元類,當(dāng)然了,你也可以創(chuàng)建自己的元類。
7__metaclass__屬性
你可以在定義一個類的時候為其添加__metaclass__屬性。
classFoo(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)你寫如下代碼時:
classFoo(Bar):
pass
Python做了如下的操作:
1.Foo中有__metaclass__這個屬性嗎?如果是,Python會通過__metaclass__創(chuàng)建一個名字為Foo的類(對象)
2.如果Python沒有找到__metaclass__,它會繼續(xù)在Bar(父類)中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。
3.如果Python在任何父類中都找不到__metaclass__,它就會在模塊層次中去尋找__metaclass__,并嘗試做同樣的操作。
4.如果還是找不到__metaclass__,Python就會用內(nèi)置的type來創(chuàng)建這個類對象。
現(xiàn)在的問題就是,你可以在__metaclass__中放置些什么代碼呢?答案就是:可以創(chuàng)建一個類的東西。那么什么可以用來創(chuàng)建一個類呢?type,或者任何使用到type或者子類化type的東東都可以。
8自定義元類
newAttr = {}
forname,valueinfuture_class_attr.items():
ifnotname.startswith("__"):
newAttr[name.upper()] = value
#調(diào)用type來創(chuàng)建一個類
returntype(future_class_name, future_class_parents, newAttr)
classFoo(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
classUpperAttrMetaClass(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 = {}
forname,valueinfuture_class_attr.items():
ifnotname.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方法
returnsuper(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)
#python2的用法
classFoo(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)
1.攔截類的創(chuàng)建
2.修改類
3.返回修改之后的類
二垃圾回收
1小整數(shù)池對象
為了優(yōu)化速度,python使用了小整數(shù)對象池,避免為整數(shù)頻頻申請和銷毀內(nèi)存空間。
Python對小整數(shù)的定義是[-5, 257)這些整數(shù)對象是提前建立好的,不會被垃圾回收。在一個Python的程序中,所有位于這個范圍內(nèi)的整數(shù)使用的都是同一個對象.
同理,單個字母也是這樣的。
但是當(dāng)定義2個相同的字符串時,引用計數(shù)為0,觸發(fā)垃圾回收
2大整數(shù)對象池
每一個對象均創(chuàng)建一個新的對象
3intern機(jī)制
在python中,對于相同的字符串只開辟一個內(nèi)存空間,靠引用計數(shù)來維護(hù)何時釋放。
·小整數(shù)[-5,257)共用對象,常駐內(nèi)存
·單個字符共用對象,常駐內(nèi)存
·單個單詞,不可修改,默認(rèn)開啟intern機(jī)制,共用對象,引用計數(shù)為0,則銷毀
?在字符串中,若含有空格,不開啟intern機(jī)制,不共用對象,引用計數(shù)為0銷毀
大整數(shù)不共用內(nèi)存,引用計數(shù)為0,銷毀
數(shù)值類型和字符串類型在Python中都是不可變的,這意味著你無法修改這個對象的值,每次對變量的修改,實際上是創(chuàng)建一個新的對象
?
4Garbage collection(GC垃圾回收)
①python采用的是引用計數(shù)機(jī)制為主,標(biāo)記-清除和分代收集兩種機(jī)制為輔的策略
python里每一個東西都是對象,它們的核心就是一個結(jié)構(gòu)體:PyObject
typedefstruct_object {
intob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每個對象必有的內(nèi)容,其中ob_refcnt就是做為引用計數(shù)。當(dāng)一個對象有新的引用時,它的ob_refcnt就會增加,當(dāng)引用它的對象被刪除,它的ob_refcnt就會減少
當(dāng)引用計數(shù)為0時,該對象生命就結(jié)束了。
②引用計數(shù)機(jī)制的優(yōu)點:
·簡單
·實時性:一旦沒有引用,內(nèi)存就直接釋放了。不用像其他機(jī)制等到特定時機(jī)。實時性還帶來一個好處:處理回收內(nèi)存的時間分?jǐn)偟搅似綍r。
③引用計數(shù)機(jī)制的缺點:
·維護(hù)引用計數(shù)消耗資源
·循環(huán)引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1與list2相互引用,如果不存在其他對象對它們的引用,list1與list2的引用計數(shù)也仍然為1,所占用的內(nèi)存永遠(yuǎn)無法被回收,這將是致命的。 對于如今的強(qiáng)大硬件,缺點1尚可接受,但是循環(huán)引用導(dǎo)致內(nèi)存泄露,注定python還將引入新的回收機(jī)制。(標(biāo)記清除和分代收集)
5 Ruby與Python垃圾回收
①應(yīng)用程序那顆躍動的心
GC系統(tǒng)所承擔(dān)的工作遠(yuǎn)比"垃圾回收"多得多。實際上,它們負(fù)責(zé)三個重要任務(wù)。它們
· ? ?為新生成的對象分配內(nèi)存
· ? ? 識別那些垃圾對象
? ? ? ·從垃圾對象那回收內(nèi)存
? ? ? ? 我認(rèn)為垃圾回收就是應(yīng)用程序那顆躍動的心。像心臟為身體其他器官提供血液和營養(yǎng)物那樣,垃圾回收器為你的應(yīng)該程序提供內(nèi)存和對象。如果心臟停跳,過不了幾秒鐘人就完了。如果垃圾回收器停止工作或運行遲緩,像動脈阻塞,你的應(yīng)用程序效率也會下降,直至最終死掉。
②一個簡單的例子
classNode:
def__init__(self,val):
self.value=val
print(Node(1))
print(Node(2))
③Python的對象分配
與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)存:
④Python住在衛(wèi)生之家
在內(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的這種垃圾回收算法被稱為引用計數(shù)。
假如我們讓n2引用n1:
'DEF'上的引用數(shù)被python減少了,垃圾回收器立刻回收DEF實例,同時JKL的引用數(shù)已經(jīng)變?yōu)榱?,因為n1和n2都指向它
⑤標(biāo)記-刪除 vs 引用計數(shù)
引用計數(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)。
⑥在Python中的零代(Generation Zero)
我們希望Python的垃圾回收機(jī)制能夠足夠智能去釋放這些對象并回收它們占用的內(nèi)存空間。但是這不可能,因為所有的引用計數(shù)都是1而不是0。Python的引用計數(shù)算法不能夠處理互相指向自己的對象。
Python使用一種不同的鏈表來持續(xù)追蹤活躍的對象。而不將其稱之為“活躍列表”,Python的內(nèi)部C代碼將其稱為零代(Generation Zero)。每次當(dāng)你創(chuàng)建一個對象或其他什么值的時候,Python會將其加入零代鏈表:
現(xiàn)在零代包含了兩個節(jié)點對象。(他還將包含Python創(chuàng)建的每個其他值,與一些Python自己使用的內(nèi)部值。)
⑦檢測循環(huán)引用
從上面可以看到ABC和DEF節(jié)點包含的引用數(shù)為1.有三個其他的對象同時存在于零代鏈表中,藍(lán)色的箭頭指示了有一些對象正在被零代鏈表之外的其他對象所引用。(接下來我們會看到,Python中同時存在另外兩個分別被稱為一代和二代的鏈表)
通過識別內(nèi)部引用,Python能夠減少許多零代鏈表對象的引用計數(shù)。在上圖的第一行中你能夠看見ABC和DEF的引用計數(shù)已經(jīng)變?yōu)榱懔耍@意味著收集器可以釋放它們并回收內(nèi)存空間了。剩下的活躍的對象則被移動到一個新的鏈表:一代鏈表。
⑧Python中的GC閾值
Python什么時候會進(jìn)行這個標(biāo)記過程?隨著你的程序運行,Python解釋器保持對新創(chuàng)建的對象,以及因為引用計數(shù)為零而被釋放掉的對象的追蹤。從理論上說,這兩個值應(yīng)該保持一致,因為程序新建的每個對象都應(yīng)該最終被釋放掉。
當(dāng)然,事實并非如此。因為循環(huán)引用的原因,并且因為你的程序使用了一些比其他對象存在時間更長的對象,從而被分配對象的計數(shù)值與被釋放對象的計數(shù)值之間的差異在逐漸增長。一旦這個差異累計超過某個閾值,則Python的收集機(jī)制就啟動了,并且觸發(fā)上邊所說到的零代算法,釋放“浮動的垃圾”,并且將剩下的對象移動到一代列表。
隨著時間的推移,程序所使用的對象逐漸從零代列表移動到一代列表。而Python對于一代列表中對象的處理遵循同樣的方法,一旦被分配計數(shù)值與被釋放計數(shù)值累計到達(dá)一定閾值,Python會將剩下的活躍對象移動到二代列表。
三 gc模塊
1垃圾回收機(jī)制
Python中的垃圾回收是以引用計數(shù)為主,分代收集為輔。
2導(dǎo)致引用計數(shù)+1的情況
①對象被創(chuàng)建,例如a=123
②對象被引用,例如b=a
③對象作為參數(shù),傳入一個函數(shù)中,例如func(a)
④對象作為一個元素,存儲在容器中,例如list1=[a,a]
3導(dǎo)致引用計數(shù)-1的情況
①對象的別名被顯式銷毀,例如del a
②對象的別名被賦予新的對象,例如a=124
③一個對象離開它的作用域,例如f函數(shù)執(zhí)行完畢時,func函數(shù)中的局部變量(全局變量不會)
④對象所在的容器被銷毀,或從容器中刪除對象
4查看一個對象的引用計數(shù)
import sys
a=‘hello world’
sys.getrefcount(a)
可以查看a對象的引用計數(shù),但比正常計數(shù)大1,因為調(diào)用查看引用計數(shù)的函數(shù)時將a傳入,使a的引用計數(shù)+1
5循環(huán)引用導(dǎo)致內(nèi)存泄漏問題
引用計數(shù)的缺陷是循環(huán)引用的問題
importgc
classClassA():
def__init__(self):
print('object born,id:%s'%str(hex(id(self))))
deff2():
whileTrue:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
delc1
delc2
#把python的gc關(guān)閉
gc.disable()
f2()
執(zhí)行f2(),進(jìn)程占用的內(nèi)存會不斷增大。
·創(chuàng)建了c1,c2后這兩塊內(nèi)存的引用計數(shù)都是1,執(zhí)行c1.t=c2和c2.t=c1后,這兩塊內(nèi)存的引用計數(shù)變成2.
·在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。
·雖然它們兩個的對象都是可以被銷毀的,但是由于循環(huán)引用,導(dǎo)致垃圾回收器都不會回收它們,所以就會導(dǎo)致內(nèi)存泄露。
說明:
垃圾回收后的對象會放在gc.garbage列表里面
·gc.collect()會返回不可達(dá)的對象數(shù)目,4等于兩個對象以及它們對應(yīng)的dict
有三種情況會觸發(fā)垃圾回收:
1.調(diào)用gc.collect(),
2.當(dāng)gc模塊的計數(shù)器達(dá)到閥值的時候。
3.程序退出的時候
6gc模塊常用功能解析
gc模塊提供一個接口給開發(fā)者設(shè)置垃圾回收的選項,上面說到,采用引用計數(shù)的方法管理內(nèi)存的一個缺陷是循環(huán)引用,而gc模塊的一個主要功能是解決循環(huán)引用的問題。
7常用函數(shù)
1、gc.set_debug(flags)設(shè)置gc的debug日志,一般設(shè)置為gc.DEBUG_LEAK
2、gc.collect([generation])顯式進(jìn)行垃圾回收,可以輸入?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的列表
8?gc模塊的自動垃圾回收機(jī)制
必須要import gc模塊,并且is_enable()=True才會啟動自動垃圾回收。
這個機(jī)制的主要作用就是發(fā)現(xiàn)并處理不可達(dá)的垃圾對象。
垃圾回收=垃圾檢查+垃圾回收
例如(488,3,0),其中488是指距離上一次一代垃圾檢查,Python分配內(nèi)存的數(shù)目減去釋放內(nèi)存的數(shù)目,注意是內(nèi)存分配,而不是引用計數(shù)的增加。例如:
printgc.get_count()# (590, 8, 0)
a = ClassA()
printgc.get_count()# (591, 8, 0)
del a
printgc.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):
注意點
gc模塊唯一處理不了的是循環(huán)引用的類都有__del__方法,所以項目中要避免定義__del__方法