Re: [問題] 使用__dict__["__setattr__"]取代現有ꨠ…

看板Python作者 (偶爾想擺爛一下)時間14年前 (2010/12/12 00:42), 編輯推噓3(304)
留言7則, 4人參與, 最新討論串1/1
※ 引述《rexrainbow ( hua)》之銘言: : 操作物件內的__dict__字典相當於操作物件內的屬性存取. 雖然仍有些不同. : obj.__dict__[name] = value # obj.name = value : 然而以 obj.__dict__["__setattr__"] = new_fn 來取代現有的 __setattr__ 時 : 遇到一個問題(使用python2.6): : class aKlass: : var = 0 : def a_fn(self): : print "aKlass.a_fn" : def __setattr__(self, name, value): : print "aKlass.__setattr__",name,value : def new_a_fn(self): : print "new_a_fn" : def new_setattr_fn(self,name,value): : print "new_setattr_fn",name,value : aKlass內含兩個函數, a_fn, __setattr__. : 另準備兩個對應的函數new_a_fn, new_setattr_fn : 希望使用aKlass.__dict__[ ]的方式取代 -- : aObj = aKlass() : aObj.a_fn() : aKlass.__dict__["a_fn"] = new_a_fn : aObj.a_fn() : aObj.var = 1 : aKlass.__dict__["__setattr__"] = new_setattr_fn : aObj.var = 1 : 執行結果: : aKlass.a_fn : new_a_fn : aKlass.__setattr__ var 1 : aKlass.__setattr__ var 1 : aKlass.__dict__["a_fn"] = new_a_fn 的確用new_a_fn取代了原先的a_fn. : 但似乎 aKlass.__dict__["__setattr__"] = new_setattr_fn 沒有達到預期的效果. : 不知原因為何? : (雖然使用 setattr(aKlass,"__setattr__",new_setattr_fn) 就可以取代原函數了.) 你測試的兩個 case 並不算是全等的,如果讓兩者在做法更接近,應該是: aKlass.__dict__["a_fn"] = new_a_fn aObj.a_fn() aKlass.__dict__["__setattr__"] = new_setattr_fn aObj.__setattr__('var', 1) 接下來回到為什麼修改 aKlass.__dict__ 後,在如此的 statement: aObj.var = 1 上看不到作用? 1. 這只會發生在 old-style class。 (new-style class 的 __dict__ 則是 immutable,實際上是個 proxy) 2. aKlass object(不是指 aObj)除了 __dict__ 裡有 reference 指涉原本在 constructing aKlass 過程中所建立的 __setattr__ function(這個 function 是指 aKlass.__setattr__.im_func),aKlass object 本身也有一個欄位存著 該 function 的位址。(可以看看 Python 安裝後一併附的 classobject.h) 我把 CPython 2.6.5 的 classobject.h 部分內容節錄在此: typedef struct { PyObject_HEAD PyObject *cl_bases; /* A tuple of class objects */ PyObject *cl_dict; /* A dictionary */ PyObject *cl_name; /* A string */ /* The following three are functions or NULL */ PyObject *cl_getattr; PyObject *cl_setattr; PyObject *cl_delattr; } PyClassObject; aKlass 在記憶體裡的結構會分別有 __getattr__/__setattr__/__delattr__ 的位址(if any)。 當執行過此 statement: aKlass.__dict__['__setattr__'] = new_setattr 只變更了 cl_dict 所指涉的 dict 的狀態,並沒有變更 cl_setattr 指向另一個 function,而 aObj.var = 1 statement 會直接使用 cl_setattr 的值(一種優化, 省了 dictionary lookup 動作),所以 aObj.var = 1 還是調用了原來的 __setattr__ function。 如果你使用下列的 statement(任一)來變更 __setattr__ 屬性: aKlass.__setattr__ = new_setattr_fn setattr(aKlass, '__setattr__', new_setattr_fn) 則會變更 cl_setattr 欄位指向 new_setattr_fn,所以 aObj.var = 1 有預期中的表現。 接下來我要透過一些手法來變更 aKlass 在記憶體中其 cl_setattr 欄位的值, 看看有什麼效果。 * 有心一試的人請在 console mode 執行 python REPL(interpreter),不要 使用 IDLE 或是 PythonWin Editor 等 IDE 環境。 沿用 aKlass/new_a_fn/new_setattr_fn 的定義。 接下來 import ctypes 套件(已內建於 Python 2.5+) from ctypes import * a = aKlass() wrapper = py_object(aKlass) p = cast(addressof(wrapper), POINTER(POINTER(c_uint32))).contents print id(aKlass.__setattr__.im_func) # address of original __setattr__ # function for x in xrange(10): print x, p[x] 執行完以上的程式碼後,大概可以看到類似如下的 output: 12325616 0 3 1 505362952 2 11210800 3 11416608 4 12313696 5 0 6 12325616 7 0 8 7 9 505362336 這表示 p[6] 是 cl_setattr 欄位(p[5] 是 cl_getattr,p[7] 是 cl_delattr, 兩者皆是 0,因為 aKlass 沒有定義 __getattr__, __delattr__)。 old_setattr_addr = p[6] # 記下原 __setattr__ 的位址 p[6] = id(new_setattr_fn) a.var = 1 # see output p[6] = old_setattr_addr a.var = 2 # see output 以上幾個 statement 的輸出應該如下: new_setattr_fn var 1 aKlass.__setattr__ var 2 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.136.175.158 ※ 編輯: sbrhsieh 來自: 220.136.175.158 (12/12 00:44)

12/12 06:59, , 1F
甚善哉, 領教了.
12/12 06:59, 1F

12/12 07:15, , 2F
推~
12/12 07:15, 2F

12/15 00:26, , 3F
哇賽,玩這麼深,長知識了
12/15 00:26, 3F

12/15 00:28, , 4F
另外請教一下old-style class和new-style class的分野是?
12/15 00:28, 4F

12/15 00:28, , 5F
2.x和3的差別嗎?謝謝
12/15 00:28, 5F

12/15 09:19, , 6F
2.2 開始有 new-style class。以 new-style class 為
12/15 09:19, 6F

12/15 09:19, , 7F
base 者為 new-style class。object 是 new-style class.
12/15 09:19, 7F
文章代碼(AID): #1D0we6yX (Python)
文章代碼(AID): #1D0we6yX (Python)