操作物件內的__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) 就可以取代原函數了.)
A:
你測試的兩個 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
沒有留言:
張貼留言