Относительный импорт за миллиард

Я был здесь:

и множество URL-адресов, которые я не копировал, некоторые на SO, некоторые на других сайтах, когда я думал, что быстро найду решение.

Постоянно повторяющийся вопрос заключается в следующем: в Windows 7, 32-битном Python 2.7.3, как мне решить это сообщение "Попытка относительного импорта в не пакет"? Я создал точную копию пакета на pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

Импорт был выполнен из консоли.

Я сделал функции с именем спам и яйца в соответствующих модулях. Естественно, это не сработало. Ответ, по-видимому, находится в 4-м URL-адресе, который я перечислил, но это все мои выпускники. Был один ответ на одном из URL, которые я посетил:

Относительный импорт использует атрибут имени модуля, чтобы определить позицию этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, для него установлено значение "main"), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.

Приведенный выше ответ выглядит многообещающе, но все это для меня иероглифы. Итак, мой вопрос, как сделать так, чтобы Python не возвращал мне "Попытка относительного импорта в неупакованном виде"? есть ответ, который включает -m, предположительно.

Может кто-нибудь сказать мне, почему Python выдает это сообщение об ошибке, что означает "не пакет", почему и как вы определяете "пакет", и точный ответ, выраженный в терминах, достаточно простых для понимания в детском саду.

Ответ 1

Скрипт против модуля

Здесь объяснение. Короткая версия заключается в том, что существует большая разница между прямым запуском файла Python и импортом этого файла из другого места. Знание того, в каком каталоге находится файл, не определяет, в каком пакете Python, по его мнению, находится. Это зависит, кроме того, от того, как вы загрузите файл в Python (запустив или импортировав).

Есть два способа загрузить файл Python: как скрипт верхнего уровня или как модуль. Файл загружается как скрипт верхнего уровня, если вы выполняете его напрямую, например, набирая python myfile.py в командной строке. Он загружается как модуль, если вы делаете python -m myfile, или если он загружается, когда оператор import встречается внутри какого-либо другого файла. За один раз может быть только один скрипт верхнего уровня; скрипт верхнего уровня - это файл Python, который вы запустили для начала.

Именование

Когда файл загружается, ему присваивается имя (которое хранится в его атрибуте __name__). Если он был загружен как скрипт верхнего уровня, его имя __main__. Если он был загружен как модуль, его именем является имя файла, которому предшествуют имена любых пакетов/подпакетов, частью которых он является, разделенных точками.

Так, например, в вашем примере:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

если вы импортировали moduleX (примечание: импортирован, непосредственно не выполнен), его имя будет package.subpackage1.moduleX. Если вы импортировали moduleA, его имя будет package.moduleA. Однако если вы напрямую запустите moduleX из командной строки, его имя будет __main__, а если вы запустите moduleA из командной строки, его имя будет __main__. Когда модуль запускается как скрипт верхнего уровня, он теряет свое обычное имя, а вместо него используется имя __main__.

Доступ к модулю НЕ через содержащий его пакет

Есть дополнительная складка: имя модуля зависит от того, был ли он импортирован "напрямую" из каталога, в котором он находится, или импортирован через пакет. Это имеет значение только в том случае, если вы запускаете Python в каталоге и пытаетесь импортировать файл в тот же каталог (или его подкаталог). Например, если вы запустите интерпретатор Python в каталоге package/subpackage1 а затем выполните import moduleX, имя moduleX будет просто moduleX, а не package.subpackage1.moduleX. Это связано с тем, что Python добавляет текущий каталог в путь поиска при запуске; если он находит импортируемый модуль в текущем каталоге, он не будет знать, что этот каталог является частью пакета, и информация о пакете не станет частью имени модуля.

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

Теперь вот что важно для вашего сообщения об ошибке: если имя модуля не имеет точек, оно не считается частью пакета. Неважно, где файл на самом деле находится на диске. Все, что имеет значение, это то, как его имя, и его имя зависит от того, как вы его загрузили.

Теперь посмотрите на цитату, которую вы включили в свой вопрос:

Относительный импорт использует атрибут имени модуля, чтобы определить позицию этого модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, для него установлено значение "main"), то относительный импорт разрешается так, как если бы модуль был модулем верхнего уровня, независимо от того, где этот модуль фактически расположен в файловой системе.

Относительный импорт...

Относительный импорт использует имя модуля, чтобы определить, где он находится в пакете. Когда вы используете относительный импорт, например, from.. import foo, точки указывают на увеличение количества уровней в иерархии пакетов. Например, если ваше текущее имя модуля - package.subpackage1.moduleX, то ..moduleA будет означать package.moduleA. Для того чтобы from.. import работало, имя модуля должно содержать как минимум столько же точек, сколько в операторе import.

... являются относительными только в пакете

Однако, если ваше имя модуля __main__, оно не считается в пакете. Его имя не имеет точек, и поэтому вы не можете использовать from.. import операторы from.. import внутри него. Если вы попытаетесь это сделать, вы получите ошибку "относительный импорт в не пакет".

Скрипты не могут импортировать относительные

Вероятно, вы пытались запустить moduleX или подобное из командной строки. Когда вы это сделали, его имя было установлено на __main__, что означает, что относительный импорт внутри него завершится неудачно, потому что его имя не показывает, что он находится в пакете. Обратите внимание, что это также произойдет, если вы запустите Python из того же каталога, где находится модуль, а затем попытаетесь импортировать этот модуль, потому что, как описано выше, Python найдет модуль в текущем каталоге "слишком рано", не осознавая, что это часть пакета.

Также помните, что при запуске интерактивного интерпретатора "имя" этого интерактивного сеанса всегда __main__. Таким образом, вы не можете выполнять относительный импорт напрямую из интерактивного сеанса. Относительный импорт предназначен только для использования в файлах модуля.

Два решения:

  1. Если вы действительно хотите запустить moduleX напрямую, но все же хотите, чтобы он считался частью пакета, вы можете сделать python -m package.subpackage1.moduleX. -m говорит Python загружать его как модуль, а не как скрипт верхнего уровня.

  2. Или, возможно, вы на самом деле не хотите запускать moduleX, вы просто хотите запустить какой-то другой скрипт, скажем, myfile.py, который использует функции внутри moduleX. Если это так, поместите myfile.py другое место - не в каталог package - и запустите его. Если внутри myfile.py вы делаете что-то вроде from package.moduleA import spam, он будет работать нормально.

Заметки

  • Для любого из этих решений каталог пакета (package в вашем примере) должен быть доступен из пути поиска модуля Python (sys.path). Если это не так, вы не сможете использовать что-либо в пакете надежно.

  • Начиная с Python 2.6, "имя" модуля в целях разрешения пакетов определяется не только его атрибутами __name__ но и атрибутом __package__. Вот почему я избегаю использовать явный символ __name__ для ссылки на модуль "имя". Начиная с Python 2.6, модуль "name" по сути является __package__ + '.' + __name__ __package__ + '.' + __name__ или просто __name__ если __package__ None.)

Ответ 2

Это действительно проблема в Python. Истоки путаницы в том, что люди ошибочно принимают относительное значение как относительный путь, а это не так.

Например, когда вы пишете в faa.py:

from .. import foo

Это имеет смысл, только если faa.py был идентифицирован и загружен python во время выполнения как часть пакета. В этом случае имя модуля для faa.py будет, например, some_packagename.faa. Если файл был загружен только потому, что он находится в текущем каталоге, при запуске Python его имя не будет ссылаться на какой-либо пакет, и в конечном итоге относительный импорт завершится неудачно.

Простое решение для ссылки на модули в текущем каталоге, это использовать это:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo

Ответ 3

Вот общий рецепт, модифицированный в качестве примера, который я сейчас использую для работы с библиотеками Python, написанными как пакеты, которые содержат взаимозависимые файлы, и я хочу иметь возможность тестировать их части по частям. Давайте назовем это lib.foo и скажем, что ему нужен доступ к lib.fileA для функций f1 и f2 и lib.fileB для класса Class3.

Я включил несколько звонков print, чтобы проиллюстрировать, как это работает. На практике вы захотите удалить их (и, возможно, также строку from __future__ import print_function).

Этот конкретный пример слишком прост, чтобы показать, когда нам действительно нужно вставить запись в sys.path. (См. ответ ларса для случая, когда он нам нужен, когда у нас есть два или более уровня каталогов пакетов, а затем мы используем os.path.dirname(os.path.dirname(__file__)), но здесь это тоже не мешает.) также достаточно безопасно сделать это без теста if _i in sys.path. Однако, если каждый импортируемый файл вставляет один и тот же путь, например, если fileA и fileB хотят импортировать утилиты из пакета, это приводит к тому, что sys.path загромождает один и тот же путь много раз, поэтому приятно иметь if _i not in sys.path в шаблоне.

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

Идея здесь заключается в следующем (и обратите внимание, что все они работают одинаково в python2.7 и python 3.x):

  1. Если запустить как import lib или from lib import foo как обычный импорт пакета из обычного кода, __package будет lib, а __name__ будет lib.foo. Мы берем первый путь к коду, импортируем из .fileA и т.д.

  2. Если запустить как python lib/foo.py, __package__ будет Нет, а __name__ будет __main__.

    Мы берем второй путь кода. Каталог lib уже будет в sys.path, поэтому добавлять его нет необходимости. Мы импортируем из fileA и т.д.

  3. При запуске в каталоге lib с именем python foo.py поведение такое же, как и в случае 2.

  4. При запуске в каталоге lib с именем python -m foo поведение аналогично случаям 2 и 3. Однако путь к каталогу lib отсутствует в sys.path, поэтому мы добавляем его перед импортом. То же самое применимо, если мы запустим Python, а затем import foo.

    (Поскольку . находится в sys.path, нам на самом деле не нужно добавлять абсолютную версию пути здесь. Именно здесь более глубокая структура вложения пакета, где мы хотим сделать from ..otherlib.fileC import ..., имеет значение. Если Вы не делаете этого, вы можете полностью опустить все манипуляции sys.path.)

Примечания

Все еще есть причуды. Если вы запустите все это извне:

$ python2 lib.foo

или:

$ python3 lib.foo

поведение зависит от содержимого lib/__init__.py. Если это существует и пусто, все хорошо:

Package named 'lib'; __name__ is '__main__'

Но если lib/__init__.py сам импортирует routine, чтобы он мог экспортировать routine.name напрямую как lib.name, вы получите:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

То есть модуль импортируется дважды, один раз через пакет, а затем снова как __main__, чтобы он выполнял ваш код main. Python 3.6 и более поздние версии предупреждают об этом:

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

Предупреждение новое, а поведение с предупреждением - нет. Это часть того, что некоторые называют двойной ловушкой импорта. (Дополнительные сведения см. в выпуске 27487.) Ник Коглан говорит:

Эта следующая ловушка существует во всех текущих версиях Python, включая 3.3, и ее можно суммировать в следующем общем руководстве: "Никогда не добавляйте каталог пакета или любой каталог внутри пакета непосредственно в путь Python".

Обратите внимание, что хотя мы нарушаем это правило здесь, мы делаем это только тогда, когда загружаемый файл не загружается как часть пакета, и наша модификация специально разработана, чтобы позволить нам получить доступ к другим файлам в этом пакете. (И, как я уже отметил, мы, вероятно, не должны делать этого вообще для одноуровневых пакетов.) Если мы хотим быть сверхчистыми, мы могли бы переписать это как, например:

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

То есть мы модифицируем sys.path достаточно долго, чтобы выполнить импорт, а затем возвращаем его обратно в прежнее состояние (удаляем одну копию _i, если и только если мы добавили одну копию _i).

Ответ 4

Так что, позаботившись об этом вместе со многими другими, я наткнулся на заметку, опубликованную Дорианом Б в этой статье, в которой решалась конкретная проблема, с которой я столкнулся, когда я разрабатывал модули и классы для использования с веб-сервисом, но я также хочу в состоянии проверить их, как я кодирую, используя средства отладчика в PyCharm. Чтобы запустить тесты в отдельном классе, я бы добавил в конец файла моего класса следующее:

if __name__ == '__main__':
   # run test code here...

но если бы я хотел импортировать другие классы или модули в той же папке, то мне пришлось бы изменить все мои операторы импорта с относительной нотации на локальные ссылки (т.е. удалить точку (.)) Но после прочтения предложения Дориана я попробовал его ' one-liner 'и это сработало! Теперь я могу тестировать в PyCharm и оставлять свой тестовый код на месте, когда использую класс в другом тестируемом классе или когда я использую его в своем веб-сервисе!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

Оператор if проверяет, запускаем ли мы этот модуль как основной или используется ли он в другом модуле, который тестируется как основной. Возможно, это очевидно, но я предлагаю эту заметку здесь на тот случай, если кто-то еще, разочарованный относительными проблемами импорта, может использовать ее.

Ответ 5

__name__ изменяется в зависимости от того, выполняется ли этот код в глобальном пространстве имен или как часть импортированного модуля.

Если код не запущен в глобальном пространстве, __name__ будет именем модуля. Если он запущен в глобальном пространстве имен, например, если вы введете его в консоль или запустите модуль как script с помощью python.exe yourscriptnamehere.py, тогда __name__ станет "__main__".

Вы увидите, что много кода python с if __name__ == '__main__' используется для проверки того, выполняется ли код из глобального пространства имен, что позволяет вам иметь модуль, который удваивается как script.

Вы пытались выполнить эти импортные операции с консоли?

Ответ 6

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

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()

Ответ 7

У меня была аналогичная проблема, когда я не хотел менять поиск модуля Python путь и необходимо загрузить модуль относительно от script (несмотря на то, что "скрипты не могут импортировать относительные со всеми", как хорошо объяснил BrenBarn выше).

Итак, я использовал следующий хак. К сожалению, он полагается на модуль imp, который стал устаревшим с версии 3.4 для отказа в пользу importlib. (Возможно ли это с помощью importlib тоже? Я не знаю.) Тем не менее, взлом работает пока.

Пример для доступа к элементам moduleX в subpackage1 из script, находящихся в папке subpackage2:

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

Более чистым подходом, по-видимому, является изменение sys.path, используемого для загрузки модулей, как упомянуто Federico.

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *

Ответ 8

Относительный импорт использует атрибут имени модуля для определения этой позиции модуля в иерархии пакетов. Если имя модуля не содержит никакой информации о пакете (например, оно установлено в "main" ), то относительный импорт разрешается, как если бы модуль был модулем верхнего уровня, независимо от того, где модуль фактически находится в файловой системе.

Написал небольшой пакет python для PyPi, который может помочь зрителям в этом вопросе. Пакет действует как обходной путь, если вы хотите иметь возможность запускать файлы python, содержащие импорт, содержащий пакеты верхнего уровня из пакета/проекта, не находясь непосредственно в каталоге файлов импорта. https://pypi.org/project/import-anywhere/

Ответ 9

Ответ @BrenBarn говорит сам за себя, но если вы похожи на меня, это может занять некоторое время, чтобы понять. Вот мой случай и то, как к нему относится ответ @BrenBarn, возможно, он вам поможет.

Дело

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

Используя наш знакомый пример, добавьте к нему, что moduleX.py имеет относительный импорт в..moduleA. Учитывая, что я пытался написать тестовый скрипт в каталоге subpackage1, который импортировал moduleX, но затем получил страшную ошибку, описанную в OP.

Решение

Переместите тестовый скрипт на тот же уровень, что и package, и импортируйте package.subpackage1.moduleX

Объяснение

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

Когда я импортирую moduleX сверху, тогда имя внутри moduleX - package.subpackage1.moduleX, и можно найти относительный импорт

Ответ 10

Чтобы Python не возвращался ко мне "Попытка относительного импорта в не-пакет". пакет /

init.py subpackage1/init.py moduleX.py moduleY.py subpackage2/init.py moduleZ.py moduleA.py

Эта ошибка возникает, только если вы применяете относительный импорт к родительскому файлу. Например, родительский файл уже возвращает main после того, как вы напечатали код "print (name)" в moduleA.py. Так что этот файл уже является основным, он не может в дальнейшем возвращать родительский пакет. относительный импорт требуется в файлах пакетов subpackage1 и subpackage2, которые вы можете использовать ".." для ссылки на родительский каталог или модуль. Но родительский пакет - если уже пакет верхнего уровня, он не может идти дальше выше этого родительского каталога (пакета). Такие файлы, где вы применяете относительный импорт к родителям, могут работать только с приложением абсолютного импорта. Если вы будете использовать ABSOLUTE IMPORT В РОДИТЕЛЬСКОМ ПАКЕТЕ, НЕТ ОШИБКИ, поскольку python знает, кто находится на верхнем уровне пакета, даже если ваш файл находится в подпакетах из-за концепции PYTHON PATH, которая определяет верхний уровень проекта.