python風格對象
對象表示形式
python提供了兩種獲取對象字符串表示形式的標準方式
repr() //便於開發者理解的方式返回對象的字符串表示形式(一般來說滿足obj==eval(repr(obj)))
str() //便於用戶理解的方式返回對象的字符串表示形式
要使對象能這兩種內置函數的參數,需要實現__repr__和__str__特殊方法,為repr()和str()提供支持。為了給對象提供其他表示形式,還會用到__bytes__和__format__
bytes() //獲取對象字節序列表示形式
format() //特殊格式顯示對象字符串表示
構建一個向量類:
fromarray import array import math class Vector: typecode = ‘d‘ def __init__(self, x, y): self.x = float(x) self.y = float(y) def __iter__(self): #將類實例變為可叠代對象 return (i for i in (self.x, self.y)) def __repr__(self): #構成供開發者使用的字符串 class_name = type(self).__name__return ‘{}({!r}, {!r})‘.format(class_name, *self) def __str__(self): #構成供用戶使用的字符串 return str(tuple(self)) def __bytes__(self): #將對象實例轉為字節序列 return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) def __eq__(self, other): #實現 ==return tuple(self) == tuple(other) def __abs__(self): #計算模長 return math.hypot(self.x, self.y) def __bool__(self): #零向量 return bool(abs(self))
使用:
if __name__ == ‘__main__‘: V = Vector(3, 4) print(V.x, V.y) x, y = V #是可叠代對象,故可以元組拆包 print((x, y)) repr_V = repr(V) #字符串表示 print(repr_V) print(eval(repr_V) == V) #執行這個字符串,打印結果 octets = bytes(V) print(octets) print(abs(V)) #打印模長 print((bool(V), bool(Vector(0, 0)))) #零向量bool返回False 3.0 4.0 (3.0, 4.0) Vector(3.0, 4.0) True b‘d\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@‘ 5.0 (True, False)
classmethod與staticmethod
在上例中,可以使用bytes()將對象實例轉化為字節序列:
def __bytes__(self): #將對象實例轉為字節序列 return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
現使用classmethod裝飾器來實現字節序列轉對象實例的方法:
@classmethod def frombytes(cls, octets): typecode = (chr(octets[0])) memv = memoryview(octets[1:]).cast(typecode) #使用傳入的字節序列創建視圖,使用typecode轉換 return cls(*memv)
使用:
vec = Vector.frombytes(octets) #調用者是vector類 print(vec) (3.0, 4.0)
classmethod是類的方法,而不是實例的方法,它改變了調用方法的方式,因此類方法的第一個參數是類本身而不是實例。(類似c++靜態方法)
而staticmethod裝飾器也改變方法調用方式,但第一個參數不是特殊的值。其實,靜態方法就是普通的函數。
class Demo: @classmethod def klassmeth(*args): return args #返回全位置參數 @staticmethod def statmeth(*args): return args #返回全位置參數 if __name__ == ‘__main__‘: print(Demo.klassmeth()) print(Demo.klassmeth(‘spam‘)) print(Demo.statmeth()) print(Demo.statmeth(‘spam‘)) #結果 (<class ‘__main__.Demo‘>,) (<class ‘__main__.Demo‘>, ‘spam‘) #無論如何調用,第一個參數始終是Demo類 () (‘spam‘,) #行為類似於普通函數
格式化顯示
內置format()函數和str.format()方法把各個類型的格式化方式委托給相應的.__format__(format_spec)方法。format_spec是格式說明符,它是以下之一:
1.format(my_obj,format_spec)的第二個參數
2.str.format()方法的格式字符串,{}裏代換字段中冒號的部分
使用示例:
brl = 1/2.43 print(brl) form1 = format(brl, ‘0.4f‘) #使用前者,格式說明符是0.4f form2 = ‘1 BRL = {rate:0.2f} USE‘.format(rate=brl) #使用後者,代替冒號部分,格式說明符是0.2f print(form1) print(form2) 0.4115226337448559 0.4115 1 BRL = 0.41 USE
格式規範語言為一些內置類型提供了專用的表示代碼,比如b表示二進制int類型,x表示十六進制int類型,f表示小數形式的float類型,%表示百分數形式。
print(format(42, ‘b‘)) print(format(42, ‘x‘)) print(format(2/3, ‘0.1%‘)) print(format(2/3, ‘0.3f‘)) 101010 2a 66.7% 0.667
用戶可自行定義__format__方法,如果沒有,會調用__str__方法返回值。而未定義__format__方法又傳入格式說明符作為參數,將拋出TypeError。
實現可散列的對象
要把類實例變為可散列的對象,必須實現__hash__方法和__eq__方法,而__hash__方法需要保證類對象散列值不變。例如Vector類,則需要讓x,y是只可讀類型。
class Vector: typecode = ‘d‘ def __init__(self, x, y): self.__x = float(x) #使用雙下劃線把屬性標記為私有 self.__y = float(y) @property def x(self): return self.__x #使用property裝飾器將讀值方法標記為特性,即可以使用obj.x獲取x @property def y(self): return self.__y #同x
之後添加__hash__方法就可以將向量變為可散列的:
def __hash__(self): return hash(self.x) ^ hash(self.y)
實際上只要能夠正確實現__hasn__和__eq__方法並且保證實例散列值不會變化即可。
私有屬性和保護屬性
python不能用private修飾符創建私有屬性,但python有個簡單機制避免子類覆蓋私有屬性。
例如,有人編寫了Dog類,用到了mood實例屬性卻未開放,這時你創建了Dog的子類Beagle,如果你在毫不知情的情況下創建了mood實例屬性,那麽繼承的方法就會覆蓋掉Dog中的mood屬性。出現了問題卻難以發現。
為避免這種情況,如果以__mood命名(前面加雙下劃線,尾部最多有一個下劃線)命名實例屬性,那麽python會把屬性名存入__dict__屬性中,而且會在前面加一個下劃線和類名。對於上例來說父類會變為_Dog__mood而子類會變為_Beagle__mood。這個語言特性叫做名稱改寫。
對於向量類的實例:
print(V.__dict__) {‘_Vector__x‘: 3.0, ‘_Vector__y‘: 4.0}
它的目的是避免意外訪問,卻不能防止故意訪問(做壞事),任何人都能直接讀取私有屬性並且給它賦值。
V._Vector__x = 5.0 print(V) #結果 (5.0, 4.0)
也就是說,它是"私有"卻不是真正的私有,不可變也不是真正的不可變。
同時,有人將單劃線(_xxx)稱為保護屬性。
__solts__
默認情況下,python在各個實例中名為__dict__的字典裏存儲實例屬性。但由於字典底層使用散列表實現,速度快也耗費大量內存。若處理上百萬個屬性不多的實例,可通過__slots__屬性可節省大量內存,它將使用元組而不是字典來存儲實例屬性。
子類不能繼承父類的__slots__屬性,只會使用自己類中定義的。
方式:創建一個類屬性__slots__,將它的值設置為一個字符串構成的可叠代對象,其中各個元素表示各個實例屬性。一般使用元組:
class Vector: __slots__ = (‘__x‘, ‘__y‘)
這個屬性定義是為了告訴解釋器:這個類中所以的實例屬性都保存在這。這樣,python會在各個實例中使用類似元組的結構存儲實例變量。
實例只能擁有__slots__列出的屬性,除非把__dict__屬性加入其中(但這樣做 失去了節省內存的功效)
如果定義了類的__slots__屬性,此時想把實例作為弱引用的目標,需要把__weakref__添加到__slots__屬性中。
覆蓋類屬性
類屬性可以為實例屬性提供默認值。Vector類中使用self.typecode讀取類屬性的值,實例本身沒有類屬性,self.typecode獲取的是類屬性Vector.typecode的值。但是如果為不存在的實例屬性賦值,就會新建實例屬性。
if __name__ == ‘__main__‘: v1 = Vector(1.1, 2.2) dumpd = bytes(v1) print(dumpd) v1.typecode = ‘f‘ #雙精度浮點數表示分量 dumpf = bytes(v1) print(dumpf) print(Vector.typecode) #結果 b‘d\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@‘ b‘f\xcd\xcc\x8c?\xcd\xcc\x0c@‘ d #類屬性沒有變
也可修改類屬性來修改所有實例的typecode默認值
Vector.typecode = ‘f‘
一般使用方式是創建一個子類,在子類中覆蓋掉類屬性。
以上來自《流暢的python》
python風格對象