可以实时修改Python代码吗(像Lisp或Erlang一样)
我在想,是否有可能在运行Python代码的时候修改它,同时保持已经创建的对象和方法的状态,就像我觉得在Lisp或Erlang中可以做到的那样(*)?
比如说,我有一个正在运行的Python会话,在这里我从自己写的模块中实例化了一个foo
类:
class foo():
@classmethod
def do_something(self):
print "this is good"
Python命令行:
>>> f =foo()
>>> f.do_something()
现在,我想把打印语句改成别的东西(比如说print "this is better"
)。如果我修改了模块文件并重新加载它,我就得重新实例化f
对象。有没有办法让我可以直接调用f.do_something()
而不需要先调用f=foo()
呢?
所以,我现在得这样做:
>>> reload my_module
>>> f =foo()
>>> f.do_something() # with changed print statement
但我想这样做:
>>> reload my_module
>>> f.do_something() # with changed print statement
(*) 我之所以这么说,是基于这段很酷的Erlang视频和这段来自实用的Common Lisp的文字:'当这个bug在距离地球一亿英里远的地方出现时,团队能够诊断并修复正在运行的代码,让实验得以完成。'
编辑:我想了想,也许我想要的在面向对象编程中本身就有缺陷(也就是说,类和方法的状态怎么办)。我觉得Erlang之所以能做到这一点,是因为它更关注于相互通信的对象,所以实时更新对象的代码更有意义。不过我不太确定,所以还是欢迎大家回答。
编辑2:也许描述我想要的最好方式是回顾一下我在下面一条评论中说的:“被调用时,方法只需要指向新的方法定义/位置。”
3 个回答
这段话的意思是,你可以对一个已经存在的类进行修改,这样这些修改就会在这个类的实例中体现出来。关键在于要修改这个已有的类,而不是重新创建一个和旧类同名的新类。
>>> class foo():
... @classmethod
... def do_something(self):
... print "this is good"
...
>>> f = foo()
>>> f.do_something()
this is good
>>> def do_something_else(self):
... print "this is better"
...
>>> foo.do_something = do_something_else
>>> f.do_something()
this is better
你可以创建一个类装饰器或者元类,确保在重新加载类的时候,旧对象的类会被改变。这里有一个可以工作的例子(至少对我来说是这样),不过我不建议你直接使用它,而是把它当作灵感,去创造一个更符合你需求的东西。(另外,这个例子没有在那些没有定义__init__
的方法的类上测试过,所以要小心使用。)
import sys
import weakref
class _ObSet(weakref.WeakValueDictionary):
def add(self, ob):
self[id(ob)] = ob
def remove(self, ob):
del self[id(ob)]
def __iter__(self):
return self.itervalues()
def reloadable(cls):
# Change the __init__ of the old class to store the instances
# in cls.__instances (you might stick this into a class as a
# static method to avoid name collisions)
if '__init__' in vars(cls):
old_init = vars(cls)['__init__']
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
old_init(self, *a, **kw)
cls.__init__ = __init__
elif '__new__' in vars(cls):
old_new = vars(cls)['__new__']
def __new__(cls, *a, **kw):
self = old_new(cls, *a, **kw)
cls.__instances.add(self)
return self
cls.__new__ = __new__
else:
def __init__(self, *a, **kw):
self.__class__.__instances.add(self)
super(cls, self).__init__(*a, **kw)
cls.__init__ = __init__
cls.__instances = _ObSet()
module = sys.modules.get(cls.__module__)
if module is None:
return cls
old_cls = getattr(module, cls.__name__, None)
if old_cls is None:
return cls
# Change the bases of all subclasses of the old class
for ob in old_cls.__instances:
if ob.__class__ is old_cls:
ob.__class__ = cls
# Change the class of all instances of the old class
for child_cls in old_cls.__subclasses__():
child_cls.__bases__ = tuple(cls if base is old_cls else base
for base in child_cls.__bases__)
return cls
下面是它的使用示例:
from reloading import reloadable
@reloadable
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
class B1(A):
def __init__(self, c, *a):
super(B1, self).__init__(*a)
self.c = c
@reloadable
class B2(A):
def __init__(self, c, *a):
super(B2, self).__init__(*a)
self.c = c
然后是它的工作原理:
>>> import test_reload
>>> a = test_reload.A(1, 2)
>>> b1 = test_reload.B1(1, 2, 3)
>>> b2 = test_reload.B2(1, 4, 6)
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1)
True
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> reload(test_reload)
<module 'test_reload' from 'test_reload.pyc'>
>>> isinstance(a, test_reload.A)
True
>>> isinstance(b1, test_reload.A)
True
>>> isinstance(b1, test_reload.B1) # will fail, not @reloadable
False
>>> isinstance(b2, test_reload.A)
True
>>> isinstance(b2, test_reload.B2)
True
>>> a.a, a.b
(1, 2)
>>> b1.a, b1.b, b1.c
(2, 3, 1)
>>> b2.a, b2.b, b2.c
(4, 6, 1)
是的,你可以做到,而且其实很简单。你想要改变的只是实例 f
,而不是类 foo
,对吧?
>>> class foo():
@classmethod
def do_something(self):
print "this is good"
>>> f = foo()
>>> f.do_something()
this is good
>>> def new_ds():
print "this is better"
>>> f.do_something = new_ds
>>> f.do_something()
this is better
>>> f2 = foo()
>>> f2.do_something() #unchanged
this is good
编辑
这样做可能不是最理想的,因为作用域发生了变化,但我在重新加载后,变化几乎是立刻生效的。
testmod.py -- 最初的状态
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is good"
testmod.py -- 修改后的状态
class foo():
@classmethod
def do_something(self):
outside_func()
def outside_func():
print "this is better"
解释器
>>> import testmod
>>> f = testmod.foo()
>>> f.do_something()
this is good
>>> reload(testmod)
<module 'testmod' from 'C:\Python26\testmod.py'>
>>> f.do_something()
this is better