Есть ли способ вручную исправить работу `super()` после перезагрузки IPython (избегая TypeError)?

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

У меня есть несколько классов, предположим, что они находятся в локальном файле «issue.py»:

class A(object):
    def save(self):
        # fancy stuff                                                                                                          
        pass

class B(A):
    def save(self):
        # misc stuff                                                                                                           
        super(B, self).save()

class C(B):
    pass

Я использую их в сеансе IPython, возможно, так:

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from issue import A, B, C

In [4]: c = C()

In [5]: c.foo = 'whatever'

In [6]: c.save()

Все идет нормально. Но потом я понимаю, что есть ошибка в «причудливых вещах» класса А, и делаю там небольшое редактирование — может быть, даже просто добавляю немного логирования. Затем я хочу повторить save():

In [7]: c.save()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-6970514bfc33> in <module>()
----> 1 c.save()

/Users/scratch/Documents/dev2015/gensim_venv/src/gensim-develop/docs/notebooks/scratch~/issue.py in save(self)
      7     def save(self):
      8         # misc stuff
----> 9         super(B, self).save()
     10 
     11 class C(B):

TypeError: super(type, obj): obj must be an instance or subtype of type

О, нет!. Страшный TypeError после перезагрузки классов, в то время как более старые экземпляры сохраняют некоторые старые суперклассы! Эта проблема обсуждается в SO и других местах, но четкого рецепта восстановления нет.

Но так получилось, что мне очень, очень хотелось бы иметь возможность запускать слегка обновленную версию A.save() на моем старом экземпляре c. (У меня есть более 20 ГБ данных в памяти, на создание которых у меня ушло около полутора дней, и которые будут сохранены предпочтительным способом с помощью метода суперкласса. Я сохранил достаточно других ручных методов, которые, я думаю, я "мог бы реконструировать c в перезапущенном ядре IPython. Но, хотя у меня все еще есть подлинный объект, я бы предпочел дать реалистичный тест пропатченному A.save() - возможно, даже сделать для него больше исправлений/тестов. перед полным перезапуском ядра.)

Так что меня интересуют любые тактики или уловки, какими бы неразумными они ни были в других ситуациях, для принуждения c к текущим определениям классов, вплоть до самого верха, чтобы c.save() просто работало.

Любые идеи?

Я ожидаю, что все, что работает для этого игрушечного примера, будет работать и в моей реальной установке, которая представляет собой IPython на основе CPython 2.7.10). (Однако в реальной ситуации эти три класса находятся в разных файлах.)


person gojomo    schedule 12.07.2015    source источник
comment
Вы пытались создать новый экземпляр нового C, а затем вручную перезаписать его данные данными из старого c? То, как это будет работать, зависит от того, какие данные c хранит и как, но это первый подход, который приходит на ум.   -  person BrenBarn    schedule 12.07.2015
comment
Я тоже об этом подумал. Однако, если вы попытаетесь создать c2 даже в игрушечном примере, он получит ту же ошибку на c2.save(), поэтому его иерархия надклассов не будет фиксированной. (В моей реальной установке есть также аналогичная ошибка TypeError, влияющая на метод __init__ C, который делает super() для __init__ B.   -  person gojomo    schedule 12.07.2015


Ответы (2)


Вы можете переназначить обновленный класс вашему экземпляру:

from issue import A, B, C
c.__class__ = C

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

Если у вас более сложная установка с несколькими модулями, вам нужно заменить все ссылки на старые классы. Если у вас есть импорт в любом модуле вида:

from some_module import Bar

class Foo(Bar):
    # ...

тогда Bar не будет повторно привязан при перезагрузке some_module. Вы можете избежать принудительной перезагрузки и повторной привязки зависимостей, избегая привязки к глобальным переменным; вместо этого свяжите только модуль:

import some_module

class Foo(some_module.Bar):
    # ...

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

Демо:

>>> class A(object):
...     def save(self):
...         # fancy stuff                                                                                                          
...         pass
... 
>>> class B(A):
...     def save(self):
...         # misc stuff                                                                                                           
...         super(B, self).save()
... 
>>> class C(B):
...     pass
... 
>>> c = C()
>>> c.foo = 'whatever'
>>> c.save()
>>> 
>>> # Re-defining the classes breaks the instance
... 
>>> class A(object):
...     def save(self):
...         # fancy stuff                                                                                                          
...         pass
... 
>>> class B(A):
...     def save(self):
...         # misc stuff                                                                                                           
...         super(B, self).save()
... 
>>> class C(B):
...     pass
... 
>>> isinstance(c, C)
False
>>> c.save()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in save
TypeError: super(type, obj): obj must be an instance or subtype of type
>>> 
>>> # Fixing the issue by rebinding the class
... 
>>> c.__class__ = C
>>> isinstance(c, C)
True
>>> c.save()
person Martijn Pieters    schedule 12.07.2015
comment
Я пробовал c.__class__ = C, но затем c.save() вызывает точно такую ​​же ошибку. - person gojomo; 12.07.2015
comment
@gojomo: так C в вашем сеансе все еще привязан к старому классу? Затем выдайте еще один from issue import A, B, C. - person Martijn Pieters; 12.07.2015
comment
Бинго, похоже, получилось! Теперь пробуем в реальном сеансе, который вдохновил пример... - person gojomo; 12.07.2015
comment
Увы, в «настоящей» настройке все же возникли проблемы. Там B и C — отдельные файлы в одном модуле; А от другого. Собираюсь попробовать отредактировать каждый файл (чтобы его обнаружил механизм обнаружения изменений IPython), а затем явный импорт... - person gojomo; 12.07.2015
comment
@gojomo: вы используете глобальный импорт; from mod import classname; если вместо этого вы импортировали модуль, вам также не пришлось бы перезагружать имя класса в модуле. Вам, вероятно, придется сделать moduleB.A = moduleA.A после перезагрузки, так как ссылка A в пространстве имен moduleB все еще привязана к старому классу. Вместо этого используйте import moduleA и везде ссылайтесь на moduleA.A. Избавляет вас от повторной привязки имен. - person Martijn Pieters; 12.07.2015
comment
Спасибо! Идея о том, что даже после перезагрузки/повторного импорта-на-верхнем-уровне/__class__-переназначения ссылки в других пространствах имен остаются и требуют исправления, была последней частью головоломки в решении моей полной проблемы. (Я также столкнулся с кучей ошибок PicklingErrors, поскольку я итеративно находил и вручную переназначал ссылки на классы, которые были несоответствующими... но в конечном итоге привели вещи в состояние, в котором «большое сохранение» завершилось нормально.) - person gojomo; 12.07.2015

В Python 3 использование нового синтаксиса super().__init__() вместо super(B, self).__init__() решило для меня аналогичную проблему.

person amolk    schedule 19.10.2018