Python進階 -- 魔法方法

魔法方法及其作用

在Python中的內置類型都支持一些統(tǒng)一的函數(shù)接口工三,比如len, print, bool等架谎,這些函數(shù)是如何實現(xiàn)的呢诸狭?當我們建立自己的數(shù)據(jù)類型時券膀,如何表現(xiàn)的很pythonic,也就是讓我們自己的數(shù)據(jù)類型也能支持這些操作呢驯遇?

在Python中有一些以雙下劃線開頭和結尾的方法芹彬,叫做魔法方法,這就是實現(xiàn)這些統(tǒng)一接口的關鍵叉庐。

例如我們想生成一個Classmate類來保存一個班級的名單舒帮,想讓len()能夠取到里面保存的人數(shù),想讓print()能夠打印出名單,想讓bool()返回名單是否為空玩郊,那么如何組織我們的類肢执,讓我們可以像使用python內置類型一樣使用它呢?

_no_value = object()


class Classmate(object):
    def __init__(self, name_lst=_no_value):
        if name_lst == _no_value:
            self._name_lst = list()
        else:
            self._name_lst = list(name_lst)  # 建立拷貝译红,防止改動傳入數(shù)據(jù)

    def __len__(self):
        return len(self._name_lst)

    def __repr__(self):
        return str(self._name_lst)

    def __bool__(self):
        return len(self._name_lst) != 0  # 其實可以不寫预茄,python會自動調用__len__


if __name__ == '__main__':
    c = Classmate(['Xiao Zhang', 'Lao Wang', 'Xiao Zhao'])
    print(c)  # ['Xiao Zhang', 'Lao Wang', 'Xiao Zhao']
    print(bool(c))  # True
    print(len(c))  # 3

在上面的例子中,我們可以看到侦厚,盡管Classmate并非python中內置的類耻陕,我們仍然可以通過實現(xiàn)魔法方法,將它的使用方式和內置類的使用方式統(tǒng)一起來假夺。

常用魔法方法總結

下面總結一下python中常用的魔法方法淮蜈,并給出使用的例子

二元操作符

+   object.__add__(self, other)
-   object.__sub__(self, other)
*   object.__mul__(self, other)
//  object.__floordiv__(self, other)
/   object.__div__(self, other)
%   object.__mod__(self, other)
**  object.__pow__(self, other[, modulo])
<<  object.__lshift__(self, other)
>>  object.__rshift__(self, other)
&   object.__and__(self, other)
^   object.__xor__(self, other)
|   object.__or__(self, other)

我們用一個二維向量的例子來演示其中幾個方法的作用:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __add__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __sub__(self, other):
        return Vector(self._vx - other.get_x(), self._vy - other.get_y())

    def __pow__(self, power, modulo=None):
        return Vector(pow(self._vx, power), pow(self._vy, power))

    def __repr__(self):
        return f"Vector({self._vx}, {self._vy})"

    def get_x(self):
        return self._vx

    def get_y(self):
        return self._vy


if __name__ == '__main__':
    v1 = Vector(3.0, 5.0)
    v2 = Vector(-1.0, 2.0)
    print(v1 + v2)  # Vector(2.0, 7.0)
    print(v1 - v2)  # Vector(2.0, 7.0)
    print(v1 ** 2)  # Vector(9.0, 25.0)

可以看到在定義了__add__, __sub__, __pow__之后,我們自定義的Vector類也可以像python中內置的int, float類型一樣已卷,進行相加梧田、相減、冪次操作了侧蘸。需要注意的是裁眯,在完成二元操作符對應的魔法方法時,我們需要返回一個相同類型的對象讳癌,這是因為我們需要考慮使用者進行連續(xù)加穿稳、連續(xù)減等操作的可能性。

擴展二元操作符

+=  object.__iadd__(self, other)
-=  object.__isub__(self, other)
*=  object.__imul__(self, other)
/=  object.__idiv__(self, other)
//= object.__ifloordiv__(self, other)
%=  object.__imod__(self, other)
**= object.__ipow__(self, other[, modulo])
<<= object.__ilshift__(self, other)
>>= object.__irshift__(self, other)
&=  object.__iand__(self, other)
^=  object.__ixor__(self, other)
|=  object.__ior__(self, other)

擴展二元操作符的使用方法和二元操作符基本一致:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __add__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __iadd__(self, other):
        return Vector(self._vx + other.get_x(), self._vy + other.get_y())

    def __sub__(self, other):
        return Vector(self._vx - other.get_x(), self._vy - other.get_y())

    def __pow__(self, power, modulo=None):
        return Vector(pow(self._vx, power), pow(self._vy, power))

    def __repr__(self):
        return f"Vector({self._vx}, {self._vy})"

    def get_x(self):
        return self._vx

    def get_y(self):
        return self._vy


if __name__ == '__main__':
    v1 = Vector(3.0, 5.0)
    v2 = Vector(-1.0, 2.0)
    v1 += v2
    print(v1)  # Vector(2.0, 7.0)

在定義了__iadd__方法之后晌坤,+=也可以被用于我們的類了

一元操作符

-   object.__neg__(self)
+   object.__pos__(self)
abs()   object.__abs__(self)
~   object.__invert__(self)
complex()   object.__complex__(self)
int()   object.__int__(self)
long()  object.__long__(self)
float() object.__float__(self)
oct()   object.__oct__(self)
hex()   object.__hex__(self)
round() object.__round__(self, n)
floor() object__floor__(self)
ceil()  object.__ceil__(self)
trunc() object.__trunc__(self)

比較函數(shù)

<   object.__lt__(self, other)
<=  object.__le__(self, other)
==  object.__eq__(self, other)
!=  object.__ne__(self, other)
>=  object.__ge__(self, other)
>   object.__gt__(self, other)

類的表示與輸出

str()   object.__str__(self) 
repr()  object.__repr__(self)
len()   object.__len__(self)
hash()  object.__hash__(self) 
bool()  object.__nonzero__(self) 
dir()   object.__dir__(self)
sys.getsizeof() object.__sizeof__(self)

__str__方法與__repr__方法

在類的表示中__str____repr__方法值得一提逢艘,這兩個方法在一定程度上是有重合的:

  • 他們都提供了將對象轉化為某種字符串的方式
  • 當對象沒有實現(xiàn)__str__方法時,會用__repr__方法替代

對于他們的使用方式骤菠,可以簡單概括為:

  • 如果只想要實現(xiàn)其中之一它改,那么實現(xiàn)__repr__
  • 如果想要輸出的結果可讀性更強,那么可以選擇去實現(xiàn)__str__

我們可以看一下下面的例子:

class Vector(object):
    def __init__(self, vx, vy):
        self._vx = vx
        self._vy = vy

    def __repr__(self):
        return f"Repr Vector({self._vx}, {self._vy})"

    def __str__(self):
        return f"Str Vector({self._vx}, {self._vy})"


if __name__ == '__main__':
    v = Vector(3.0, 5.0)
    print(v)  # Vector({self._vx}, {self._vy})
    print("%s" % v)  # Str Vector(3.0, 5.0)
    print("%r" % v)  # Vector({self._vx}, {self._vy})
    print(str(v))  # Vector({self._vx}, {self._vy})

如果我們注釋掉__repr__方法商乎,得到的結果為:

Str Vector(3.0, 5.0)
Str Vector(3.0, 5.0)
<__main__.Vector object at 0x10c4e0eb8>
Str Vector(3.0, 5.0)

如果我們注釋掉__str__方法央拖,得到的結果為:

Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)
Repr Vector(3.0, 5.0)

對比第二行的結果,說明了當沒有__str__方法時鹉戚,會調用__repr__的結果鲜戒,但是當沒有實現(xiàn)__repr__方法時,則會用默認的__repr__輸出類似return "%s(%r)" % (self.__class__, self.__dict__)的結果抹凳。

類容器實現(xiàn)

類容器的實現(xiàn)方法告訴編譯器我們的類將執(zhí)行迭代遏餐、調用、索引等行為:

len()   object.__len__(self)
self[key]   object.__getitem__(self, key)
self[key] = value   object.__setitem__(self, key, value)
從對象取切片 object.__getslice__(self, start, end)
為切片設置值 object.__setslice__(self, start, end, sequence)
刪除切片 object.__delslice__(self, start, end)
del[key] object.__delitem__(self, key)
iter()  object.__iter__(self)
reversed()  object.__reversed__(self)
in操作    object.__contains__(self, item)
字典key不存在時   object.__missing__(self, key)

__getitem__赢底、__setitem__和__delitem__

這三個方法用于從類容器中用鍵取值境输,設置了這三個函數(shù)之后蔗牡,就可以對類容器直接用鍵來取得颖系、修改和刪除里面的鍵值對嗅剖。例如:

class Classmate(object):
    """A demo class which stores some name-age pairs"""

    def __init__(self, **kwargs):
        self._info = kwargs

    def __str__(self):
        return str(self._info)

    def __getitem__(self, item):
        print("__getitem__")
        if item in self._info:
            return self._info[item]
        else:
            print("Name not found")

    def __setitem__(self, key, value):
        print("__setitem__")
        self._info[key] = value

    def __delitem__(self, key):
        print("__delitem__")
        if key in self._info:
            del self._info[key]


c = Classmate(laowang=50, xiaohuang=18, xiaoli=17, laojin=66)
print(c["laowang"])  # 調用__getitem__
c["xinren"] = 20  # 調用__setitem__
del c["xiaohuang"]  # 調用__delitem__
print(c)

同時,在python3中嘁扼,將python2對容器進行切片操作的魔法方法__getslice__信粮、__setslice__和__delslice__也整合到了這三個方法當中。如下例:

class someClass(object):
    """ This is description for the class"""

    def __init__(self, startVal, endVal):
        if endVal > startVal:
            self._lst = list(range(startVal, endVal))
        else:
            self._lst = list(range(0, 10))

    def __getitem__(self, index):
        print("__getitem__")
        if isinstance(index, slice):
            print(self._lst[index.start:index.stop:index.step])

    def __setitem__(self, index, value):
        print("__setitem__")
        if isinstance(index, slice):
            self._lst[index.start:index.stop:index.step] = value

    def __delitem__(self, index):
        print("__delitem__")
        if isinstance(index, slice):
            del self._lst[index.start:index.stop:index.step]

    def __str__(self):
        return str(self._lst)


c = someClass(5, 10)
print(c)
print(c[1:3])
c[1:3] = [10, 11]
print(c)
del c[1:3]
print(c)
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末趁啸,一起剝皮案震驚了整個濱河市强缘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌不傅,老刑警劉巖旅掂,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異访娶,居然都是意外死亡商虐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門崖疤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秘车,“玉大人,你說我怎么就攤上這事劫哼《E浚” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵权烧,是天一觀的道長眯亦。 經常有香客問我,道長般码,這世上最難降的妖魔是什么妻率? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮侈询,結果婚禮上舌涨,老公的妹妹穿的比我還像新娘。我一直安慰自己扔字,他們只是感情好囊嘉,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著革为,像睡著了一般扭粱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上震檩,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天琢蛤,我揣著相機與錄音蜓堕,去河邊找鬼。 笑死博其,一個胖子當著我的面吹牛套才,可吹牛的內容都是我干的。 我是一名探鬼主播慕淡,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼背伴,長吁一口氣:“原來是場噩夢啊……” “哼峰髓!你這毒婦竟也來了傻寂?” 一聲冷哼從身側響起携兵,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徐紧,沒想到半個月后静檬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡浪汪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了死遭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡呀潭,死狀恐怖钉迷,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情钠署,我是刑警寧澤糠聪,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布谐鼎,位于F島的核電站,受9級特大地震影響狸棍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜草戈,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一唐片、第九天 我趴在偏房一處隱蔽的房頂上張望丙猬。 院中可真熱鬧,春花似錦茧球、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至损痰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卢未,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工伟墙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戳葵。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓汉匙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親噩翠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容