1.序列協議
Python的序列協議只需實現__len__和__getitem__方法
首先創建一個 Ve類的升級版
class Ve:
test = 'd'
def __init__(self, components):
self._components = array(self.test, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components) 用於限制輸出, 當元組個數大於6時
components = components[components.find('['):-1] 其餘部分用...代替
return 'Ve({})'.format(components) 參數的列表顯示
def __str__(self):
return str(tuple(self))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self)) 計算模長的函數
def __bool__(self):
return bool(abs(self))
def __len__(self):
return len(self._components)
obj = Ve([1, 2])
print(str(obj), repr(obj), len(obj), abs(obj))
(1.0, 2.0) Ve([1.0, 2.0]) 2 2.23606797749979
2.切片原理
設置切片功能
def __getitem__(self, index):
cls = type(self) type(實例化對象) 獲取實例所屬的類
if isinstance(index, slice): 如果參數爲切片對象
return cls(self._components[index]) 調用類的構造方法,創建新實例化對象
elif isinstance(index, numbers.Integral): 如果參數爲整形索引,獲取其值
return self._components[index]
else:
msg = 'operate error' 否則報錯
raise TypeError(msg)
obj = Ve([1, 2, 3, 4])
print(obj[1])
print(obj[1:3])
print(obj[1, 2])
2.0
(2.0, 3.0)
TypeError: operate error
只有序列纔有索引、切片等功能,而使用索引、切片時會默認調用類的__getitem__方法
3.動態存取屬性
names = 'xyzt' 在類中定義
def __getattr__(self, name):
""
動態獲取屬性,使 xyzt 4個分量分別獲取對象的前4個元素數值
""
cls = type(self)
if len(name) == 1:
pos = cls.names.find(name) 獲取參數的索引位置
if 0 <= pos < len(self._components): 如果索引位置合法
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
return AttributeError(msg.format(cls, name))
def __setattr__(self, name, value):
""
禁止實例化對象屬性值修改或添加新屬性
""
cls = type(self)
if len(name) == 1:
if name in cls.names:
error = 'readonly attribute {attr_name!r}'
elif name.islower(): 不允許添加新屬性
error = "can not set attributes 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__, attr_name=name)
raise AttributeError(msg)
super().__setattr__(name, value) 繼承超類的setattr方法
obj = Ve([1, 2, 3, 4, 5])
print(obj.z)
3.0
小提示,爲什麼要有 setattr 方法
實際上, __getattr__方法是一種後備機制,僅當對象沒有指定名稱的屬性時,python纔會調用這個方法。
如果有像 obj.x = 10 這樣的賦值後,則默認不會調用此函數,所以我們要禁止這種操作
4.優化__eq__方法
優點:減少處理時間和內存用量
代碼
def __eq__(self, other):
return len(self) == len(other) and all(a ==b for a, b in zip(self, other))
zip函數會在最短的操作數耗盡時停止,且支持多個對象--->zip(a, b, c)
5.setitem 方法
完成參數的賦值
def __setitem__(self, dict, key, value):
self.dict[key] = value
知識點—判斷類型
isinstance(x, numbers.Integral) 判斷是否爲整形
isinstance(x, numbers.Real) 判斷是否爲浮點數
isinstance(obj, Hashable) 判斷對象是否可散列
6.繼承
當我們需要繼承內置類型如 List, Dict時, 不要直接繼承
1.內置類型dict的__init__和__update__方法會忽略我們覆蓋的__setitem__方法
2.dict.update方法會忽略我們覆蓋的__getitem__方法
3.需要繼承時我們通常選擇collections.UserList, UserDict
4.實際上, 所有關於索引或切片的獲取值都會調用__getitem__方法; 且不論key是否存在都會返回規定值
區別如下
class Answer(dict):
def __getitem__(self, key):
return 13
a = Answer(s='foo') 如果繼承 collections.UserDict, 則輸出如下
print(a)
t = {}
t.update(a)
print(t)
{'s': 'foo'} {'s': 'foo'}
{'s': 'foo'} {'s': 13}
內置的 dict、list 和 str 類型是Python的底層基礎,因此速度必須快。
這些內置類型由CPython實現,CPython走了捷徑,故意不調用被子類覆蓋的方法
7.解析順序
1.直接在類上調用實例方法時,必須顯式傳入self參數
2.類的__mro__屬性會顯示繼承方法的執行順序
如
class A:
pass
class B:
pass
class C(B, A):
pass
print(C.__mro__)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
8.補充
重新定義 eq 方法, 判斷是否爲類的實例對象
def __eq__(self, other):
if isinstance(other, Ve):
return len(self) == len(other) and all(a == b for a, b in zip(self, other))
else:
return TypeError('not obj')
9.重載運算符
實現向量加法
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
return Ve(a + b for a, b in pairs)
except TypeError:
return NotImplemented
def __radd__(self, other): 反向加法, 備選之用
return self + other
obj = Ve([1, 2, 3, 4, 5])
obj1 = Ve([1, 2, 3, 4])
print(obj + obj1)
(2.0, 4.0, 6.0, 8.0, 5.0)
註釋
1.pairs 是一個生成器, 它會生成(a, b)形式的元組, 其中a來自self, b來自other。
2.使用 fillvalue參數填充較短的那個可迭代對象, 此處用 0 填充
3.構建一個新Ve實例.
乘法同理
增值運算符 +=, *= 不會修改不可變目標,而是新建實例然後重新綁定