Как получить травление для работы с iPython?

Я пытаюсь загрузить засоленные объекты в iPython .

Я получаю ошибку:

AttributeError: у объекта 'FakeModule' нет атрибута 'World'

Кто-нибудь знает, как заставить его работать, или, по крайней мере, есть обходной путь для загрузки объектов в iPython в Чтобы просмотреть их в интерактивном режиме?

Спасибо

отредактировано, чтобы добавить:

У меня есть скрипт под названием world.py, который в основном делает:

import pickle
class World:
    ""
if __name__ == '__main__':
    w = World()
    pickle.dump(w, open("file", "wb"))

Чем в REPL я делаю:

import pickle  
from world import World  
w = pickle.load(open("file", "rb"))

, который работает в REPL ванильного Python, но не с iPython.

Я использую Python 2.6. 5 и iPython 0.10 из дистрибутива Enthought Python, но у меня также была проблема с предыдущими версиями.

7
задан Martijn Pieters 2 May 2014 в 12:42
поделиться

2 ответа

Похоже, вы изменили FakeModule между моментом, когда вы обрабатываете свои данные, и временем, когда вы пытаетесь их распаковать: в частности, вы удалили из этого модуля некий объект верхнего уровня с именем World (возможно, класс, возможно, функция).

Pickling сериализует классы и функции «по имени», поэтому они должны быть именами на верхнем уровне своего модуля и , что модуль не должен быть изменен (по крайней мере, таким образом, чтобы не повлиять на эти имена плохо - - определенно , а не путем удаления этих имен из модуля!) между временем травления и временем распаковки.

После того, как вы точно определили, какое изменение вы сделали, что препятствует распаковке, его часто можно взломать, если по другим причинам вы не можете просто отменить изменение. Например, если вы только что переместили World из FakeModule в CoolModule , выполните:

import FakeModule
import CoolModule
FakeModule.World = CoolModule.World

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

Правка : правка Q OP значительно упрощает понимание его ошибки.Поскольку он сейчас проверяет, равно ли __ name __ '__ main __' , становится очевидным, что pickle при написании сохранит объект класса __ main __. World . Так как он использует рассол ASCII ( очень плохой выбор для производительности и дискового пространства, кстати), проверить тривиально:

$ cat file
(i__main__
World
p0
(dp1

ищущий модуль (ясно и очевидно) __ main__ . Теперь, даже не беспокоясь об ipython, но с помощью простого интерактивного интерпретатора Python:

$ py26
Python 2.6.5 (r265:79359, Mar 24 2010, 01:32:55) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import world
>>> import pickle
>>> pickle.load(open("file", "rb"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1370, in load
    return Unpickler(file).load()
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 858, in load
    dispatch[key](self)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1069, in load_inst
    klass = self.find_class(module, name)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'World'
>>> 

ошибку можно легко воспроизвести, и ее причина столь же очевидна: модуль, в котором выполняется поиск имени класса (то есть __ main__ ) действительно не имеет атрибута с именем «Мир». В модуле world он есть, но OP не «соединил точки», как я объяснил в предыдущей части ответа, поместив ссылку с правильным именем в модуль, в котором она нужна для маринованного файла. . То есть:

>>> World = world.World
>>> pickle.load(open("file", "rb"))
<world.World instance at 0xf5300>
>>> 

теперь это, конечно, работает просто отлично (и как я уже сказал ранее). Возможно, ОП не видит этой проблемы, потому что он использует форму импорта, которую я ненавижу, from world import World (непосредственный импорт функции или класса из модуля, а не самого модуля).

Уловка для решения проблемы в ipython точно такая же с точки зрения базовой архитектуры Python - просто требуется еще пара строк кода, потому что ipython для предоставления всех своих дополнительных услуг не не ] сделать модуль __ main __ напрямую доступным для прямой записи того, что происходит в интерактивной командной строке, но вместо этого вставляет модуль (называемый FakeModule, как OP узнал из сообщения об ошибке ;-) и творит с ним черную магию в чтобы быть "крутым" и т. д. Тем не менее, всякий раз, когда вы хотите перейти непосредственно к модулю с заданным именем, это, конечно, довольно тривиально в Python:

In [1]: import world

In [2]: import pickle

In [3]: import sys

In [4]: sys.modules['__main__'].World = world.World

In [5]: pickle.load(open("file", "rb"))
Out[5]: <world.World instance at 0x118fc10>

In [6]: 

Урок, который следует сохранить, номер один: избегайте черной магии, по крайней мере, до тех пор, пока вы не будете достаточно хороши в качестве ученик чародея, чтобы иметь возможность обнаруживать и исправлять его случайные побеги (в противном случае эти ведра с метлами могут затопить мир, пока вы спите ;-).

Или, альтернативное прочтение: чтобы правильно использовать определенный уровень абстракции (например, «классные», которые ipython кладет поверх Python), вам необходимо глубокое понимание нижележащего уровня (здесь самого Python и его основных механизмов, таких как травление и sys.modules).

Урок номер два: этот файл pickle по сути не работает из-за того, как вы его написали, потому что он может быть загружен только тогда, когда модуль __ main __ имеет класс по имени Word , чего, конечно же, не было бы без некоторых хаков, подобных описанным выше. Вместо этого файл pickle должен записывать класс как живущий в модуле world .Если вы абсолютно уверены, что должны создать файл в предложении if __name__ == '__main __': в world.py , тогда используйте некоторую избыточность для этой цели. :

import pickle
class World:
    ""
if __name__ == '__main__':
    import world
    w = world.World()
    pickle.dump(w, open("file", "wb"))

это работает нормально и без хаков (по крайней мере, если вы следуете лучшей практике Python, согласно которой на верхнем уровне модуля никогда не будет никакого существенного кода - только import, class, def и тривиальные присваивания - все остальное принадлежит функциям; Если вы не следовали этому передовому опыту, отредактируйте свой код, чтобы сделать это, это сделает вас намного более счастливыми с точки зрения гибкости и производительности).

11
ответ дан 6 December 2019 в 19:31
поделиться

Когда вы обрабатываете w в модуле __ main __ с помощью pickle.dump (w, open ("файл", "wb")) , тот факт, что w происходит от модуля __ main __ , записывается в первой строке файл :

% xxd file
0000000: 2869 5f5f 6d61 696e 5f5f 0a57 6f72 6c64  (i__main__.World
0000010: 0a70 300a 2864 7031 0a62 2e              .p0.(dp1.b.

Когда IPython пытается распаковать файл , он выполняет следующие строки:

/usr/lib/python2.6/pickle.pyc in find_class(self, module, name)
   1124         __import__(module)
   1125         mod = sys.modules[module]
-> 1126         klass = getattr(mod, name)
   1127         return klass
   1128 

В частности, он пытается выполнить __ import __ ('__ main __') . Если вы попробуете это в REPL, вы получите

In [29]: fake=__import__('__main__')

In [32]: fake
Out[32]: <module '__main__' from '/usr/lib/pymodules/python2.6/IPython/FakeModule.pyc'>

Это FakeModule , который IPython упоминает в AttributeError.

Если вы заглянете внутрь fake .__ dict __ , то увидите, что он не включает World , даже если вы скажете из тестового импорта World до или после __ импорт __ .

Если вы запустите

In [35]: fake.__dict__['World']=World

, то pickle.load будет работать:

In [37]: w = pickle.load(open("file", "rb"))

Может быть более чистый способ; Я не знаю. В любом случае, если вы поместите World в пространство имен fake , это должно работать.

PS. В 2008 году Фернандо Перес, создатель IPython, написал немного по этой проблеме.Он мог бы исправить это каким-то образом, чтобы избежать моего грязного взлома. Вы можете спросить в списке рассылки пользователей IPython или, что проще, просто не копировать в пространстве имен __ main __ .

2
ответ дан 6 December 2019 в 19:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: