這里只分析幾個可能會常用到的魔法方法,像__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