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


Я здесь:

и много 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 не возвратится ко мне "покушение на относительный импорт в упаковке"? есть ответ, который включает в себя -м, якобы.

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

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

8 312

8 ответов:

скрипт и модуль

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

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

наименования

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

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

то, что вы, вероятно, сделали, это вы пытались бежать 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__ атрибуты, но и . Вот почему я избегаю использования явного символа __name__ для ссылки на "имя" модуля. Поскольку Python 2.6 "имя" модуля эффективно __package__ + '.' + __name__, или просто __name__ если __package__ и None.)

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

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

from .. import foo

это имеет смысл только если faa.г был определены и загружены на 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

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

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

этот пример слишком прост, чтобы показать, когда нам нужно вставить в sys.path. (См.Ларса ответ для случая, когда мы do это нужно, когда у нас есть два или более уровней каталогов пакетов, а затем мы используем os.path.dirname(os.path.dirname(__file__)) - но это на самом деле не больно здесь.) Это также достаточно безопасно, чтобы сделать это без как python foo.py, поведение такое же, как в случае 2.

  • если запустить в пределах как python -m foo, поведение аналогично случаям 2 и 3. Однако, путь к не 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__'
    

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

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

        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).

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

    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()
    

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

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

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

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

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

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

    пример для доступа к членам moduleX на subpackage1 из скрипта, находящегося в :

    #!/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.путь, используемый для загрузки модулей, как упоминалось Федерико.

    #!/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 *
    

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

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

    но если я если бы я хотел импортировать другие классы или модули в ту же папку, мне пришлось бы изменить все мои операторы импорта с относительной нотации на локальные ссылки (т. е. удалить точку (.)) Но после прочтения предложения Дориана, я попробовал его "один лайнер", и это сработало! Теперь я могу тестировать в 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 проверяет, запускаем ли мы этот модуль как main или если он используется в другом модуле, который тестируют как main. Возможно, это очевидно, но я предлагаю эту заметку здесь, Если кто-то еще расстроен относительными проблемами импорта выше, может использовать ее.

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

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