在 Python 中,如何在重新加载后更改实例化对象?
Posted
技术标签:
【中文标题】在 Python 中,如何在重新加载后更改实例化对象?【英文标题】:In Python, how do you change an instantiated object after a reload? 【发布时间】:2010-11-08 00:23: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() 方法对对象内部的数据进行操作,所以静态模块函数不会这样做...
【问题讨论】:
回答您的子问题“为什么需要这样做?”:不是。 import mymodule 将 mymodule 放到命名空间中,但 mymodule 本身是一个命名空间,其中包含 ClassChange,所以你必须使用 mymodule.ClassChange 来解决它 谢谢 balpha,我一直想知道这一点 :) 所有这些答案对我来说都是很好的思考。我还不知道哪个答案是正确的,所以我会在选择之前尝试一些解决方案。谢谢! :) 您可能希望在此问题与上一个问题之间的链接中进行编辑。任何对其中一个感兴趣的人都可能想看看另一个。 提到的相关balpha是:***.com/questions/1080521/… 【参考方案1】:您必须创建一个新对象。没有办法神奇地更新现有对象。
阅读reload
builtin documentation - 非常清楚。这是最后一段:
如果一个模块实例化了一个类的实例,重新加载定义该类的模块不会影响实例的方法定义——它们会继续使用旧的类定义。派生类也是如此。
文档中还有其他警告,因此您真的应该阅读它并考虑替代方案。也许您想从为什么开始一个新问题,您想使用reload
,并询问其他实现相同目标的方法。
【讨论】:
感谢您的回答,尽管答案很悲观。我希望用户能够为对象重写代码,并使该代码生效。例如,你有一个星球物体,以及星球上的生物。您想更改星球代码,因此星球上的天气会改变行为。所有的生物都有指向那个星球的指针,而且星球数据很大,所以删除/创建它会很痛苦。也许重新加载不适合这种情况...... 不要太忧郁,aidave - 看看我的回答......只要你知道你想更新哪些实例,我会告诉你一种方法实现这一点,分配它们的 class 或通过可能添加或删除某些属性的方法运行它们是非常可行的(无论您是想使用 reload 来获取新的类对象,还是任何其他相同的方法目的,是相当次要的问题)。 给定的文档引用并不意味着“没有办法神奇地更新现有对象”。你可以例如遍历gc.get_referrers(oldclass)
或已知的子集,并尽可能地对现有实例进行重新分类(或者甚至使用一些修复代码重新工作)。这是在开发过程中获得快速 Pythonic 编辑运行周期的多产常见做法。【参考方案2】:
下面的代码做你想做的事,但是请不要使用它(至少在你确定自己做的是正确的事情之前不要使用它),我是仅出于说明目的发布。。
mymodule.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
)。 Wenn ClassChange
被重新加载,这个类方法也是如此。
您可以看到,我还将 instance 作为参数传递给正确(相同)的 ClassChange 实例。您可以看到,因为在两种情况下都打印了相同的对象 id。
【讨论】:
有趣。这实际上可能符合我的想法,但与我的设想完全不同! 请记住您在上一个问题中从 nosklo 和 Alex Martelli 那里得到的所有好建议。这是一个非常困难的话题。【参考方案3】:我不确定这是否是最好的方法,或者与您想要做的事情相吻合...但这可能对您有用。如果要更改方法的行为,对于某种类型的所有对象......只需使用函数变量。例如:
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
设置为新的值。我试过这段代码。它工作正常:-)。
这对你有用吗?
【讨论】:
感谢您的回复,但是当您需要的不仅仅是课堂上的静态行为时会发生什么?我应该更具体。该函数将需要利用特定于该实例化的数据。 什么意思?您可以更改 behavior() 以获取一个对象……例如 behavior(the_object),并将 self 传递给它。 ie - Foo.behavior(self)(在 method_that_changes_behavior 中) 这样您就可以使用特定于对象的状态。 这似乎可行,但是......我必须不断添加新功能,因为用户不断更改代码。假设用户可以编写行为代码,它会经常更改,我不知道如何管理它,但是使用 Python 一切皆有可能...... 您能更清楚地了解您对什么感到困惑吗?行为不是方法。它是一个类变量,您可以在其中找到一个函数。该函数预计将采用 Foo。现在,任何人都可以编写一个以 Foo 作为参数的函数。他们如何做到这一点并不重要......您可以从模块中动态加载这样的功能。您只需要知道将哪个函数绑定到行为。加载模块后,您可以说 Foo.behavior = new_function_that_takes_a_foo。这有意义吗?这只是加载模块后的一行代码。【参考方案4】:要更新一个类的所有实例,有必要在某个地方跟踪这些实例——通常通过弱引用(弱值字典是最方便和通用的),因此“跟踪”功能不会阻止不需要的实例离开,当然!
您通常希望在类对象中保留这样一个容器,但在这种情况下,由于您将重新加载模块,因此获取旧的类对象并非易事;在模块级别工作更简单。
所以,假设一个“可升级模块”需要在其开始时定义一个弱值 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
这还不是全部——关于这个主题还有很多话要说——但是,这是要点,而且答案已经够长了(我已经筋疲力尽了;-)。
【讨论】:
哇,读起来很有趣:-)。如果目标非常简单——改变单个函数的行为,我认为我的回答仍然有一些优点。但是,就可以更改的内容而言,您的答案显然更加通用和灵活。为此,+1。如果可以的话,我会给你更多+1,因为我每天至少从你的帖子中学到一件有趣的事情:-)。 @Tom,绝对,我同意您的回答可能表明适合更简单目标的替代架构 - 并且“尽可能简单,但不要更简单”肯定接近黄金法则!-) Tx for the +1, and the kudos -- 我一直在与这些问题作斗争(有时这些问题赢得了比赛;-) 这么久了,对我来说“支付”我在 我经验不足,尽我所能帮助别人!-) 这是一个绝妙的主意,亚历克斯。谢谢你。我已经在跟踪我的对象了,这不是问题。事实上,这更加灵活,因为用户可以选择重新加载所有对象,仅一个、一些或无,并让未来的对象使用新代码。我将在本周为您提供解决方案并在这里报告。再次感谢! 我尝试实现这一点,但遇到了一些问题。我是否认为第一个代码块第 6 行的List
应该是 _List
,与第 2 行定义的类匹配?还有_objs
和_nextkey
需要在函数中使用之前声明global
?在我尝试使用它时,似乎这些_List
对象会立即被GC,即使它们中的obj
仍在使用中,所以当我重新加载模块时_objs
字典是空的。这是设计中的根本缺陷,还是我做错了什么?【参考方案5】:
有一些技巧可以让你想要的东西成为可能。
有人已经提到你可以有一个类来保存它的实例列表,然后在重新加载时将每个实例的类更改为新的。
但是,这样做效率不高。更好的方法是更改旧类,使其与新类相同。
【讨论】:
【参考方案6】:我的处理方法如下:
-
查看所有导入的模块并仅重新加载具有新 .py 文件的模块(与现有 .pyc 文件相比)
对于每个重新加载的函数和类方法,设置 old_function.__code__ = new_function.__code__。
对于每个重新加载的类,使用 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
【讨论】:
我为 iPython 的自动重载她建议了这个:github.com/ipython/ipython/issues/461#issuecomment-54382721【参考方案7】:您必须从新模块中获取新类并将其分配回实例。
如果您可以在任何时候使用带有此 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)
【讨论】:
以上是关于在 Python 中,如何在重新加载后更改实例化对象?的主要内容,如果未能解决你的问题,请参考以下文章
在 Laravel 中重新加载自定义 .env 文件后如何更改存储设置?