python 魔術(shù)方法

python 魔術(shù)方法

前言

在做python開發(fā)的過程中,我們大家都會(huì)遇到在class(類)中使用雙下劃線的方法,這些都是我們經(jīng)常所說的"魔法"方法.這些方法可以對(duì)類添加特殊的功能,使用恰當(dāng)可以很大的提升我們?cè)陂_發(fā)過程中的便捷性,方便的進(jìn)行擴(kuò)展.

概覽

目前我們常見的魔法方法大致可分為以下幾類:

  • 構(gòu)造與初始化
  • 類的表示
  • 訪問控制
  • 比較操作
  • 容器類操作
  • 可調(diào)用對(duì)象
  • Pickling序列化

我們這次主要介紹這幾類常用魔法方法:

1.構(gòu)造與初始化

__init__
構(gòu)造方法是我們使用頻率最高的魔法方法了劣砍,幾乎在我們定義類的時(shí)候容握,都會(huì)去定義構(gòu)造方法梢杭,它的主要作用就是在初始化一個(gè)對(duì)象時(shí)吐葱,定義這個(gè)對(duì)象的初始值。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


p1 = Person('Jack', 25)
p2 = Person('shuke', 20)

__new__

  1. 事實(shí)上,當(dāng)我們理解了new方法后淤翔,我們還可以利用它來做一些其他有趣的事情蕴茴,比如實(shí)現(xiàn) 設(shè)計(jì)模式中的 單例模式(singleton)
  2. 依照Python官方文檔的說法,new方法主要是當(dāng)你繼承一些不可變的class時(shí)(比如int, str, tuple)因惭, 提供給你一個(gè)自定義這些類的實(shí)例化過程的途徑。還有就是實(shí)現(xiàn)自定義的metaclass
  3. 這個(gè)方法我們一般很少定義绩衷,不過我們?cè)谝恍╅_源框架中偶爾會(huì)遇到定義這個(gè)方法的類蹦魔。實(shí)際上激率,這才是"真正的構(gòu)造方法",它會(huì)在對(duì)象實(shí)例化時(shí)第一個(gè)被調(diào)用勿决,然后再調(diào)用init乒躺,它們的區(qū)別主要如下:
  • new的第一個(gè)參數(shù)是cls,而init的第一個(gè)參數(shù)是self
  • new返回值是一個(gè)實(shí)例低缩,而init沒有任何返回值嘉冒,只做初始化操作
  • new由于是返回一個(gè)實(shí)例對(duì)象,所以它可以給所有實(shí)例進(jìn)行統(tǒng)一的初始化操作
  1. 由于new優(yōu)先于init調(diào)用咆繁,且返回一個(gè)實(shí)例讳推,所以我們可以利用這種特性,每次返回同一個(gè)實(shí)例來實(shí)現(xiàn)一個(gè)單例類:

__new__的作用:

class PositiveInteger(int):

  def __init__(self, value):

    super(PositiveInteger, self).__init__(self, abs(value))

i = PositiveInteger(-3)

print(i)

但運(yùn)行后會(huì)發(fā)現(xiàn)玩般,結(jié)果根本不是我們想的那樣娜遵,我們?nèi)匀坏玫搅?3。這是因?yàn)閷?duì)于int這種不可變的對(duì)象壤短,我們只有重載它的new方法才能起到自定義的作用设拟。
修改后的代碼如下:

class PositiveInteger(int):

  def __new__(cls, value):

    return super(PositiveInteger, cls).__new__(cls, abs(value))

i = PositiveInteger(-3)

print(i)

通過重載new方法,我們實(shí)現(xiàn)了需要的功能.

class g(float):
    """千克轉(zhuǎn)克"""

    def __new__(cls, kg):
        return float.__new__(cls, kg * 2)


# 50千克轉(zhuǎn)為克
a = g(50)
print(a)  # 100.0
print(a + 100)  # 200.0 由于繼承了float久脯,所以可以直接運(yùn)算纳胧,非常方便!

new來實(shí)現(xiàn)單例
因?yàn)轭惷恳淮螌?shí)例化后產(chǎn)生的過程都是通過new來控制的帘撰,所以通過重載new方法跑慕,我們 可以很簡單的實(shí)現(xiàn)單例模式。

# 寫法一
class Singleton(object):
    def __new__(cls):
        # 關(guān)鍵在于這摧找,每一次實(shí)例化的時(shí)候核行,我們都只會(huì)返回這同一個(gè)instance對(duì)象

        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)

        return cls.instance

obj1 = Singleton()
obj2 = Singleton()
obj1.attr1 = 'value1'
print(obj1.attr1, obj2.attr1)
print(obj1 is obj2)

"""
>>>
value1 value1
True
"""
可以看到obj1和obj2是同一個(gè)實(shí)例。

# 寫法二
class Singleton(object):
    """單例"""
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance


class MySingleton(Singleton):
    pass


a = MySingleton()
b = MySingleton()
print(a is b)
"""
>>> 
True
"""

2. del析構(gòu)方法

這個(gè)方法代表析構(gòu)方法蹬耘,也就是在對(duì)象被垃圾回收時(shí)被調(diào)用芝雪。但是請(qǐng)注意,執(zhí)行del x不一定會(huì)執(zhí)行此方法综苔。

由于Python是通過引用計(jì)數(shù)來進(jìn)行垃圾回收的惩系,也就是說,如果這個(gè)實(shí)例還是有被引用到如筛,即使執(zhí)行del銷毀這個(gè)對(duì)象堡牡,但其引用計(jì)數(shù)還是大于0,所以不會(huì)觸發(fā)執(zhí)行del杨刨。
例子:
此時(shí)我們沒有對(duì)實(shí)例進(jìn)行任何操作時(shí)晤柄,del在程序退出后被調(diào)用。

class Person(object):
    def __del__(self):
        print('__del__')

a = Person()
print('exit')

"""
exit
__del__
"""

由于此實(shí)例沒有被其他對(duì)象所引用妖胀,當(dāng)我們手動(dòng)銷毀這個(gè)實(shí)例時(shí)芥颈,del被調(diào)用后程序正常退出惠勒。

class Person(object):
    def __del__(self):
        print('__del__')


a = Person()
b = a  # b引用a
del a  # 手動(dòng)銷毀,不觸發(fā)__del__
print('exit')

"""
exit
__del__
"""

此時(shí)實(shí)例有被其他對(duì)象引用,盡管我們手動(dòng)銷毀這個(gè)實(shí)例浇借,但依然不會(huì)觸發(fā)del方法,而是在程序正常退出后被調(diào)用執(zhí)行怕品。
為了保險(xiǎn)起見妇垢,當(dāng)我們?cè)趯?duì)文件、socket進(jìn)行操作時(shí)肉康,要想安全地關(guān)閉和銷毀這些對(duì)象闯估,最好是在try異常塊后的finally中進(jìn)行關(guān)閉和釋放操作!

3. 類的表示

str/repr
這兩個(gè)魔法方法一般會(huì)放到一起進(jìn)行講解吼和,它們的主要差別為:

str強(qiáng)調(diào)可讀性涨薪,而repr強(qiáng)調(diào)準(zhǔn)確性/標(biāo)準(zhǔn)性
str的目標(biāo)人群是用戶,而repr的目標(biāo)人群是機(jī)器炫乓,它的結(jié)果是可以被執(zhí)行的
%s調(diào)用str方法刚夺,而%r調(diào)用repr方法
來看幾個(gè)例子,了解內(nèi)置類實(shí)現(xiàn)這2個(gè)方法的效果:

>>> a = 'hello'
>>> str(a)
'hello'
>>> '%s' % a   # 調(diào)用__str__
'hello'
>>>
>>> repr(a)     # 對(duì)象a的標(biāo)準(zhǔn)表示末捣,也就是a是如何創(chuàng)建的
"'hello'"
>>> '%r' % a    # 調(diào)用__repr__ 
"'hello'"
>>>
>>>
>>> import datetime
>>> b = datetime.datetime.now()
>>> str(b)
'2018-05-03 19:08:45.921879'
>>> print(b)     # 等同于print str(b)
2018-05-03 19:08:45.921879
>>>
>>>
>>> repr(b)     # 展示對(duì)象b的標(biāo)準(zhǔn)創(chuàng)建方式(如何創(chuàng)建的 
'datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)'
>>> b
datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)
>>>
>>> c = eval(repr(b))
>>> c
datetime.datetime(2018, 5, 3, 19, 8, 45, 921879)

從上面的例子可以看出這兩個(gè)方法的主要區(qū)別侠姑,在實(shí)際中我們定義類時(shí),一般這樣定義即可:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        # 格式化箩做,友好對(duì)用戶展示
        return 'name: %s, age: %s' % (self.name, self.age)

    def __repr__(self):
        # 標(biāo)準(zhǔn)化展示
        return "Person('%s', %s)" % (self.name, self.age)


person = Person('zhangsan', 20)
print(str(person))      # name: zhangsan, age: 20
print('%s' % person)    # name: zhangsan, age: 20
print(repr(person))     # Person('zhangsan', 20)
print('%r' % person)    # Person('zhangsan', 20)

"""
name: zhangsan, age: 20
name: zhangsan, age: 20
Person('zhangsan', 20)
Person('zhangsan', 20)
"""

這里值得注意的是莽红,如果只定義了strrepr其中一個(gè),那會(huì)是什么結(jié)果邦邦?

如果只定義了str_安吁,那么repr(person)輸出<main.Person object at 0x10783b400>
如果只定義了repr,那么str(person)與repr(person)結(jié)果是相同的
也就是說燃辖,repr在表示類時(shí)鬼店,是一級(jí)的,如果只定義它黔龟,那么str = repr薪韩。
str展示類時(shí)是次級(jí)的,用戶可自定義類的展示格式捌锭,如果沒有定義repr俘陷,那么repr(person)將會(huì)展示缺省的定義。

4. 對(duì)象判斷

hash/eq
hash方法返回一個(gè)整數(shù)观谦,用來表示該對(duì)象的唯一標(biāo)識(shí)拉盾,配合eq方法判斷兩個(gè)對(duì)象是否相等(==):

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __repr__(self):
        return 'Person(%s)' % self.uid

    def __hash__(self):
        return self.uid

    def __eq__(self, other):
        return self.uid == other.uid


p1 = Person(1)
p2 = Person(1)
print(p1 == p2)
p3 = Person(2)
print(set([p1, p2, p3]))  # 根據(jù)唯一標(biāo)識(shí)去重輸出 set([Person(1), Person(2)])

"""
True
{Person(1), Person(2)}
"""

如果我們需要判斷兩個(gè)對(duì)象是否相等,只要我們重寫hasheq方法就可以完成此功能豁状。此外使用set存放這些對(duì)象時(shí)捉偏,會(huì)根據(jù)這兩個(gè)方法進(jìn)行去重操作倒得。

5. 對(duì)象布爾判斷

bool
當(dāng)調(diào)用bool(obj)時(shí),會(huì)調(diào)用bool方法夭禽,返回True/False霞掺。

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __bool__(self):
        return self.uid > 10

p1 = Person(1)
p2 = Person(15)
print(bool(p1))  # False
print(bool(p2))  # True

"""
False
True
"""

??: 在Python3中,nonzero被重命名bool

6. 訪問控制

訪問控制相關(guān)的魔法方法讹躯,主要涉及以下幾個(gè):

setattr:通過.設(shè)置屬性或setattr(key, value)
getattr:訪問不存在的屬性
delattr:刪除某個(gè)屬性
getattribute:訪問任意屬性或方法
來看一個(gè)完整的例子:


class Person(object):
    def __setattr__(self, key, value):
        """屬性賦值"""
        if key not in ('name', 'age'):
            return
        if key == 'age' and value < 0:
            raise ValueError()
        super(Person, self).__setattr__(key, value)

    def __getattr__(self, key):
        """訪問某個(gè)不存在的屬性"""
        return 'unknown'

    def __delattr__(self, key):
        """刪除某個(gè)屬性"""
        if key == 'name':
            raise AttributeError()
        super(Person, self).__delattr__(key)

    def __getattribute__(self, key):
        """所有屬性/方法調(diào)用都經(jīng)過這里"""
        if key == 'money':
            return 100
        if key == 'hello':
            return self.say
        return super(Person, self).__getattribute__(key)

    def say(self):
        return 'hello'

p1 = Person()
p1.name = 'zhangsan'  # 調(diào)用__setattr__
p1.age = 20  # 調(diào)用__setattr__
print(p1.name)  # zhangsan
print(p1.age)  # 20
setattr(p1, 'name', 'lisi')  # 調(diào)用__setattr__
setattr(p1, 'age', 30)  # 調(diào)用__setattr__
print(p1.name)  # lisi
print(p1.age)  # 30

p1.gender = 'male'  # __setattr__中忽略對(duì)gender賦值
print(p1.gender)  # gender不存在,調(diào)用__getattr__返回:unknown
print(p1.money)  # money不存在,在__getattribute__中返回100
print(p1.say())  # hello
print(p1.hello())  # hello,調(diào)用__getattribute__菩彬,間接調(diào)用say方法

del p1.name  # __delattr__中引發(fā)AttributeError
p2 = Person()
p2.age = -1  # __setattr__中引發(fā)ValueError
  1. setattr
    通過此方法,對(duì)象可在在對(duì)屬性進(jìn)行賦值時(shí)進(jìn)行控制潮梯,所有的屬性賦值都會(huì)經(jīng)過它骗灶。
    一般常用于對(duì)某些屬性賦值的檢查校驗(yàn)邏輯,例如age不能小于0秉馏,否則認(rèn)為是非法數(shù)據(jù)等等耙旦。
  2. getattr
    很多同學(xué)以為此方法是和setattr完全對(duì)立的,其實(shí)不然萝究!
    這個(gè)方法只有在訪問某個(gè)不存在的屬性時(shí)才會(huì)被調(diào)用免都,看上面的例子,由于gender屬性在賦值時(shí)帆竹,忽略了此字段的賦值操作琴昆,所以此屬性是沒有被成功賦值給對(duì)象的。當(dāng)訪問這個(gè)屬性時(shí)馆揉,getattr被調(diào)用业舍,返回unknown。
  3. del
    刪除對(duì)象的某個(gè)屬性時(shí)升酣,此方法被調(diào)用舷暮。一般常用于某個(gè)屬性必須存在,否則無法進(jìn)行后續(xù)的邏輯操作噩茄,會(huì)重寫此方法下面,對(duì)刪除屬性邏輯進(jìn)行檢查和校驗(yàn)。
  4. getattribute
    這個(gè)方法我們很少用到绩聘,它與getattr很容易混淆沥割。它與前者的區(qū)別在于:
    getattr訪問某個(gè)不存在的屬性被調(diào)用,getattribute訪問任意屬性被調(diào)用
    getattr只針對(duì)屬性訪問凿菩,getattribute不僅針對(duì)所有屬性訪問机杜,還包括方法調(diào)用

7. Python的類下面的item系列

xxxitem:使用 [''] 的方式操作屬性時(shí)被調(diào)用
setitem:每當(dāng)屬性被賦值的時(shí)候都會(huì)調(diào)用該方法,因此不能再該方法內(nèi)賦值 self.name = value 會(huì)死循環(huán)
getitem:當(dāng)訪問不存在的屬性時(shí)會(huì)調(diào)用該方法
delitem:當(dāng)刪除屬性時(shí)調(diào)用該方法

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/5/2

class A(object):
    def __init__(self):
        self['B'] = "BB"
        self['D'] = "DD"
        del self['B']

    def __setitem__(self, name, value):
        '''''
        @summary: 每當(dāng)屬性被賦值的時(shí)候都會(huì)調(diào)用該方法衅谷,因此不能再該方法內(nèi)賦值 self.name = value 會(huì)死循環(huán)
        '''
        print("__setitem__:Set %s Value %s" % (name, value))
        self.__dict__[name] = value

    def __getitem__(self, name):
        '''''
        @summary: 當(dāng)訪問不存在的屬性時(shí)會(huì)調(diào)用該方法
        '''
        print("__getitem__:No attribute named '%s'" % name)
        return 123

    def __delitem__(self, name):
        '''''
        @summary: 當(dāng)刪除屬性時(shí)調(diào)用該方法
        '''
        print("__delitem__:Delect attribute '%s'" % name)
        del self.__dict__[name]
        print(self.__dict__)


if __name__ == "__main__":
    X = A()
    X['bb'] = "BB"
    print(X.__dict__)

"""
>>> 
__setitem__:Set B Value BB
__setitem__:Set D Value DD
__delitem__:Delect attribute 'B'
{'D': 'DD'}
__setitem__:Set bb Value BB
{'D': 'DD', 'bb': 'BB'}
"""

越是強(qiáng)大的魔法方法椒拗,責(zé)任越大,如果你不能正確使用它,最好還是不用為好蚀苛,否則在出現(xiàn)問題時(shí)很難排查!

參考原文
魔術(shù)方法二
延伸

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末在验,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子堵未,更是在濱河造成了極大的恐慌腋舌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渗蟹,死亡現(xiàn)場(chǎng)離奇詭異块饺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拙徽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門刨沦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诗宣,“玉大人膘怕,你說我怎么就攤上這事≌倥樱” “怎么了岛心?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長篮灼。 經(jīng)常有香客問我忘古,道長,這世上最難降的妖魔是什么诅诱? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任髓堪,我火速辦了婚禮,結(jié)果婚禮上娘荡,老公的妹妹穿的比我還像新娘干旁。我一直安慰自己,他們只是感情好炮沐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布争群。 她就那樣靜靜地躺著,像睡著了一般大年。 火紅的嫁衣襯著肌膚如雪换薄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天翔试,我揣著相機(jī)與錄音轻要,去河邊找鬼。 笑死垦缅,一個(gè)胖子當(dāng)著我的面吹牛伦腐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播失都,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柏蘑,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼幸冻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咳焚,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤洽损,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后革半,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碑定,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年又官,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了延刘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡六敬,死狀恐怖碘赖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情外构,我是刑警寧澤普泡,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站审编,受9級(jí)特大地震影響撼班,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垒酬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一砰嘁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勘究,春花似錦矮湘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至走净,卻和暖如春券时,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伏伯。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工橘洞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人说搅。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓炸枣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子适肠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容