python風格對象

對象表示形式

python提供了兩種獲取對象字符串表示形式的標準方式python

repr()         //便於開發者理解的方式返回對象的字符串表示形式(通常來講知足obj==eval(repr(obj)))c++

str()           //便於用戶理解的方式返回對象的字符串表示形式函數

要使對象能這兩種內置函數的參數,須要實現__repr__和__str__特殊方法,爲repr()和str()提供支持。爲了給對象提供其餘表示形式,還會用到__bytes__和__format__spa

bytes()      //獲取對象字節序列表示形式code

format()    //特殊格式顯示對象字符串表示orm

構建一個向量類:對象

from array 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))

使用:blog

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》

相關文章
相關標籤/搜索