Python魔法方法(magic method)細解幾個常用魔法方法(上)


這里只分析幾個可能會常用到的魔法方法,像__new__這種不常用的,用來做元類初始化的或者是__init__這種初始化使用的 每個人都會用的就不介紹了。

其實每個魔法方法都是在對內建方法的重寫,和做像裝飾器一樣的行為。理解這個道理 再嘗試去理解每個細節裝飾器會比較方便。

 

關於__str____repr__:

直接上例子:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __str__(self):
        return 'world is %s str' % self.world

    def __repr__(self):
        return 'world is %s repr' % self.world

t = Test('world_big')
print str(t)
print repr(t)

output:

world is world_big str
world is world_big repr

其實__str__相當於是str()方法 而__repr__相當於repr()方法。str是針對於讓人更好理解的字符串格式化,而repr是讓機器更好理解的字符串格式化。

其實獲得返回值的方法也很好測試,在我們平時使用ipython的時候,在不使用print直接輸出對象的時候,通常調用的就是repr方法,這個時候改寫repr方法可以讓他方便的輸出我們想要知道的內容,而不是一個默認內容。

 

關於__hash____dir__:

其實在實際應用中寫了這么久python,也沒有用到需要這兩個方法出現的地方,但是在有些庫里面是有看到過。

__hash__是hash()方法的裝飾器版本,而__dir__是dir()的裝飾器版本。

上代碼展示一下__hash__用法:

class Test(object):
    def __init__(self, world):
        self.world = world


x = Test('world')
p = Test('world')
print hash(x) == hash(p)
print hash(x.world) == hash(p.world)


class Test2(object):
    def __init__(self, song):
        self.song = song

    def __hash__(self):
        return 1241

x = Test2('popo')
p = Test2('janan')

print x, hash(x)
print p, hash(p)

output:

False
True
<__main__.Test2 object at 0x101b0c590> 1241
<__main__.Test2 object at 0x101b0c4d0> 1241

可以看到這里的hash()方法總是會返回int型的數字。可以用於比較一個唯一的對象,比方說一個不同內存的object不會相當,而相同字符串hash之后就會相等。然后我們通過修改__hash__方法來修改hash函數的行為。讓他總是返回1241,也是可以輕松做到的。

 

另外一個方法是dir(),熟悉python的人都知道dir()可以讓我們查看當前環境下有哪些方法和屬性可以進行調用。如果我們使用dir(object)語法,可以獲得一個對象擁有的方法和屬性。同樣的道理如果我們在類中定義了__dir__(),就可以指定哪些方法和屬性能夠被dir()方法所查看查找到。道理一樣我這里不再貼出代碼了,有興趣的朋友可以自己去試試。

 

關於控制參數訪問的__getattr__, __setattr__, __delattr__, __getattribute__:

 

__getattr__是一旦我們嘗試訪問一個並不存在的屬性的時候就會調用,而如果這個屬性存在則不會調用該方法。

來看一個__getattr__的例子:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __getattr__(self, item):
        return item


x = Test('world123')
print x.world4

output:
world4

這里我們並沒有world4屬性,在找不到屬性的情況下,正常的繼承object的對象都會拋出AtrributeError的錯誤。但是這里我通過__getattr__魔法方法改變了找不到屬性時候的類的行為。輸出了查找的屬性的參數。

 

__setattr__是設置參數的時候會調用到的魔法方法,相當於設置參數前的一個鈎子。每個設置屬性的方法都繞不開這個魔法方法,只有擁有這個魔法方法的對象才可以設置屬性。在使用這個方法的時候要特別注意到不要被循環調用了。

下面來看一個例子:

 

class Test(object):
    def __init__(self, world):
        self.world = world

    def __setattr__(self, name, value):
        if name == 'value':
            object.__setattr__(self, name, value - 100)
        else:
            object.__setattr__(self, name, value)

x = Test(123)
print x.world
x.value = 200
print x.value

output:
123
100

 

這里我們先初始化一個Test類的實例x,通過__init__方法我們可以注意到,會給初始化的world參數進行賦值。這里的self.world = world語句就是在做這個事情。

注意,這里在進行world參數賦值的時候,就是會調用到__setattr__方法。這個例子來看world就是name,而后面的值的world就是value。我在__setattr__里面做了一個行為改寫,我將判斷name 值是'value'的進行特殊處理,把它的value值減少100. 所以輸出了預期的結果。

 

我為什么說__setattr__特別容易出現循環調用?因為任何賦值方法都會走這個魔法方法,如果你在你改寫__setattr__方法里面使用了類似的賦值,又回循環調用回__setattr__方法。例如:

class Test(object):
    def __init__(self, world):
        self.world = world

    def __setattr__(self, name, value):
        self.name = value


x = Test(123)
print x.world

output:
RuntimeError: maximum recursion depth exceeded

這里我們想讓__setattr__執行默認行為,也就是將value賦值給name,和object對象中的同樣方法,做類似的操作。但是這里我們不調用父類__setattr__的方法來實現,做這樣的嘗試得到的結果就是,超過循環調用深度,報錯。因為這里在執行初始化方法self.world = world的時候,就會調用__setattr__方法,而這里的__setattr__方法里面的self.name = value又會調用自身。所以造成了循環調用。所以使用該魔法方法的時候要特別注意。

 

__delattr__的行為和__setattr__特別相似,同樣需要注意的也是循環調用問題,其他都差不多,只是把屬性賦值變成了 del self.name這樣的表示。下面直接上個例子,不再多贅述。

class Test(object):
    def __init__(self, world):
        self.world = world

    def __delattr__(self, item):
        print 'hahaha del something'
        object.__delattr__(self, item)


x = Test(123)
del x.world
print x.world

output:

hahaha del something
Traceback (most recent call last):
File "/Users/piperck/Desktop/py_pra/laplace_pra/practie_01_23/c2.py", line 12, in <module>
print x.world
AttributeError: 'Test' object has no attribute 'world'

可以看到我們將屬性刪除之后,就找不到那個屬性了。但是在刪除屬性的時候調用了__delattr__,我在里面打印了一段話,在執行之前被打印出來了

 

__getattribute____getattr__方法唯一不同的地方是,上面我們已經介紹了__getattr__方法只能在找不到屬性的時候攔截調用,然后進行重載或者加入一些其他操作。但是__getattribute__更加強大,他可以攔截所有的屬性獲取。所以也容易出現我們上面提到的,循環調用的問題。下面上一個例子來說明這個問題:

 

class Test(object):
    def __init__(self, world):
        self.world = world

    def __getattribute__(self, item):
        print 'get_something: %s' % item
        return item


x = Test(123)
print x.world
print x.pp

output:
get_something: world
world
get_something: pp
pp

可以看到,區別於__getattr__只攔截不存在的屬性,__getattribute__會攔截所有的屬性。所以導致了已經被初始化的world值123,也被改寫成了字符串world。而不存在的屬性也被改寫了成了pp。

 

后面可能我會再撰文,闡述一些常用的protocol

 

Reference:

A Guide to Python’s Magic Methods --Rafe Kettler  September 4, 2015 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM