在Python中,如何在重载后更改实例化对象?

16 投票
8 回答
12581 浏览
提问于 2025-04-15 12:40

假设你有一个对象,它是从一个模块里的类创建出来的。现在,你重新加载了那个模块。接下来,你想让这个重新加载的操作影响到那个类。

mymodule.py
---
class ClassChange():
    def run(self):
        print 'one'

myexperiment.py
---
import mymodule
from mymodule import ClassChange  # why is this necessary?
myObject = ClassChange()
myObject.run()
>>> one
### later, i changed this file, so that it says print 'two'

reload(mymodule)
# trick to change myObject needed here
myObject.run()
>>> two

你是不是需要创建一个新的ClassChange对象,把myObject复制到里面,然后再删除旧的myObject?还是说有更简单的方法呢?

补充说明:run()方法看起来像是静态类的方法,但那只是为了简洁起见。我希望run()方法能操作对象内部的数据,所以静态模块函数就不合适了……

8 个回答

4

我处理这个问题的方法如下:

  1. 查看所有导入的模块,只重新加载那些有新 .py 文件的模块(和现有的 .pyc 文件相比)
  2. 对于每个重新加载的函数和类方法,把旧的函数代码替换成新的函数代码。
  3. 对于每个重新加载的类,使用 gc.get_referrers 列出该类的实例,并把它们的 __class__ 属性更新为新版本。

这种方法的优点有:

  • 通常不需要按特定顺序重新加载模块
  • 通常只需要重新加载那些代码有变动的模块,不需要多余的操作
  • 不需要修改类来跟踪它们的实例

你可以在这里了解更多关于这种技术(以及它的局限性)的内容: http://luke-campagnola.blogspot.com/2010/12/easy-automated-reloading-in-python.html

你可以在这里下载代码: http://luke.campagnola.me/code/downloads/reload.py

6

你需要创建一个新的对象。没有办法神奇地更新已经存在的对象。

看看reload这个内置函数的文档,里面讲得很清楚。这里是最后一段:

如果一个模块创建了一个类的实例,重新加载定义这个类的模块并不会影响这些实例的方法定义——它们仍然使用旧的类定义。派生类也是如此。

文档中还有其他注意事项,所以你真的应该仔细阅读一下,并考虑其他的选择。也许你可以重新提问,说明你为什么想使用reload,并询问其他实现同样效果的方法。

17

要更新一个类的所有实例,首先需要在某个地方记录这些实例的信息。通常我们会用弱引用(weak references),比如使用一个弱值字典,这样可以确保在不需要的时候,这些实例不会被保留。

一般来说,我们会把这个记录的容器放在类对象里,但在这种情况下,由于你会重新加载模块,获取旧的类对象就变得不那么简单了;所以在模块层面上处理会更方便。

假设一个“可升级的模块”需要在开始时定义一个弱值字典(以及一个辅助的“下一个使用的键”的整数),我们可以给它起一些常规的名字:

import weakref
class _List(list): pass   # a weakly-referenceable sequence
_objs = weakref.WeakValueDictionary()
_nextkey = 0
def _register(obj):
  _objs[_nextkey] = List((obj, type(obj).__name__))
  _nextkey += 1

模块中的每个类通常在__init__方法里都会调用_register(self)来注册新的实例。

现在,“重新加载函数”可以在重新加载模块之前,通过获取_objs的副本来获取这个模块中所有类的所有实例的列表。

如果只需要改变代码,那事情就比较简单了:

def reload_all(amodule):
    objs = getattr(amodule, '_objs', None)
    reload(amodule)
    if not objs: return  # not an upgraable-module, or no objects
    newobjs = getattr(amodule, '_objs', None)
    for obj, classname in objs.values():
        newclass = getattr(amodule, classname)
        obj.__class__ = newclass
        if newobjs: newobjs._register(obj)

可惜的是,通常我们希望新的类能更细致地将旧类的对象升级为自己,比如通过一个合适的类方法。这也不是太难:

def reload_all(amodule):
    objs = getattr(amodule, '_objs', None)
    reload(amodule)
    if not objs: return  # not an upgraable-module, or no objects
    newobjs = getattr(amodule, '_objs', None)
    for obj, classname in objs:
        newclass = getattr(amodule, classname)
        upgrade = getattr(newclass, '_upgrade', None)
        if upgrade:
            upgrade(obj)
        else:
            obj.__class__ = newclass
        if newobjs: newobjs._register(obj)

例如,假设新版本的类Zap把一个属性从foo改名为bar。这可能是新Zap的代码:

class Zap(object):
    def __init__(self):
        _register(self)
        self.bar = 23

    @classmethod
    def _upgrade(cls, obj):
        obj.bar = obj.foo
        del obj.foo
        obj.__class__ = cls

这还不是全部——关于这个主题还有很多要说的——但这就是大致的意思,答案已经够长了(我也累得够呛;-)。

撰写回答