『流暢的Python』第9章_對象
一、Python風格
以一個二元素向量對象為例
import math from array import array class Vector2d: typecode = ‘d‘ def __init__(self, x, y): self.x = float(x) self.y = float(y) def __iter__(self): # 使得Vector2d變成可叠代對象 # __iter__方法的實現使得本類可以被轉化為tuple在內的其他可叠代類 return (i for i in (self.x, self.y)) def __repr__(self): class_name = type(self).__name__ # type(self): <class ‘__main__.Vector2d‘> return ‘{}({!r},{!r})‘.format(class_name, *self) def __str__(self): return str(tuple(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)) def __bytes__(self): """將Vector2d對象處理為二進制序列,格式我們自定""" # d:double類型數組 return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) # —————備用析構方法—————— @classmethod # 類方法,cls表示類本身 def frombytes(cls, octets): """對應於上面的方法,這裏創建一個新的析構函數,使用特定的二進制序列構造Vector2d類實例""" typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) # 類名(參數),可見,類方法常用作備用析構 # —格式化輸出— def angle(self): # math.atan(scope)輸入為tan值 # math.atan2(y, x)輸入為對應向量坐標(起點為原點) return math.atan2(self.y, self.x) def __format__(self, fmt_spec=‘‘): """格式化輸出,如果格式末尾為p則輸出極坐標, 輸入其他格式為數字型格式,一個輸入格式指定到兩個數上,如:.3ep""" if fmt_spec.endswith(‘p‘): fmt_spec = fmt_spec[:-1] coords = (abs(self), self.angle()) out_fmt = ‘<{}, {}>‘ else: coords = self out_fmt = ‘({}, {})‘ components = (format(c, fmt_spec) for c in coords) return out_fmt.format(*components)
此時這個對象支持大部分python操作,
if __name__ == ‘__main__‘: b = bytes(Vector2d(3, 4)) print(Vector2d.frombytes(b)) print(format(Vector2d(1, 1), ‘.5fp‘))
(3.0, 4.0)
<1.41421, 0.78540>
但是一個重要的方法還是沒能實現,__hash__,這關乎到對象是否可以被存入字典進行高速讀取的屬性,實際上可以hash對象需要三個條件:
-
需要__hash__方法
-
需要__eq__方法(已經實現)
-
需要對象不可變 # 實例的散列值關乎查找等使用方式,絕對不可以變化
也就是我們指定v.x=1(v為class實例)會報錯才行,這需要一些其他操作:
class Vector2d: typecode = ‘d‘ def __init__(self, x, y): self.__x = float(x) self.__y = float(y) @property def x(self): return self.__x @property def y(self): return self.__y def __hash__(self): return hash(self.x) ^ hash(self.y)
其他方法不需要修改,
v1 = Vector2d(3, 4)
v2 = Vector2d(3.1, 4.2)
print(hash(v1), hash(v2))
# 7 384307168202284039
二、類方法和靜態方法
# —對比類方法和靜態方法— class Demo: @classmethod def klassmeth(*args): return args @ staticmethod def statmeth(*args): return args def normal(*args): return args
和實例方法不同,類方法第一個參數永遠是類本身,所以常用於備用析構,靜態方法沒有默認的首位參數,測試如下:
print(Demo.klassmeth("hello"))
print(Demo.statmeth("hello"))
demo = Demo()
print(demo.normal("hello"))# (<class ‘__main__.Demo‘>, ‘hello‘)
# (‘hello‘,)
# (<__main__.Demo object at 0x000000000289F978>, ‘hello‘)
三、私有屬性和受保護屬性
兩個前導下劃線"__",一個或者沒有後置下劃線的實例屬性(self.屬性名)為私有變量,會被存入__dict__中,且名稱被改寫為"_類名__屬性名",主要目的是防止類被繼承以後,子類實例的繼承屬性(未在子類中顯式的聲明)被錯誤的改寫。
註意,__dict__不僅僅存儲私有變量,實例屬性均存放在__dict__中(默認情況下)。
四、__slots__類屬性節約存儲空間
class Vector2d: __slots__ = (‘__x‘, ‘__y‘) typecode = ‘d‘
類屬性__slots__為一個存儲字符串的可叠代對象,其中的各個字符串是不同的實例屬性名,使用tuple是作者推薦的方式,因為可以保證信息不被改動。使用它可以有效節約存儲空間,尤其是需要創建大量實例的時候(運行速度往往也更快)。
- 繼承會自動忽略__slot__屬性,所以子類需要顯式的定義它
- 定義了__slots__後,用戶不可以自行添加其他實例屬性,但是如果把__dict__存儲在__slots__中,就可以添加了,不過就完全沒意義了……
- 如果想要支持弱引用,需要手動將__weakref__添加進來,雖然自定義class默認存在__weakref__屬性,但是想要讓實例成為弱引用目標還是需要添加進來才可以。
『流暢的Python』第9章_對象