1. 程式人生 > >『流暢的Python』第9章_對象

『流暢的Python』第9章_對象

form 大量 ash 名稱 format spa ots 數字 3.1

一、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對象需要三個條件:

  1. 需要__hash__方法
  2. 需要__eq__方法(已經實現)
  3. 需要對象不可變  # 實例的散列值關乎查找等使用方式,絕對不可以變化

也就是我們指定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是作者推薦的方式,因為可以保證信息不被改動。使用它可以有效節約存儲空間,尤其是需要創建大量實例的時候(運行速度往往也更快)。

  1. 繼承會自動忽略__slot__屬性,所以子類需要顯式的定義它
  2. 定義了__slots__後,用戶不可以自行添加其他實例屬性,但是如果把__dict__存儲在__slots__中,就可以添加了,不過就完全沒意義了……
  3. 如果想要支持弱引用,需要手動將__weakref__添加進來,雖然自定義class默認存在__weakref__屬性,但是想要讓實例成為弱引用目標還是需要添加進來才可以。

『流暢的Python』第9章_對象