python中实例动态绑定的方法访问私有方法

python有一个编译好的模块,需要增加一个方法。由于不想修改源代码再编译,所以使用动态绑定方法来给实例增加方法。

第一印象,想到使用如下方法:

def foo(self):
	print self.name

a = A()
a.foo = foo
>>> a.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 argument (0 given)

结果是无法访问实例的变量。 比较新绑定的方法与原有的实例方法,发现原有的实例方法是bound method。只有bound method才能访问实例的变量。

要动态为实例绑定方法,可以使用new模块(http://docs.python.org/library/new.html)。 (文档中说new模块已经过期,推荐使用types模块。但我看types的文档,想不明白如何取代new模块)

import new
a.foo = new.instancemethod(foo, a, A)

问题又来了,新加的方法里有调用实例的私有函数(以双下划线开头),报了如下错误:

class A():
    def __private(self):
        print "private"

    def public(self):
        self.__private()

def foo(self):
    self.__private()

a = A()
import new
a.foo = new.instancemethod(foo, a, A)
a.foo()
Traceback (most recent call last):
  File "E:tmptest.py", line 14, in <module>
    a.foo()
  File "E:tmptest.py", line 9, in foo
    self.__private()
AttributeError: A instance has no attribute '__private'

通过观察原有方法和动态绑定方法的字节码,发现LOAD_ATTR有差别。原有方法的LOAD_ATTR是“_A__private”,动态绑定的方法的LOAD_ATTR是“__private”

class A():
    def __private(self):
        print "private"

    def public(self):
        self.__private()

def foo(self):
    self.__private()

a = A()
import dis
dis.dis(a.public)
a.public()

import new
a.foo = new.instancemethod(foo, a, A)
dis.dis(a.foo)
a.foo()
  6           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (_A__private) # a.public
              6 CALL_FUNCTION            0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
private
  9           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (__private) # a.foo
              6 CALL_FUNCTION            0
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

这里的原因是python会对private方法进行名字粉碎(name mangling) 。因此修改foo方法里面的调用为self._A__private(),通过。但这样修改后,方法对于不同的class会不通用,有待研究更好的方法


  • Lotaku

    现在,types 的用法可以参考:http://wiki.woodpecker.org.cn/moin/IntroMixin