Найти - Пользователи
Полная версия: Lazy importer and gtk
Начало » Python для экспертов » Lazy importer and gtk
1
Ed
Пытаюсь использовать вот этот рецепт: http://code.activestate.com/recipes/473888/
Работал на ура, пока не столкнулся с gtk.

Вот тестовый пример:
import importer
importer.install()
import gtk

print gtk.POS_LEFT
При применении кода из рецепта ломается так:
Traceback (most recent call last):
File "test.py", line 5, in <module>
print gtk.POS_LEFT
File "/usr/lib/python2.5/site-packages/importer.py", line 76, in __getattr__
mod = _loadModule()
File "/usr/lib/python2.5/site-packages/importer.py", line 34, in _loadModule
mod = imp.load_module(name, file, pathname, desc)
File "/usr/lib/python2.5/site-packages/gtk-2.0/gtk/__init__.py", line 50, in <module>
import gdk
ImportError: No module named gdk
С изменениями из комментария 4 падает c Segmentation fault.

Любые идеи как это отдебажить/пофиксить?
Андрей Светлов
Принтов напихай. На первый взгляд все должно работать (если gdk ставить в skip_list, конечно).
Ed
Ага, спасибо.
Я именно таким путем и пошел. Кроме того немного переделал, почитав это: http://blog.doughellmann.com/2009/11/pymotw-sys-part-7-modules-and-imports.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+PyMOTW+%28Python+Module+of+the+Week%29
Засада оказалось в cairo. __skip = оказалось достаточно для моего случая.
Правда в результате получилось не совсем то, что я ожидал, поэтому продолжаю копать.
Конструкции вида from bla.bla1 import bla2 вызывают __getattr__(“__path__”) для bla.bla1 и вся ленивость на этом заканчивается. Это происходит не во всех случаях, например from os.path import join этого не делает, а для моих модулей это происходит. Вот пытаюсь понять в чем разница.
Андрей Светлов
А никто счастья не обещал :)

os и os.path - это не package а обманка. Так что не показатель.

__path__ пусть себе дергают - при использованиее lazy importer нужно __init__.py делать пустыми (или очень маленькими).
Тогда много вреда не будет.

Еще стоит везде проставить from __future__ import absolute_import - уйдут лишние попытки открыть несуществующий файл, и таких много.
Ed
Да тут не до счастья, хоть какую-то пользу извлечь бы :)

С os все оказалось просто - он уже проимпортирован самим importer-ом до того, как тот регистрирует свой функционал, поэтому ничего и не звалось.

Так что чуда опять не случилось - все детерминированно и скучно, как обычно :)

Конструкция from popen2 import popen2 приводит к такой последовательности вызовов:
find_module popen2 None
__getattr__ <moduleProxy ‘popen2’ from ‘/usr/lib/python2.5/popen2.py’> __path__
_loadModule /usr/lib/python2.5/popen2.py
mod: <module ‘popen2’ from ‘/usr/lib/python2.5/popen2.pyc’>
__getattr__ <moduleProxy ‘popen2’ from ‘/usr/lib/python2.5/popen2.py’> popen2
_loadModule /usr/lib/python2.5/popen2.py
mod: <module ‘popen2’ from ‘/usr/lib/python2.5/popen2.pyc’>
result: <function popen2 at 0xb7eabd14>

То есть даже в этом случае происходит загрузка самого модуля, а не __init__.py .
Какая же это ленивость. Вовсе не этого хотелось :(

from __future__ import absolute_import это хорошо, спасибо. К сожалению у меня другой случай.
Я хотел привнести ленивость со стороны, не изменяя ничего в существующей системе.

Пошел читать теорию: http://www.python.org/dev/peps/pep-0302/
Андрей Светлов
Еще одну вешь забыл упомянуть - для lazy import нельзя использовать конструкцию from module import name
Именно потому что, как вы метко заметили, ленивость сразу же исчезает.
Смотрите:
>>> def f():
... from urllib.request import urlopen
...
>>> dis.dis(f)
2 0 LOAD_CONST 1 (0)
3 LOAD_CONST 2 (('urlopen',))
6 IMPORT_NAME 0 (urllib.request)
9 IMPORT_FROM 1 (urlopen)
12 STORE_FAST 0 (urlopen)
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
>>>
Тут без альтернатив:
1. импортируем модуль IMPORT_NAME urllib.request (фактически это будет вызов __import__)
2. берем из него аттрибут IMPORT_FROM urlopen
3. создаем локальную переменню STORE_FAST
хвост начиная с 15 байта никакого отношения к импорту не имеет. STORE_FAST будет STORE_GLOBAL для вызова не из функции.
Итого - пять опкодов, и не один из них ни разу не ленивый.
Так что ничего не меняя - не выйдет. PEP 302 - штука хорошая, но в вашем случае он не поможет.
Ed
А по каким параметром вы определяете ленивый опкод или нет?
Я вот смотрю на это:
In [1]: import dis   

In [2]: def f():
...: import popen2
...:
...:

In [3]: dis.dis(f)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (popen2)
9 STORE_FAST 0 (popen2)
12 LOAD_CONST 0 (None)
15 RETURN_VALUE
И вижу там много общего с тем, что вы показали. Но эта конструкция вполне себе ленивая. Что же превращает from … import в не ленивую?
Андрей Светлов
Давайте я не поленюсь и покажу декомпиляцию для модулей.
Дело в том, что dis хочет объект - и самый быстрый способ, если все пишешь на лету в консоли - это создать функцию. И рассматривать ее просто как контейнер.
Сейчас поправлюсь.
mod = """
import os
from os import close

def f():
fd = os.open("some file")
close(fd)

class A:
def f(self):
pass
"""

mod = compile(mod, '<module>', 'exec')

import dis
print ("Module")
dis.dis(mod)
d = {}
exec(mod, d, d)
print ("Function")
dis.dis(d['f'])
Сначала компилируем и смотрим что получилось.
Затем запускаем этот codeobject, который на самом деле модуль - и анализируем уже функцию f.
Все выглядит несколько нетривиально потому, что если будем анализировать результат:
import mod
dis.dis(mod)
- увидим код модуля _после_ загрузки (exec), а не то, _как именно_ он создается.
А теперь - результат
Module
- Импорт os и сохранение под тем же именем
2 0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (os)
9 STORE_NAME 0 (os)

- импорт из os функции close, и сохранение имени 'close' - про модуль os можем забыть
- на самом деле "правильный" импортер, конечно, положит модуль в sys.modules - но коду создания этого не видно
- ленивый импортер получит свой __getattribute__ уже здесь, сразу при загрузке модуля
3 12 LOAD_CONST 0 (0)
15 LOAD_CONST 2 (('close',))
18 IMPORT_NAME 0 (os)
21 IMPORT_FROM 1 (close)
24 STORE_NAME 1 (close)
27 POP_TOP

- сделать функцию, и запомнить ее под именем f. <code object f ...> - это код самой функции.
5 28 LOAD_CONST 3 (<code object f at 0x01B8C968, file "<module>", line 5>)
31 MAKE_FUNCTION 0
34 STORE_NAME 2 (f)

- сделать класс и тоже сохранить под именем. Вызываемая функция на самом деле что-то вроде return type('A', (), class_dict) - но это уже другая история.
9 37 LOAD_BUILD_CLASS
38 LOAD_CONST 4 (<code object A at 0x01C09260, file "<module>", line 9>)
41 MAKE_FUNCTION 0
44 LOAD_CONST 5 ('A')
47 CALL_FUNCTION 2
50 STORE_NAME 3 (A)
53 LOAD_CONST 1 (None)
56 RETURN_VALUE

- теперь вызовы импортированных объектов в f
Function
- затянуть объект с именем os
6 0 LOAD_GLOBAL 0 (os)
- спросить у него аттрибут open (здесь как раз и происходит вызов __getattribute__ у ленивого импортера)
3 LOAD_ATTR 1 (open)
- вызвать функцию open
6 LOAD_CONST 1 ('some file')
9 CALL_FUNCTION 1
12 STORE_FAST 0 (fd)
- а в случае from mod import name все проще:
- уже есть имя close, просто подгружаем его в стек
7 15 LOAD_GLOBAL 2 (close)
- и вызываем
18 LOAD_FAST 0 (fd)
21 CALL_FUNCTION 1
- оставшиеся три инструкции на самом деле означают 'return None'
24 POP_TOP
25 LOAD_CONST 0 (None)
28 RETURN_VALUE
Как-то так.
Ed
Спасибо за подробное объяснение.
Я не мог понять в чем корень зла. Теперь понимаю, что в STORE_NAME 1 (close).
Даже если вернуть вместо этого некий proxy, то можно будет отследить вызовы, перекрыв __call__, но просто обращения к этой переменной ну никак уже не отследишь.
Андрей Светлов
Правильно поняли
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB