Как получить травление для работы с iPython? - PullRequest
7 голосов
/ 07 августа 2010

Я пытаюсь загрузить маринованные объекты в 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 vanilla python, но не с iPython.

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

Ответы [ 2 ]

11 голосов
/ 07 августа 2010

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

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

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

import FakeModule
import CoolModule
FakeModule.World = CoolModule.World

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

Edit : редактирование 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".В модуле 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 обнаружил по ошибке msg ;-) и выполняет с ней черную магию, чтобы быть «крутым» и т. д.Тем не менее, всякий раз, когда вы хотите напрямую перейти к модулю с заданным именем, в 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 и его основных механизмов, таких как pickling и sys.modules).

Урок номер два: этот файл выбора, по сути, поврежден из-за того, как вы его написали, потому что он может быть загружен только тогда, когда модуль __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 о том, чтобы никогда не иметь какого-либо существенного кода на верхнем уровне модуля - только импорт, класс, def и тривиальные назначения - все остальное принадлежит функциям; еслиВы не следовали этой наилучшей практике, а затем отредактируйте свой код, чтобы сделать вас намного счастливее с точки зрения как гибкости, так и производительности).

2 голосов
/ 08 августа 2010

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

% 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 пытается распаковать file, он выполняет следующие строки:

/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, даже если вы скажете from test import World до или после __import__.

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

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

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

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

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

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

...