In Python, how do you change an instantiated object after a reload?

前端 未结 7 1596
南方客
南方客 2021-02-04 06:25

Let\'s say you have an object that was instantiated from a class inside a module. Now, you reload that module. The next thing you\'d like to do is make that reload affect that c

7条回答
  •  鱼传尺愫
    2021-02-04 07:28

    To update all instances of a class, it is necessary to keep track somewhere about those instances -- typically via weak references (weak value dict is handiest and general) so the "keeping track" functionality won't stop unneeded instances from going away, of course!

    You'd normally want to keep such a container in the class object, but, in this case, since you'll be reloading the module, getting the old class object is not trivial; it's simpler to work at module level.

    So, let's say that an "upgradable module" needs to define, at its start, a weak value dict (and an auxiliary "next key to use" int) with, say, conventional names:

    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
    

    Each class in the module must have, typically in __init__, a call _register(self) to register new instances.

    Now the "reload function" can get the roster of all instances of all classes in this module by getting a copy of _objs before it reloads the module.

    If all that's needed is to change the code, then life is reasonably easy:

    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)
    

    Alas, one typically does want to give the new class a chance to upgrade an object of the old class to itself more finely, e.g. by a suitable class method. That's not too hard either:

    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)
    

    For example, say the new version of class Zap has renamed an attribute from foo to bar. This could be the code of the new 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
    

    This is NOT all -- there's a LOT more to say on the subject -- but, it IS the gist, and the answer is WAY long enough already (and I, exhausted enough;-).

提交回复
热议问题