Python進階----Fifth

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實例.

乘法同理

增值運算符 +=, *= 不會修改不可變目標,而是新建實例然後重新綁定
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章