Re: [問題] 使用__dict__["__setattr__"]取代現有ꨠ…
※ 引述《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
12/15 00:28, 4F
→
12/15 00:28, , 5F
12/15 00:28, 5F
→
12/15 09:19, , 6F
12/15 09:19, 6F
→
12/15 09:19, , 7F
12/15 09:19, 7F
Python 近期熱門文章
PTT數位生活區 即時熱門文章