15

假设您有一个从模块内的类实例化的对象。现在,您重新加载该模块。您想做的下一件事是使重新加载影响该类。

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

4

7 回答 7

16

要更新一个类的所有实例,有必要在某个地方跟踪这些实例——通常通过弱引用(弱值字典是最方便和通用的),因此“跟踪”功能不会阻止不需要的实例消失,当然!

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

因此,假设一个“可升级模块”需要在其开始时定义一个弱值 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)

_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

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

于 2009-07-03T21:59:27.663 回答
6

您必须创建一个新对象。没有办法神奇地更新现有对象。

阅读reload内置文档- 非常清楚。这是最后一段:

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

文档中还有其他警告,因此您真的应该阅读它,并考虑替代方案。也许您想提出一个新问题,说明您为什么要使用reload并寻求其他方法来实现相同的目标。

于 2009-07-03T20:04:49.007 回答
4

我的方法如下:

  1. 查看所有导入的模块并仅重新加载具有新 .py 文件的模块(与现有的 .pyc 文件相比)
  2. 对于每个重新加载的函数和类方法,设置 old_function.__code__ = new_function.__code__。
  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

于 2012-04-03T21:58:55.223 回答
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)
于 2013-04-05T17:09:14.947 回答
2

以下代码可以满足您的需求,但请不要使用它(至少在您非常确定自己做对了事情之前不要使用它),我发布它仅用于解释目的

我的模块.py:

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

我的实验.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)。WennClassChange被重新加载,这个类方法也是如此。

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

于 2009-07-03T20:24:14.230 回答
1

有一些技巧可以使您想要的成为可能。

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

但是,这样做效率不高。更好的方法是更改​​旧类,使其与新类相同。

于 2009-07-04T01:38:04.993 回答
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新的东西。我试过这段代码。它工作正常:-)。

这对你有用吗?

于 2009-07-03T20:26:21.763 回答