В Python, как вы изменяете экземпляр объекта после перезагрузки?

Скажем, у вас есть объект, который был создан из класса внутри модуля. Теперь вы перезагружаете этот модуль. Следующее, что вы хотели бы сделать, это заставить эту перезагрузку повлиять на этот класс.

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() работал с данными внутри объекта, поэтому статическая функция модуля не выполнялась бы...

Ответы

Ответ 1

Чтобы обновить все экземпляры класса, необходимо отслеживать где-то об этих экземплярах - обычно через слабые ссылки (слабое значение 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) для регистрации новых экземпляров.

Теперь функция перезагрузки может получить список всех экземпляров всех классов в этом модуле, получив копию _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

Это НЕ ВСЕ - еще БОЛЬШЕ сказать по этому вопросу, но, это суть, и ответ уже достаточно длинный (и я, исчерпал достаточно; -).

Ответ 2

Вам нужно создать новый объект. Невозможно магически обновить существующие объекты.

Прочтите reload встроенную документацию - это очень ясно. Здесь последний абзац:

Если модуль создает экземпляры класса, перезагрузка модуля, который определяет класс, не влияет на определения методов экземпляров - они продолжают использовать старое определение класса. То же самое верно для производных классов.

В документации есть другие оговорки, поэтому вы действительно должны прочитать их и рассмотреть альтернативы. Возможно, вы хотите начать новый вопрос с почему, который вы хотите использовать reload, и попросить другие способы достижения того же.

Ответ 3

Мой подход к этому заключается в следующем:

  • Просмотрите все импортированные модули и перезагрузите только те, у которых есть новый .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

Ответ 4

Вы должны получить новый класс из нового модуля и назначить его экземпляру.

Если вы можете запускать эту операцию в любое время, когда вы используете экземпляр с этим 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)

Ответ 5

Следующий код делает то, что вы хотите, но , пожалуйста, не используйте его (по крайней мере, пока вы не уверены, что поступаете правильно), Я отправляя его только для объяснения.

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. Вы можете видеть это, потому что один и тот же идентификатор объекта печатается в обоих случаях.

Ответ 6

Я не уверен, что это лучший способ сделать это, или сетки с тем, что вы хотите сделать... но это может сработать для вас. Если вы хотите изменить поведение метода, для всех объектов определенного типа... просто используйте функциональную переменную. Например:


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 на что-то новое. Я пробовал этот код. Он отлично работает: -).

Это работает для вас?

Ответ 7

Есть трюки, чтобы сделать то, что вы хотите.

Кто-то уже упоминал, что вы можете иметь класс, который хранит список его экземпляров, а затем меняет класс каждого экземпляра на новый при перезагрузке.

Однако это неэффективно. Лучший способ - изменить старый класс так, чтобы он был таким же, как новый класс.

Взгляните на старую статью, которую я написал, которая, вероятно, лучше всего подходит для перезагрузки, которая будет изменять старые объекты.

http://www.codexon.com/posts/a-better-python-reload