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

时间:2009-07-03 20:03:08

标签: python module reload python-import

假设您有一个从模块内的类实例化的对象。 现在,您重新加载该模块。 接下来你要做的就是让重新加载影响那个类。

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()方法对对象内部的数据进行操作,因此静态模块函数不会这样做...

7 个答案:

答案 0 :(得分:15)

要更新类的所有实例,有必要跟踪这些实例的某些位置 - 通常通过弱引用(弱值dict是最方便和通用的),因此“保持跟踪”功能不会阻止不需要的实例走开,当然!

您通常希望将这样的容器保留在类对象中,但是,在这种情况下,由于您将重新加载模块,因此获取旧的类对象并非易事;在模块级别工作更简单。

所以,让我们说一个“可升级模块”需要在其开始时定义一个弱值dict(以及一个辅助的“使用下一个键”int),例如传统名称:

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)来注册新实例。

现在,“reload function”可以通过在重新加载模块之前获取_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

这不是全部 - 关于这个问题还有很多话要说 - 但是,这是要点,答案已经足够长了(而且,我已经筋疲力尽了; - )。

答案 1 :(得分:6)

你必须制作一个新对象。没有办法神奇地更新现有对象。

阅读reload builtin documentation - 非常清楚。这是最后一段:

  

如果模块实例化类的实例,则重新加载定义类的模块不会影响实例的方法定义 - 它们继续使用旧的类定义。派生类也是如此。

文档中还有其他注意事项,所以你真的应该阅读它,并考虑替代方案。也许你想用为什么开始一个新的问题,你想要使用reload并要求其他方法来实现同样的目标。

答案 2 :(得分:4)

我的方法如下:

  1. 查看所有导入的模块并仅重新加载具有新.py文件的模块(与现有的.pyc文件相比)
  2. 对于重新加载的每个函数和类方法,设置old_function .__ code__ = new_function .__ code__。
  3. 对于每个重新加载的类,使用gc.get_referrers列出该类的实例,并将其__class__属性设置为新版本。
  4. 这种方法的优点是:

    • 通常无需按任何特定顺序重新加载模块
    • 通常只需要使用更改后的代码重新加载模块
    • 不需要修改类来跟踪他们的实例

    您可以在此处阅读有关技术(及其局限性)的信息: http://luke-campagnola.blogspot.com/2010/12/easy-automated-reloading-in-python.html

    您可以在此处下载代码: http://luke.campagnola.me/code/downloads/reload.py

答案 3 :(得分:3)

您必须从新模块中获取新类并将其分配回实例。

如果您可以在使用此mixin的实例时随时触发此操作:

import sys

class ObjDebug(object):
  def __getattribute__(self,k):
    ga=object.__getattribute__
    sa=object.__setattr__
    cls=ga(self,'__class__')
    modname=cls.__module__ 
    mod=__import__(modname)
    del sys.modules[modname]
    reload(mod)
    sa(self,'__class__',getattr(mod,cls.__name__))
    return ga(self,k)

答案 4 :(得分:2)

以下代码可以满足你的需求,但是请不要使用它(至少在你确定自己正在做正确的事情之前),我是发布仅供参考

mymodule.py:

class ClassChange():
    @classmethod
    def run(cls,instance):
        print 'one',id(instance)

myexperiment.py:

import mymodule

myObject = mymodule.ClassChange()
mymodule.ClassChange.run(myObject)

# change mymodule.py here

reload(mymodule)
mymodule.ClassChange.run(myObject)

当您的代码 时,您实例myObject,您将获得ClassChange的实例。此实例有一个名为run实例方法。即使重新加载,该对象也会保留此实例方法(由nosklo解释的原因),因为重新加载仅重新加载 ClassChange

在上面的我的代码中,run类方法。类方法总是绑定并操作类,而不是实例(这就是为什么它们的第一个参数通常称为cls,而不是self)。重新加载了Wenn ClassChange,这个类方法也是如此。

您可以看到我还将实例作为参数传递,以使用ClassChange的正确(相同)实例。您可以看到,因为在两种情况下都会打印相同的对象ID。

答案 5 :(得分:1)

我不确定这是否是最好的方法,或者与你想做的事情相符...但这可能对你有用。如果要更改某个方法的行为,对于某种类型的所有对象...只需使用一个函数变量。例如:


def default_behavior(the_object):
  print "one"

def some_other_behavior(the_object):
  print "two"

class Foo(object):
  # Class variable: a function that has the behavior
  # (Takes an instance of a Foo as argument)
  behavior = default_behavior

  def __init__(self):
    print "Foo initialized"

  def method_that_changes_behavior(self):
    Foo.behavior(self)

if __name__ == "__main__":
  foo = Foo()
  foo.method_that_changes_behavior() # prints "one"
  Foo.behavior = some_other_behavior
  foo.method_that_changes_behavior() # prints "two"

  # OUTPUT
  # Foo initialized
  # one
  # two

现在您可以拥有一个负责重新加载模块的类,并在重新加载后将Foo.behavior设置为新的。我试过这段代码。它工作正常: - )。

这对你有用吗?

答案 6 :(得分:1)

有一些技巧可以让你想要的东西成为可能。

有人已经提到过,你可以拥有一个保存其实例列表的类,然后在重新加载时将每个实例的类更改为新实例。

然而,这并不高效。更好的方法是更改​​旧类,使其与新类相同。