Як зробити відносний імпорт у Python?


527

Уявіть цю структуру каталогу:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Я кодую mod1, і мені потрібно щось імпортувати mod2. Як мені це зробити?

Я спробував, from ..sub2 import mod2але отримую "Спробний відносний імпорт у непакеті".

Я гуляв навколо, але знайшов лише " sys.pathманіпуляційні" хаки. Чи немає чистого способу?


Редагувати: усі мої __init__.pyв даний час порожні

Edit2: Я намагаюся зробити це , тому що sub2 містить класи , які є загальними по підгрупах пакетів ( sub1, subXі т.д.).

Edit3: Поведінка, яку я шукаю, така сама, як описана в PEP 366 (дякую Джон Б)


8
Рекомендую оновити своє запитання, щоб зрозуміти, що ви описуєте проблему, розглянуту в PEP 366.
Джон Б

2
Це давно пояснене пояснення, але перевірте тут: stackoverflow.com/a/10713254/1267156 Я відповів на дуже схоже запитання. Я мав цю саму проблему до минулої ночі.
Sevvy325

3
Для тих, хто бажає завантажити модуль, розміщений на довільній трасі, дивіться це: stackoverflow.com/questions/67631/…
Сергій Євгеній

2
У відповідній примітці Python 3 змінить режим імпорту за замовчуванням на абсолютний за замовчуванням; відносний імпорт повинен бути чітко вказаний.
Росс

Відповіді:


337

Здається, всі хочуть сказати вам, що вам слід робити, а не просто відповідати на питання.

Проблема полягає в тому, що ви запускаєте модуль як "__main__", передаючи mod1.py як аргумент інтерпретатору.

Від PEP 328 :

Відносний імпорт використовує атрибут __name__ модуля для визначення положення цього модуля в ієрархії пакетів. Якщо ім'я модуля не містить жодної інформації про пакет (наприклад, для нього встановлено значення __main__), то відносний імпорт вирішується так, як якщо б модуль був модулем верхнього рівня, незалежно від того, де цей модуль фактично розташований у файловій системі.

У Python 2.6 вони додають можливість посилатися на модулі відносно основного модуля. PEP 366 описує зміни.

Оновлення : За словами Ніка Коглана, рекомендованою альтернативою є запуск модуля всередині пакета за допомогою перемикача -m.


2
Відповідь тут передбачає возитися з sys.path у кожній точці входу до вашої програми. Я думаю, що це єдиний спосіб зробити це.
Нік Реталак

76
Рекомендована альтернатива - запускати модулі всередині пакунків за допомогою -mперемикача, а не безпосередньо вказувати їх ім'я файлу.
ncoghlan

127
Я не розумію: де тут відповідь? Як можна імпортувати модулі в такій структурі каталогу?
Том

27
@Tom: У цьому випадку mod1 буде from sub2 import mod2. Потім, щоб запустити mod1 з додатка, зробіть python -m sub1.mod1.
Xiong Chiamiov

11
@XiongChiamiov: це означає, що ви не можете цього зробити, якщо ваш python вбудований у додаток, тому у вас немає доступу до комутаторів командного рядка python?
LarsH

130

Ось рішення, яке працює для мене:

Я роблю відносний імпорт як from ..sub2 import mod2 і тоді, якщо я хочу запустити, mod1.pyто я переходжу до батьківського каталогу appта запускаю модуль, використовуючи перемикач python -m як python -m app.sub1.mod1.

Справжня причина, чому ця проблема виникає при відносному імпорті, полягає в тому, що відносний імпорт працює, беручи за собою __name__властивість модуля. Якщо модуль безпосередньо запускається, він __name__встановлюється __main__та не містить ніякої інформації про структуру пакету. І тому python скаржиться на relative import in non-packageпомилку.

Отже, за допомогою перемикача -m ви надаєте інформацію про структуру пакету python, завдяки якій він може успішно вирішувати відносний імпорт.

Я стикався з цією проблемою багато разів, роблячи відносний імпорт. І, прочитавши всі попередні відповіді, я все ще не зміг зрозуміти, як це вирішити, чистим способом, не потребуючи введення кодового коду у всі файли. (Хоча деякі коментарі були дуже корисними, завдяки @ncoghlan та @XiongChiamiov)

Сподіваюсь, це допомагає тому, хто бореться з проблемою відносного імпорту, тому що пройти PEP - це справді не цікаво.


9
Найкраща відповідь ІМХО: не тільки пояснює, чому ОП виникло проблему, але й знаходить спосіб вирішити це, не змінюючи способу імпорту його модулів . Якщо взагалі, відносний імпорт ОП був хороший. Винуватець полягав у відсутності доступу до зовнішніх пакетів при безпосередньому запуску сценарію, що -mбуло розроблено для вирішення.
MestreLion

26
Також візьміть до уваги: ​​ця відповідь була через 5 років після запитання. На той час ці функції були недоступні.
JeremyKun

1
Якщо ви хочете імпортувати модуль з того самого каталогу, ви можете це зробити from . import some_module.
Ротарети

124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Ти біжиш python main.py.
  2. main.py робить: import app.package_a.module_a
  3. module_a.py робить import app.package_b.module_b

2 або 3 можуть використовувати: from app.package_a import module_a

Це буде працювати до тих пір, поки у вас є appПІТОНПАТ. main.pyтоді може бути де завгодно.

Таким чином, ви пишете, setup.pyщоб скопіювати (встановити) весь пакет додатків та підпакети до папок python main.pyцільової системи та до папок скриптів цільової системи.


3
Відмінна відповідь. Чи є спосіб імпортувати цей спосіб без встановлення пакета в PYTHONPATH?
аурахам

4
Пропоноване додаткове читання: blog.habnab.it/blog/2013/07/21/python-packages-and-you
nosklo

6
то, одного разу, потрібно змінити назву програми на test_app. що б сталося? Вам потрібно буде змінити всі вихідні коди, імпортувати app.package_b.module_b -> test_app.package_b.module_b. це абсолютно BAD практика ... І ми повинні намагатися використовувати відносний імпорт всередині пакета.
Spybdai

49

"Guido розглядає запущені сценарії в пакеті як анти-шаблон" (відхилено PEP-3122 )

Я витратив стільки часу, намагаючись знайти рішення, читаючи пов’язані публікації тут, на Stack Overflow, і кажу собі: "Треба бути кращим способом!". Схоже, немає.


10
Примітка. Вже згаданий pep-366 (створений приблизно в той же час, що і pep-3122 ) надає ті самі можливості, але використовує іншу реалізацію, сумісну назад, тобто, якщо ви хочете запустити модуль всередині пакета як сценарій і використовувати явний відносний імпорт у ньому тоді ви можете запустити його за допомогою -mперемикача: python -m app.sub1.mod1або викликати app.sub1.mod1.main()з сценарію верхнього рівня (наприклад, згенерованого з пункту входу_точок setuptools, визначеного в setup.py).
jfs

+1 для використання setuptools та точок входу - це правильний спосіб налаштування сценаріїв, які будуть запускатися ззовні, у чітко визначеному місці, на відміну від нескінченного злому PYTHONPATH
RecencyEffect

38

Це вирішено на 100%:

  • додаток /
    • main.py
  • налаштування /
    • local_setings.py

Імпортуйте налаштування / local_setting.py в додаток / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

2
Дякую тобі! всі ppl змушували мене запускати свій сценарій по-іншому, а не говорити, як його вирішити в сценарії. Але мені довелося змінити код на використання sys.path.insert(0, "../settings")і тодіfrom local_settings import *
Віт Бернатик

25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Я використовую цей фрагмент для імпорту модулів із шляхів, сподіваюся, що це допоможе


2
Я використовую цей фрагмент у поєднанні з імпульсним модулем (як пояснено тут [1]), щоб мати великий ефект. [1]: stackoverflow.com/questions/1096216 / ...
Сюн Chiamiov

7
Ймовірно, sys.path.append (шлях) слід замінити sys.path.insert (0, шлях), а sys.path [-1] замінити sys.path [0]. Інакше функція імпортує неправильний модуль, якщо в шляху пошуку вже є модуль з такою ж назвою. Наприклад, якщо в поточному режимі є "some.py", import_path ("/ import / some.py") імпортує неправильний файл.
Алекс Че

Я згоден! Іноді переважає інший відносний імпорт. Використовуйте sys.path.insert
iElectric

Як би ви повторили поведінку з x import y (або *)?
levesque

Не ясно, вкажіть повне використання цього сценарію для вирішення проблеми з ОП.
mrgloom

21

пояснення nosklo'sвідповіді на прикладах

Примітка: усі __init__.pyфайли порожні.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

якщо запустити, $ python main.pyвін повертається:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py робить: from app.package_b import fun_b
  • fun_b.py робить from app.package_a.fun_a import print_a

так що файл у папці package_bвикористовується файл у папці package_a, який ви хочете. Правильно ??


12

На жаль, це злом sys.path, але він працює досить добре.

Я зіткнувся з цією проблемою з іншим шаром: у мене вже був модуль із вказаною назвою, але це був неправильний модуль.

що я хотів зробити, це наступне (модуль, над яким я працював, був модуль3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Зверніть увагу, що я вже встановив мій модуль, але в моїй установці у мене немає "mymodule1"

і я отримаю ImportError, оскільки він намагався імпортувати зі своїх встановлених модулів.

Я спробував зробити sys.path.append, і це не вийшло. Що працювало, було sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Так собі хак, але все це спрацювало! Тож майте на увазі, якщо ви хочете, щоб ваше рішення змінило інші шляхи, тоді вам потрібно використовувати sys.path.insert (0, ім'я шляху), щоб змусити його працювати! Це було дуже неприємним моментом для мене, але багато людей кажуть використовувати функцію "додавання" для sys.path, але це не працює, якщо у вас вже визначений модуль (я вважаю це дуже дивною поведінкою)


sys.path.append('../')добре працює для мене (Python 3.5.2)
Nister

Я думаю, що це добре, оскільки він локалізує злом на виконуваний файл і не впливає на інші модулі, які можуть залежати від ваших пакетів.
Том Расселл

10

Дозвольте мені просто покласти це тут для власного ознайомлення. Я знаю, що це не гарний код Python, але мені потрібен був сценарій для проекту, над яким я працював, і хотів поставити скрипт у scriptsкаталог.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

9

Як говорить @EvgeniSergeev у коментарях до ОП, ви можете імпортувати код з .pyфайлу у довільному місці за допомогою:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Це взято з цієї відповіді .



2

Від доктора Python ,

У Python 2.5 ви можете переключити поведінку імпорту на абсолютний імпорт за допомогою from __future__ import absolute_importдирективи. Ця поведінка з абсолютним імпортом стане за замовчуванням у майбутній версії (ймовірно, Python 2.7). Після абсолютного імпорту за замовчуванням import stringзавжди знайдеться стандартна версія бібліотеки. Користувачам пропонується якомога більше використовувати абсолютний імпорт, тому бажано почати писати from pkg import stringу своєму коді


1

Я вважав, що простіше встановити змінну навколишнього середовища "PYTHONPATH" у верхню папку:

bash$ export PYTHONPATH=/PATH/TO/APP

тоді:

import sub1.func1
#...more import

звичайно, PYTHONPATH є "глобальним", але це ще не створило проблем для мене.


Це по суті, як virtualenvдозволяє керувати вашими заявами про імпорт.
byxor

1

Крім того, що сказав Джон Б, здається, що встановлення __package__змінної має допомогти, а не змінювати, __main__що може накрутити інші речі. Але наскільки я міг перевірити, він не працює повністю так, як слід.

У мене однакова проблема, і ні PEP 328, ні 366 не вирішують проблему повністю, оскільки обом, до кінця дня, потрібно включити голову пакета sys.path, наскільки я міг зрозуміти.

Я також повинен зазначити, що я не знайшов, як відформатувати рядок, який повинен увійти в ці змінні. Це "package_head.subfolder.module_name"чи що?


0

Ви повинні додати шлях модуля до PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"

1
Це приблизно те саме, що маніпулювати sys.path, оскільки sys.pathотримує ініціалізацію зPYTHONPATH
Йоріл,

@Joril Це правильно, але sys.pathйого потрібно жорстко закодувати у вихідному коді, на відміну від PYTHONPATHзмінної середовища, і його можна експортувати.
Giorgos Myrianthous
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.