作者sbrhsieh (偶尔想摆烂一下)
看板Python
标题Re: [问题] 使用__dict__["__setattr__"]取代现有ꨠ…
时间Sun Dec 12 00:42:43 2010
※ 引述《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)
1F:推 jacobcrab:甚善哉, 领教了. 12/12 06:59
2F:推 rexrainbow:推~ 12/12 07:15
3F:推 cobrasgo:哇赛,玩这麽深,长知识了 12/15 00:26
4F:→ cobrasgo:另外请教一下old-style class和new-style class的分野是? 12/15 00:28
5F:→ cobrasgo:2.x和3的差别吗?谢谢 12/15 00:28
6F:→ sbrhsieh:2.2 开始有 new-style class。以 new-style class 为 12/15 09:19
7F:→ sbrhsieh:base 者为 new-style class。object 是 new-style class. 12/15 09:19