摘要:先從最初的腦洞開始吧。這樣做的目的在于,把調用過程中的狀態存儲起來,借此實現帶狀態的調用。這種實例我們稱之為函數對象。在里面也有同樣的機制。對于真正的而言,肯定不會用這種的判斷方式。
有一天閑著無聊的時候,腦子里突然冒出一個Magic Method的有趣用法,可以用__getattr__來實現Python版的method_missing。
順著這個腦洞想下去,我發現Python的Magic Method確實有很多妙用之處。故在此記下幾種有趣(也可能有用的)Magic Method技巧,希望可以拋磚引玉,打開諸位讀者的腦洞,想出更加奇妙的用法。
如果對Magic Method的了解僅僅停留在知道這個術語和若干個常用方法上(如__lt__,__str__,__len__),可以閱讀下這份教程,看看Magic Method可以用來做些什么。
Python method_missing先從最初的腦洞開始吧。曾幾何時,Ruby社區的人總是夸耀Ruby的強大的元編程能力,其中method_missing更是不可或缺的特性。通過調用BaseObject上的method_missing,Ruby可以實現在調用不存在的屬性時進行攔截,并動態生成對應的屬性。
Ruby例子
# 來自于Ruby文檔: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-method_missing class Roman def roman_to_int(str) # ... end def method_missing(methId) str = methId.id2name roman_to_int(str) end end r = Roman.new r.iv #=> 4 r.xxiii #=> 23 r.mm #=> 2000
method_missing的應用是如此地廣泛,以至于只要是成規模的Ruby庫,多多少少都會用到它。像是ActiveRecord就是靠這一特性去動態生成關聯屬性。
其實Python一早就內置了這一功能。Python有一個Magic Method叫__getattr__,它會在找不到屬性的時候調用,正好跟Ruby的method_missing是一樣的。
我們可以這樣動態添加方法:
class MyClass(object): def __getattr__(self, name): """called only method missing""" if name == "missed_method": setattr(self, name, lambda : True) return lambda : True myClass = MyClass() print(dir(myClass)) print(myClass.missed_method()) print(dir(myClass))
于是乎,前面的Ruby例子可以改寫成下面的Python版本:
class Roman(object): roman_int_map = { "i": 1, "v": 5, "x": 10, "l": 50, "c":100, "d": 500, "m": 1000 } def roman_to_int(self, s): decimal = 0 for i in range(len(s), 0, -1): if (i == len(s) or self.roman_int_map[s[i-1]] >= self.roman_int_map[s[i]]): decimal += self.roman_int_map[s[i-1]] else: decimal -= self.roman_int_map[s[i-1]] return decimal def __getattr__(self, s): return self.roman_to_int(s) r = Roman() print(r.iv) r.iv #=> 4 r.xxiii #=> 23 r.mm #=> 2000
很有可能你會覺得這個例子沒有什么意義,你是對的!其實它就是把方法名當做一個羅馬數字字符串,傳入roman_to_int而已。不過正如遞歸不僅僅能用來計算斐波那契數列,__getattr__的這一特技實際上還是挺有用的。你可以用它來進行延時計算,或者方法分派,抑或像基于Ruby的DSL一樣動態地合成方法。這里有個用__getattr__實現延時加載的例子。
函數對象在C++里面,你可以重載掉operator (),這樣就可以像調用函數一樣去調用一個類的實例。這樣做的目的在于,把調用過程中的狀態存儲起來,借此實現帶狀態的調用。這種實例我們稱之為函數對象。
在Python里面也有同樣的機制。如果想要存儲的狀態只有一種,你需要的是一個生成器。通過send來設置存儲的狀態,通過next來獲取調用的結果。不過如果你需要存儲多個不同的狀態,生成器就不夠用了,非得定義一個函數對象不可。
Python里面可以重載__call__來實現operator ()的功能。下面的例子里面,就是一個存儲有兩個狀態value和called_times的函數對象:
class CallableCounter(object): def __init__(self, initial_value=0, start_times=0): self.value = initial_value self.called_times = start_times def __call__(self): print("Call the object and do something with value %d" % self.value) self.value += 1 self.called_times += 1 def reset(self): self.called_times = 0 cc = CallableCounter(initial_value=5) for i in range(10): cc() print(cc.called_times) cc.reset()偽造一個Dict
最后請允許我奉上一個大腦洞,偽造一個Dict類。(這個可就沒有什么實用價值了)
首先確定下把數據存在哪里。我打算把數據存儲在類的__dict__屬性中。由于__dict__屬性的值就是一個Dict實例,我只需把調用在FakeDict上的方法直接轉發給對應的__dict__的方法。代價是只能接受字符串類型的鍵。
class FakeDict: def __init__(self, iterable=None, **kwarg): if iterable is not None: if isinstance(iterable, dict): self.__dict__ = iterable else: for i in iterable: self[i] = None self.__dict__.update(kwarg) def __len__(self): """len(self)""" return len(self.__dict__) def __str__(self): """it looks like a dict""" return self.__dict__.__str__() __repr__ = __str__
接下來開始做點實事。Dict最基本的功能是給一個鍵設置值和返回一個鍵對應的值。通過定義__setitem__和__getitem__方法,我們可以重載掉[]=和[]。
def __setitem__(self, k, v): """self[k] = v""" self.__dict__[k] = v def __getitem__(self, k): """self[k]""" return self.__dict__[k]
別忘了del方法:
def __delitem__(self, k): """del self[k]""" del self.__dict__[k]
Dict的一個常用用途是允許我們迭代里面所有的鍵。這個可以通過定義__iter__實現。
def __iter__(self): """it iterates like a dict""" return iter(self.__dict__)
Dict的另一個常用用途是允許我們查找一個鍵是否存在。其實只要定義了__iter__,Python就能判斷if x in y,不過這個過程中會遍歷對象的所有值。對于真正的Dict而言,肯定不會用這種O(n)的判斷方式。定義了__contains__之后,Python會優先使用它來判斷if x in y。
def __contains__(self, k): """key in self""" return k in self.__dict__
接下要實現==的重載,不但要讓FakeDict和FakeDict之間可以進行比較,而且要讓FakeDict和正牌的Dict也能進行比較。
def __eq__(self, other): """ implement self == other FakeDict, also implement self == other dict """ if isinstance(other, dict): return self.__dict__ == other return self.__dict__ == other.__dict__
要是繼續實現了__subclass__和__class__,那么我們的偽Dict就更完備了。這個就交給感興趣的讀者自己動手了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37635.html
摘要:也就是說,如果不需要,兩者使用起來并沒有什么分別。來看個例子,先定義個類,里面只有一個成員方法,返回倍的數值使用類來掉這個成員方法使用類來兩者沒有任何區別,都成功了了成員方法。再看下兩者的區別因為使用類時,默認不會創建這個的,所以報錯。 Python的unittest.mock模塊中提供了兩個主要的mock類,分別是Mock和MagicMock. 先看一下官方文檔的定義: MagicM...
摘要:本篇文章總結了目前主流的實現單例模式的方法供讀者參考。使用實現單例模式同樣,我們在類的創建時進行干預,從而達到實現單例的目的。 很多初學者喜歡用 全局變量 ,因為這比函數的參數傳來傳去更容易讓人理解。確實在很多場景下用全局變量很方便。不過如果代碼規模增大,并且有多個文件的時候,全局變量就會變得比較混亂。你可能不知道在哪個文件中定義了相同類型甚至重名的全局變量,也不知道這個變量在程序的某...
摘要:所以準確來說是和共同構成了構造函數是用來創建類并返回這個類的實例而只是將傳入的參數來初始化該實例在創建一個實例的過程中必定會被調用但就不一定,比如通過的方式反序列化一個實例時就不會調用。 前言 在Python中,所有以__雙下劃線包起來的方法,都統稱為魔術方法。比如我們接觸最多的__init__. 有些魔術方法,我們可能以后一輩子都不會再遇到了,這里也就只是簡單介紹下; 而有些魔術方法...
摘要:關于的介紹自行查閱官方文檔,這里不再贅述。使用的同學注意了,如果在我們的代碼中使用到了中相關的魔術方法,需要在文件中指明告訴應該如何來跟蹤變量屬性。下面我們來具體實踐分析。確實這個樣子可以實現,但沒有利用到這一魔術方法的特性。 關于 Magic Methods 的介紹自行查閱官方文檔,這里不再贅述。http://php.net/manual/en/lang... 使用 phpstorm...
摘要:在代碼中可以看到一些常見的,在這里做一個簡單的小結。的妙用在某些場景下我們需要判斷我們是否是從一個循環中跳出來的,并且只針對跳出的情況做相應的處理。這時候我們通常的做法是使用一個變量來標識是否是從循環中跳出的。 在 python 代碼中可以看到一些常見的 trick,在這里做一個簡單的小結。 json 字符串格式化 在開發 web 應用的時候經常會用到 json 字符串,但是一段比...
閱讀 955·2019-08-30 14:24
閱讀 987·2019-08-30 14:13
閱讀 1799·2019-08-29 17:21
閱讀 2661·2019-08-29 13:44
閱讀 1654·2019-08-29 11:04
閱讀 438·2019-08-26 10:44
閱讀 2564·2019-08-23 14:04
閱讀 908·2019-08-23 12:08