6.1 一切皆對(duì)象
1. 運(yùn)算符
運(yùn)算符是可以定義的贝室,運(yùn)算符有如:+、-仿吞、<滑频、>、and茫藏、or等误趴。
我們用特殊方法__add__()來定義加法,__sub__()來定義減法务傲,__mul__()來定義乘法凉当,__or__()來定義或的邏輯運(yùn)算......
定義運(yùn)算符,對(duì)復(fù)雜對(duì)象的處理極其有幫助售葡,例如某種物體的屬性多樣看杭,若只取其中一種屬性進(jìn)行比較而決定物體的好壞,那么物體之間的比較就可以 定義成 其中的一種屬性的比較挟伙。
2.元素引用
如同運(yùn)算符一般楼雹,元素引用的許多形式或函數(shù)都是通過調(diào)用特殊方法來實(shí)現(xiàn)的。有如:
- li[3]→li.__getitem__(3)
- li.insert(3,0)→li.__setitem__(3,0)
-
dict.pop("a")→dict.__delitem__("a")
......
3. 內(nèi)置函數(shù)的實(shí)現(xiàn)
直接列舉一些:
1.len([1,2,3])→[1,2,3].__len__()
2.abs(-1)→(-1).__abs__()
3.int(2.3)→(2.3).__int__()
......
6.2 屬性管理
1.屬性覆蓋
以前我們學(xué)到了繼承,其中提及到屬性覆蓋贮缅,但未進(jìn)行深究榨咐,此刻便稍微對(duì)繼承中的屬性覆蓋進(jìn)行更為深刻的了解。
在理解屬性覆蓋之前谴供,我們需要知道的是Python的__dict__屬性块茁。當(dāng)一個(gè)類或者對(duì)象擁有屬性時(shí),便會(huì)記錄在字典__dict__中桂肌。其中鍵為屬性名数焊,值為對(duì)應(yīng)的屬性值。Python在尋找對(duì)象的屬性時(shí)崎场,會(huì)依照繼承關(guān)系來依次查找__dict__佩耳。
首先,類Object是類層次結(jié)構(gòu)的根類谭跨,Object類是所有類的父類干厚,所以放在最底層,然后到定義的一個(gè)大類即父類(相對(duì)其屬下)饺蚊,父類又擁有子類萍诱,最后到屬于子類的對(duì)象。顯然這是一個(gè)分層管理機(jī)制污呼。當(dāng)我們要調(diào)用某個(gè)屬性時(shí)裕坊,會(huì)采取就近原則,即從對(duì)象往底層搜尋字典__dict__燕酷。正因如此籍凝,某個(gè)屬性可能在不同的層被重復(fù)定義過,于是在向下歷遍的過程中苗缩,取最近的便會(huì)發(fā)生屬性覆蓋饵蒂。
這是屬性覆蓋的原理所在。
查看屬性:
對(duì)象名/類名.__dict__
調(diào)用屬性時(shí)酱讶,Python會(huì)分層查找退盯;但是如果是賦值時(shí),Python只會(huì)在該層進(jìn)行操作泻肯,在__dict__中添加或修改屬性渊迁。
也可以不考慮繼承,直接修改父類的屬性灶挟。
2. 特性
即時(shí)生成屬性的方法之一——特性(property)琉朽,即特殊的屬性。目的是希望修改屬性時(shí)稚铣,依賴其的其他屬性也能同時(shí)變化箱叁。
內(nèi)置函數(shù)property()創(chuàng)建特性:
class num(object):
def __init__(self,value):
self.value = value
def get_neg(self):
return -self.value
def set_neg(self,value):
self.value = -value
def del_neg(self):
print("value also deleted")
del self.value
neg = property(get_neg,set_neg,del_neg,"I'm negative")
#property函數(shù)最多可以加載4個(gè)參數(shù)墅垮,分別用于設(shè)置獲取、修改和刪除特性耕漱,最后一個(gè)為說明文檔
x = num(1.1)
print(x.neg)
x.neg = -22
print(x.value)
print(num.neg.__doc__)
del x.neg
3. __getattr__方法
除了內(nèi)置函數(shù)property來查詢即時(shí)生成的屬性算色,還可以用__getattr__(self,name)方法。當(dāng)__dict無法找到要調(diào)用的屬性時(shí)螟够,就會(huì)調(diào)用__getattr__()來即時(shí)生成該屬性剃允。(__getattr__()只能用來查詢__dict__不存在的屬性,__getattribute__()則可以查詢?nèi)我鈱傩?/strong>)
示例:
class Bird(object):
feather = True
class chicken(Bird):
fly = False
def __init__(self,age):
self.age = age
def __getattr__(self,name):
if name == "adult":
if self.age > 1.0:
return True
else:
return False
else:
raise AttributeError(name)
summer = chicken(2)
print(summer.adult) #True
summer.age = 0.5
print(summer.adult) #False
print(summer.male) #拋出錯(cuò)誤AttributeError
__getattr__()可以將所有即時(shí)生成的屬性集中在一個(gè)函數(shù)內(nèi)進(jìn)行處理齐鲤,其根據(jù)函數(shù)名區(qū)別處理不同的屬性。
另外:__setattr__(self,name,value)和__delattr__(self,name)分別用于修改和刪除屬性椒楣,可用于任意屬性给郊。
Python描述符 (descriptor) 詳解
對(duì)象屬性的訪問順序:
① __getattribute__(), 無條件調(diào)用
② 數(shù)據(jù)描述符:由 ① 觸發(fā)調(diào)用 (若人為的重載了該 __getattribute__() 方法捧灰,可能會(huì)調(diào)職無法調(diào)用描述符)
③ 實(shí)例對(duì)象的字典(若與描述符對(duì)象同名淆九,會(huì)被覆蓋哦)
④ 類的字典
⑤ 非數(shù)據(jù)描述符
⑥ 父類的字典
⑦ __getattr__() 方法
6.3 我是風(fēng)兒,我是沙
1. 動(dòng)態(tài)類型
典型的動(dòng)態(tài)類型的體現(xiàn)就是變量毛俏,變量不需要事先聲明炭庙,其可以任意多次賦值以改變它的值。來一個(gè)簡(jiǎn)單的語(yǔ)句:
a = 1
a是對(duì)象名煌寇,1是對(duì)象焕蹄,對(duì)象名是指向?qū)ο蟮囊茫褪?strong>對(duì)象名相當(dāng)于中介阀溶,因?yàn)閷?duì)象是存儲(chǔ)在內(nèi)存中的實(shí)體腻脏,我們碰不到它,所以要一個(gè)中間人去接觸它银锻。
通過內(nèi)置函數(shù)id()我們可以查看引用指向的是哪個(gè)對(duì)象永品。
如此,重新賦值就是換個(gè)對(duì)象而已击纬,中介不變鼎姐。即變量名是個(gè)隨時(shí)變更指向的引用,自然更振,類型也可以"隨變"炕桨。
Python會(huì)緩存小的整數(shù)和段字符串,可以通過is運(yùn)算來判斷引用之間是否指向同一對(duì)象殃饿。
2. 可變與不可變對(duì)象
一個(gè)對(duì)象可有多個(gè)引用谋作。
引用之間呈獨(dú)立性,即改變一個(gè)引用乎芳,不會(huì)影響其他引用的指向遵蚜。
對(duì)于以下情況:
list2 = [1,2,3]
list1 = list2
list1[0] = 10
print(list2)
并非失去了引用的獨(dú)立性帖池,而是list1和list2依舊指向同一列表,但是list1[0] = 10改變的正是指向的列表中的第一位元素的指向吭净,即只改變了其中一個(gè)元素的指向睡汹,因此指向該列表的對(duì)象名(引用)都會(huì)受到影響。
可變對(duì)象:如列表寂殉、詞典這種自身能發(fā)生改變的對(duì)象囚巴。
不可變對(duì)象:如整數(shù)、浮點(diǎn)數(shù)友扰、字符串彤叉、元組這種不能改變對(duì)象本身,賦值最多只能改變引用的指向的不可變數(shù)據(jù)對(duì)象村怪。
3. 從動(dòng)態(tài)類型看函數(shù)的參數(shù)傳遞
函數(shù)的參數(shù)傳遞秽浇,本質(zhì)上傳遞的是引用。
def nishama(x):
print(id(x))
x = 100
print(id(x))
a = 1
print(id(a))
nishama(a)
print(a)
這里是不可變對(duì)象的操作甚负,其實(shí)就是賦值操作柬焕。
def nishama(x):
x[0] = 10
print(x)
a = [1,2,3]
nishama(a)
print(a) #打印[10,2,3]
這是可變對(duì)象的操作,通過引用操作可變對(duì)象會(huì)影響到其他的引用梭域。
6.4 內(nèi)存管理
1. 引用管理
一個(gè)對(duì)象可以有多個(gè)引用斑举,引用次數(shù)就是來記錄這些引用總數(shù)的〔≌牵可以用標(biāo)準(zhǔn)庫(kù)中sys包中的getrefcount()來查看某個(gè)對(duì)象的引用次數(shù)富玷。
當(dāng)使用某個(gè)引用為參數(shù),傳遞給getrefcount()時(shí)既穆,參數(shù)實(shí)際上是創(chuàng)建了一個(gè)臨時(shí)的引用凌彬。故getrefcount()所得到的結(jié)果會(huì)比期望多1:
from sys import getrefcount
a = [1,2,3]
print(getrefcount(a)) #打印2,即比期望多1循衰,1+1=2
b = a
print(getrefcount(b)) #打印3铲敛,2+1=3
2. 對(duì)象引用對(duì)象
對(duì)于可變對(duì)象,如列表和詞典会钝,對(duì)象是包含對(duì)象的伐蒋。準(zhǔn)確地說,容器對(duì)象包含的是指向各個(gè)元素對(duì)象的引用迁酸。
若在主程序中使用a = 1先鱼,就會(huì)把這個(gè)關(guān)系存入到一個(gè)詞典中,這個(gè)詞典是記錄所有全局引用的奸鬓,可以用globals()來查看這個(gè)詞典焙畔。
當(dāng)一個(gè)對(duì)象a被另外一個(gè)對(duì)象b引用時(shí),a的引用計(jì)數(shù)將增加1:
from sys import getrefcount
a = [1,2,3]
print(getrefcount(a)) #打印2
b = [a,a]
print(getrefcount(a)) #打印4串远,1+1+2=4宏多,引用2次a所以增加2
容器對(duì)象的引用可能會(huì)構(gòu)成很復(fù)雜的拓?fù)浣Y(jié)構(gòu)(其實(shí)可能就是花樣套娃)儿惫,于是可以用objgraph包來描繪其引用關(guān)系:
x = [1,2,3]
y = [x,dict(key1=x)]
z = [y,(x,y)]
import objgraph
objgraph.show_refs([z],filename='ref_topo.png') #第二個(gè)參數(shù)說明繪圖文件名
兩個(gè)對(duì)象相互引用,形成引用環(huán)伸但。
del關(guān)鍵字:1肾请、刪除某個(gè)引用:del a
2、刪除容器中的元素:del a[0]
引用的轉(zhuǎn)移會(huì)使對(duì)象的引用次數(shù)減少更胖,就是有一個(gè)中介人不做你的生意了铛铁,你的收入就少一份。
3. 垃圾回收
Python的垃圾回收機(jī)制即對(duì)于沒有引用次數(shù)的對(duì)象却妨,會(huì)進(jìn)行刪除回收饵逐,將其所占內(nèi)存清除。
垃圾回收的啟動(dòng)機(jī)制:會(huì)設(shè)置一個(gè)關(guān)于分配對(duì)象和取消分配對(duì)象的次數(shù)差值的閾值彪标。一旦高于這個(gè)閾值梳毙,垃圾回收才會(huì)啟動(dòng)【柘拢可以通過gc模塊的get_threshold()方法來查看閾值或改變閾值。(會(huì)返回三個(gè)值萌业,第一個(gè)就是回收啟動(dòng)的閾值坷襟,后面兩個(gè)值是與分代回收相關(guān)的閾值)
手動(dòng)啟動(dòng)回收:使用gc.collect()
上面講的是基礎(chǔ)回收,下面講分代回收生年。
分代回收將對(duì)象分為0婴程、1、2三代抱婉,新建的是0代档叔,經(jīng)歷過一次垃圾回收仍留下的對(duì)象就歸為1代,如果再經(jīng)歷過一次回收依舊留下的對(duì)象就歸為2代蒸绩。垃圾回收啟動(dòng)時(shí)衙四,肯定對(duì)0代全部對(duì)象進(jìn)行掃描。若0代經(jīng)過一定次數(shù)的垃圾回收患亿,就對(duì)0代和1代進(jìn)行掃描清理传蹈。1代也經(jīng)歷了一定次數(shù)的回收,就對(duì)0步藕、1惦界、2代進(jìn)行掃描,全部對(duì)象的掃描咙冗。
前面提到的后兩個(gè)值就是(每n次0代垃圾回收會(huì)配合1次1代的垃圾回收)沾歪,(每n次1代就會(huì)有1次2代清查)。
4. 孤立的引用環(huán)
還記得上上一小節(jié)講的引用環(huán)嗎雾消?它對(duì)垃圾回收機(jī)制帶來很大的麻煩灾搏。簡(jiǎn)而言之挫望,就是引用環(huán)鎖住了一些對(duì)象,使一些對(duì)象的引用次數(shù)不降為0确镊,但是這些對(duì)象又偏偏被刪除了引用士骤。
為了這樣的引用環(huán),Python出了一個(gè)針對(duì)性解決的方法:就是復(fù)制每個(gè)對(duì)象的引用次數(shù)蕾域,記作gc_ref拷肌,假設(shè)每個(gè)對(duì)象i,計(jì)數(shù)就為gc_ref_i旨巷。Python歷遍所有的對(duì)象i巨缘,對(duì)于每個(gè)對(duì)象所引用的對(duì)象j,將相應(yīng)的gc_ref_j減去1采呐,當(dāng)歷遍結(jié)束以后若锁,保留gc_ref不為0的對(duì)象和這些對(duì)象引用的對(duì)象及更下游引用的對(duì)象(一個(gè)套娃有效,它的里面全部套娃就有效)斧吐,其他的進(jìn)行回收又固。
總結(jié):Python是一種動(dòng)態(tài)類型的語(yǔ)言,其對(duì)象與引用分離煤率,內(nèi)置垃圾回收以釋放內(nèi)存仰冠,因是以引用次數(shù)為依據(jù)的簡(jiǎn)單垃圾回收機(jī)制,所以需要對(duì)孤立引用環(huán)的問題進(jìn)行解決蝶糯。