Bezdepbonusu7Программирование: статьи и ответы

Вопрос

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

Автор вопроса: gojomoОтветов: 2

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

У меня есть несколько классов, предположим, что они находятся в локальном файле "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). (Однако в реальной ситуации эти три класса находятся в разных файлах.)

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

pythonipythontypeerroripython-magic

Ответы

Ответ 1 · рейтинг 2 · Martijn Pieters

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

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()

Ответ 2 · рейтинг 1 · amolk

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

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы