кошмар с относительным импортом, как работает pep 366?

У меня есть «каноническая файловая структура» (я даю разумные имена, чтобы облегчить чтение):

mainpack/ __main__.py __init__.py - helpers/ __init__.py path.py - network/ __init__.py clientlib.py server.py - gui/ __init__.py mainwindow.py controllers.py 

В этой структуре, например, модули, содержащиеся в каждом пакете, могут захотеть получить доступ к утилитам helpers посредством относительного импорта в виде:

 # network/clientlib.py from ..helpers.path import create_dir 

Программа запускается «как скрипт» с использованием файла __main__.py следующим образом:

 python mainpack/ 

Пытаясь следовать PEP 366, я ввел в __main__.py следующие строки:

 ___package___ = "mainpack" from .network.clientlib import helloclient 

Но при запуске:

 $ python mainpack Traceback (most recent call last): File "/usr/lib/python2.6/runpy.py", line 122, in _run_module_as_main "__main__", fname, loader, pkg_name) File "/usr/lib/python2.6/runpy.py", line 34, in _run_code exec code in run_globals File "path/mainpack/__main__.py", line 2, in <module> from .network.clientlib import helloclient SystemError: Parent module 'mainpack' not loaded, cannot perform relative import 

Что не так? Каков правильный способ обработки и эффективного использования относительного импорта?

Я также попытался добавить текущий каталог в PYTHONPATH, ничего не изменилось.

4 Solutions collect form web for “кошмар с относительным импортом, как работает pep 366?”

Код загрузки выглядит примерно так:

  try: return sys.modules[pkgname] except KeyError: if level < 1: warn("Parent module '%s' not found while handling " "absolute import" % pkgname, RuntimeWarning, 1) return None else: raise SystemError, ("Parent module '%s' not loaded, cannot " "perform relative import" % pkgname) 

что заставляет меня думать, что, возможно, ваш модуль не находится на sys.path. Если вы запустите Python (обычно) и просто введите «import mainpack» в подсказке, что он делает? Он должен уметь его найти.

Я пробовал это сам и получил ту же ошибку. После некоторого чтения я нашел следующее решение:

 # foo/__main__.py import sys mod = __import__('foo') sys.modules["foo"]=mod __package__='foo' from .bar import hello hello() 

Мне кажется, это немного хаки, но это работает. Кажется, что трюк гарантирует загрузку пакета foo поэтому импорт может быть относительным.

«Шаблон», указанный в PEP 366, кажется неполным. Хотя он задает переменную __package__ , она фактически не импортирует пакет, что также необходимо для обеспечения относительного импорта. Решение extraneon находится на правильном пути.

Обратите внимание, что недостаточно просто иметь каталог, содержащий модуль в sys.path , соответствующий пакет должен быть явно импортирован. Следующее выглядит как лучший шаблон, чем то, что было дано в PEP 366 для обеспечения возможности выполнения модуля python независимо от способа его запуска (через регулярный import или с помощью python -m или с помощью python из любого места):

 # boilerplate to allow running as script directly if __name__ == "__main__" and __package__ is None: import sys, os # The following assumes the script is in the top level of the package # directory. We use dirname() to help get the parent directory to add to # sys.path, so that we can import the current package. This is necessary # since when invoked directly, the 'current' package is not automatically # imported. parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(1, parent_dir) import mypackage __package__ = str("mypackage") del sys, os # now you can use relative imports here that will work regardless of how this # python file was accessed (either through 'import', through 'python -m', or # directly. 

Если сценарий не находится на верхнем уровне каталога пакета, и вам нужно импортировать модуль под верхним уровнем, тогда имя os.path.dirname должно повторяться до тех пор, пока parent_dir будет каталогом верхнего уровня.

Вдохновленные ответами extraneon и taherh здесь есть некоторый код, который запускает дерево файлов до тех пор, пока у него не закончится __init__.py для создания полного имени пакета. Это определенно взломанно, но, похоже, работает независимо от глубины файла в дереве каталогов. Кажется, что абсолютный импорт сильно поощряется.

 import os, sys if __name__ == "__main__" and __package__ is None: d,f = os.path.split(os.path.abspath(__file__)) f = os.path.splitext(f)[0] __package__ = [f] #__package__ will be a reversed list of package name parts while os.path.exists(os.path.join(d,'__init__.py')): #go up until we run out of __init__.py files d,name = os.path.split(d) #pull of a lowest level directory name __package__.append(name) #add it to the package parts list __package__ = ".".join(reversed(__package__)) #create the full package name mod = __import__(__package__) #this assumes the top level package is in your $PYTHONPATH sys.modules[__package__] = mod #add to modules 

Это минимальная настройка, основанная на большинстве других ответов, протестированных на python 2.7 с таким раскладком пакетов. Это также имеет то преимущество, что вы можете вызывать скрипт runme.py из любого места, и кажется, что он делает правильные вещи – я еще не тестировал его в более сложной настройке, поэтому предостережение emptor … и т. Д.

Это в основном ответ Брэда выше с вставкой в ​​sys.path, о котором другие рассказывали.

 packagetest/ __init__.py # Empty mylib/ __init__.py # Empty utils.py # def times2(x): return x*2 scripts/ __init__.py # Empty runme.py # See below (executable) 

runme.py выглядит так:

 #!/usr/bin/env python if __name__ == '__main__' and __package__ is None: from os import sys, path d = path.dirname(path.abspath(__file__)) __package__ = [] while path.exists(path.join(d, '__init__.py')): d, name = path.split(d) __package__.append(name) __package__ = ".".join(reversed(__package__)) sys.path.insert(1, d) mod = __import__(__package__) sys.modules[__package__] = mod from ..mylib.utils import times2 print times2(4) 
Python - лучший язык программирования в мире.