Импорт модулей из родительской папки

627

Я использую Python 2.5.

Это мое дерево папок:

ptdraft/
  nib.py
  simulations/
    life/
      life.py

(У меня тоже есть __init__.pyв каждой папке, здесь опущено для удобства чтения)

Как импортировать nibмодуль изнутри lifeмодуля? Я надеюсь, что можно обойтись без возиться с sys.path.

Примечание. Основной модуль, который запускается, находится в ptdraftпапке.

Рам Рахум
источник
1
Каковы ваши настройки PYTHONPATH?
S.Lott
2
Росс: Я посмотрел там. Что мне с этим делать? У меня уже есть __init__.py. С.Лотт: Я не знаю, как проверить ...
Рам Рахум
4
эхо $ PYTHONPATH из оболочки; импорт системы; распечатать sys.path из Python. docs.python.org/tutorial/…
S.Lott
@FlipMcF Google - запутанная поисковая система, поэтому тот факт, что этот результат довольно высок для вас, не имеет значения. Гораздо важнее тот факт, что поисковый движок без пузырей, DuckDuckGo, также оценивает это очень высоко.
ArtOfWarfare
3
Я настоятельно рекомендую пропустить все sys.pathили PYTHONPATHответы и проверить отличный ответ np8 . Да, это долго читать. Да, это похоже на большую работу. Но это единственный ответ, который действительно решает проблему правильно и чисто.
Аран-Фей

Ответы:

119

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

Вам нужно добавить каталог, который содержит ptdraftPYTHONPATH

Вы сказали, что import nibработали с вами, это, вероятно, означает, что вы добавили ptdraftсебя (не его родителя) в PYTHONPATH.

Hasen
источник
15
Добавление всего в pythonpath не является хорошим решением, если вы работаете с большим количеством пакетов, которые никогда не будут повторно использоваться.
Джейсон Ченг
@JasonCheng: Тогда почему они пакеты ?
Дэвис Херринг
7
Для меня этот ответ сработал. Тем не менее, чтобы сделать это более явным, процедура должна import sysи затемsys.path.append("..\<parent_folder>")
BCJuan
604

Вы можете использовать относительный импорт (python> = 2.5):

from ... import nib

(Что нового в Python 2.5) PEP 328: Абсолютный и относительный импорт

РЕДАКТИРОВАТЬ : добавил еще одну точку "." пройти две посылки

f3lix
источник
178
ValueError: Attempted relative import in non-package
эндолит
18
@endolith: Вы должны быть в пакете, то есть у вас должен быть файл инициализации .py.
kirbyfan64sos
31
Попытка относительного импорта за пределы пакета верхнего уровня
пользователь
291
Чтобы быть еще более точным, вам нужен __init__.pyфайл.
Кара Дениз
17
См. Также следующий ответ, поскольку добавление __init__.py- это не единственное, что вам нужно сделать: stackoverflow.com/questions/11536764/…
Бен Фармер,
286

Относительный импорт (как в from .. import mymodule) работает только в пакете. Чтобы импортировать «mymodule», который находится в родительском каталоге вашего текущего модуля:

import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir) 

import mymodule

редактировать : __file__атрибут не всегда дается. Вместо использования os.path.abspath(__file__)я теперь предложил использовать модуль inspect для получения имени файла (и пути) текущего файла.

Remi
источник
97
что-то немного короче:sys.path.insert(1, os.path.join(sys.path[0], '..'))
JHolta
8
Любая причина, чтобы избежать sys.path.append () вместо вставки?
Тайлер
2
@Tyler - может иметь значение, если где-то еще parentdir, но в одном из путей, уже указанных в sys.path, есть другой модуль с именем «mymodule». Вставка в parentdirкачестве первого элемента sys.pathсписка гарантирует, что parentdirвместо него будет импортирован модуль из .
Реми
5
@JHolta Если вы не имеете дело с пакетами, ваше решение является лучшим.
Джонатон Рейнхарт
5
@JHolta отличная идея. sys.path.insert(1, os.path.realpath(os.path.pardir))тоже работает
SpeedCoder5
179

Я отправил аналогичный ответ также на вопрос об импорте из родственных пакетов. Вы можете увидеть это здесь .

Решение без sys.pathвзломов

Резюме

  • Оберните код в одну папку (например packaged_stuff)
  • Используйте setup.pyсценарий создания, где вы используете setuptools.setup () .
  • Pip установить пакет в редактируемом состоянии с pip install -e <myproject_folder>
  • Импортировать используя from packaged_stuff.modulename import function_name

Настроить

Я предполагаю ту же структуру папок, что и в вопросе

.
└── ptdraft
    ├── __init__.py
    ├── nib.py
    └── simulations
        ├── __init__.py
        └── life
            ├── __init__.py
            └── life.py

Я называю .корневую папку, и в моем случае она находится в C:\tmp\test_imports.

меры

1) Добавить setup.pyв корневую папку

Содержание setup.pyможно просто

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())

В принципе "любой" setup.pyбудет работать. Это просто минимальный рабочий пример.

2) Используйте виртуальную среду

Если вы знакомы с виртуальными средами, активируйте одну и перейдите к следующему шагу. Использование виртуальных сред не является абсолютно обязательным, но они действительно помогут вам в долгосрочной перспективе (когда у вас более 1 проекта ..). Самые основные шаги (запустить в корневой папке)

  • Создать виртуальную среду
    • python -m venv venv
  • Активировать виртуальную среду
    • . /venv/bin/activate(Linux) или ./venv/Scripts/activate(Win)

Чтобы узнать больше об этом, просто Google "учебник по Python virtualenv" или подобное. Вам, вероятно, никогда не понадобятся какие-либо другие команды, кроме создания, активации и деактивации.

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

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>

3) pip установите ваш проект в редактируемое состояние

Установите пакет верхнего уровня, myprojectиспользуя pip. Хитрость заключается в использовании -eфлага при установке. Таким образом, он устанавливается в редактируемом состоянии, и все изменения, внесенные в файлы .py, будут автоматически включены в установленный пакет.

В корневом каталоге запустите

pip install -e . (обратите внимание на точку, это означает «текущий каталог»)

Вы также можете увидеть, что он установлен с помощью pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0

4) Импорт путем добавления mainfolderк каждому импорту

В этом примере mainfolderбудет ptdraft. Преимущество этого заключается в том, что вы не столкнетесь с конфликтами имен с другими именами модулей (из стандартной библиотеки Python или сторонних модулей).


Пример использования

nib.py

def function_from_nib():
    print('I am the return value from function_from_nib!')

life.py

from ptdraft.nib import function_from_nib

if __name__ == '__main__':
    function_from_nib()

Запуск life.py

(venv) PS C:\tmp\test_imports> python .\ptdraft\simulations\life\life.py
I am the return value from function_from_nib!
NP8
источник
1
Почему это работает? Это как-то меняет PYTHONPATH за кулисами?
Гомер Эсмеральдо
2
Кроме того, почему это лучше или элегантнее, чем использование подхода sys.path?
Гомер Эсмеральдо,
5
@HomeroBarrocasSEsmeraldo Хорошие вопросы. PYTHONPATH env var нетронут. Он устанавливается в пакеты сайтов , которые уже sys.pathвключены и где код обычно устанавливается конечными пользователями. Это лучше, чем sys.pathхаки (и вы можете включить использование PYTHONPATH в эту категорию хаков путей), потому что это означает, что код в разработке должен вести себя так же, как и для других пользователей. Напротив, при использовании модификаций пути операторы импорта будут разрешаться другим способом.
Вим
5
Это, безусловно, лучшее решение, которое я читал. Это создает ссылку на ваш текущий каталог, поэтому все обновления кода напрямую отражаются в вашем рабочем каталоге. Если вам нужно удалить эту запись, вручную удалите файл egg и удалите запись из easy-install в dist-packages (site-packages, если вы использовали pip).
Ракшит Котари
2
почему головная боль при импорте python еще сложнее? Мы должны написать установочный файл, виртуальную среду, установить pip? О, мой бог!
Эмерсон Сю
68

Вы можете использовать путь, зависящий от ОС, в «пути поиска модуля», который указан в sys.path . Таким образом, вы можете легко добавить родительский каталог следующим образом

import sys
sys.path.insert(0,'..')

Если вы хотите добавить родительский каталог,

sys.path.insert(0,'../..')

Это работает как в Python 2 и 3.

Shinbero
источник
6
Это относительно текущего каталога, даже не обязательно содержащего основной скрипт.
Дэвис Херринг
57

Если добавление папки вашего модуля в PYTHONPATH не сработало, вы можете изменить список sys.path в вашей программе, где интерпретатор Python ищет модули для импорта, в документации по Python сказано:

Когда импортируется модуль с именем spam , интерпретатор сначала ищет встроенный модуль с таким именем. Если он не найден, он ищет файл с именем spam.py в списке каталогов, заданных переменной sys.path. sys.path инициализируется из этих мест:

  • каталог, содержащий входной скрипт (или текущий каталог).
  • PYTHONPATH (список имен каталогов с тем же синтаксисом, что и для переменной оболочки PATH).
  • зависящий от установки по умолчанию.

После инициализации программы Python могут изменять sys.path . Каталог, содержащий выполняемый скрипт, помещается в начало пути поиска, перед стандартным путем к библиотеке. Это означает, что скрипты в этом каталоге будут загружены вместо модулей с одинаковыми именами в каталоге библиотеки. Это ошибка, если замена не предназначена.

Зная это, вы можете сделать следующее в своей программе:

import sys
# Add the ptdraft folder path to the sys.path list
sys.path.append('/path/to/ptdraft/')

# Now you can import your module
from ptdraft import nib
# Or just
import ptdraft
Рикардо Мурильо
источник
6
Ваш ответ хороший, но может не всегда работать и не очень переносим. Если программа была перемещена в новое место, /path/to/ptdraftее необходимо будет отредактировать. Существуют решения, которые обрабатывают текущий каталог файла и таким же образом импортируют его из родительской папки.
Эдвард
@ Эдвард, что это за техника?
Шукласваг
1
@Shuklaswag например, ответ stackoverflow.com/questions/714063/… .
Эдвард
50

Не очень много знаю о Python 2.
В Python 3 родительская папка может быть добавлена ​​следующим образом:

import sys 
sys.path.append('..')

... и тогда можно импортировать модули из него

Роб Элленбрук
источник
13
Это работает, только если ваш текущий рабочий каталог таков, что '..'ведет к каталогу с данным модулем.
user5359531
31

Вот простой ответ, чтобы вы могли увидеть, как он работает, маленький и кроссплатформенный.
Он использует только встроенные модули ( os, sysи inspect) , поэтому должны работать
на любой операционной системе (ОС) , так как Python предназначен для этого.

Более короткий код для ответа - меньше строк и переменных

from inspect import getsourcefile
import os.path as path, sys
current_dir = path.dirname(path.abspath(getsourcefile(lambda:0)))
sys.path.insert(0, current_dir[:current_dir.rfind(path.sep)])
import my_module  # Replace "my_module" here with the module name.
sys.path.pop(0)

Для меньшего количества строк, чем это, замените вторую строку import os.path as path, sys, inspect,
добавьте inspect.в начале getsourcefile(строка 3) и удалите первую строку.
- однако это импортирует весь модуль, поэтому может потребоваться больше времени, памяти и ресурсов.

Код для моего ответа ( более длинная версия )

from inspect import getsourcefile
import os.path
import sys

current_path = os.path.abspath(getsourcefile(lambda:0))
current_dir = os.path.dirname(current_path)
parent_dir = current_dir[:current_dir.rfind(os.path.sep)]

sys.path.insert(0, parent_dir)

import my_module  # Replace "my_module" here with the module name.

Он использует пример из ответа переполнения стека. Как получить путь к текущему
исполняемому файлу в Python?
найти источник (имя файла) работающего кода с помощью встроенного инструмента.

from inspect import getsourcefile  
from os.path import abspath  

Далее, где бы вы ни хотели найти исходный файл, просто используйте:

abspath(getsourcefile(lambda:0))

Мой код добавляет путь к файлу sys.path, в список путей питона
, поскольку это позволяет Python для импорта модулей из этой папки.

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

Примечания о переменной имени файла

Мой ответ не использует __file__переменную для получения пути к файлу / имени файла исполняемого
кода, потому что пользователи здесь часто называют его ненадежным . Вы не должны использовать его
для импорта модулей из родительской папки в программах, используемых другими людьми.

Некоторые примеры, где это не работает (цитата из этого вопроса переполнения стека):

• его нельзя найти на некоторых платформах • иногда это не полный путь к файлу

  • py2exeне имеет __file__атрибута, но есть обходной путь
  • Когда вы запускаете из IDLE с execute()нет __file__атрибута
  • OS X 10.6, где я получаю NameError: global name '__file__' is not defined
Эдвард
источник
1
Я сделал это и удалил новую запись пути sys.path.pop(0)сразу после импорта нужного модуля. Тем не менее, последующий импорт все еще шел в это место. Например, у меня есть app/config, app/toolsи app/submodule/config. От submodule, я вставляю app/для импорта tools, затем удаляю app/и пытаюсь импортировать config, но получаю app/configвместо app/submodule/config.
user5359531
Я понял, что один из toolsимпортов также импортируется configиз родительского каталога. Поэтому, когда я позже попытался сделать sys.path.pop(0); import configвнутри submodule, ожидая получить app/submodule/config, я на самом деле получал app/config. Очевидно, Python возвращает кэшированную версию модуля с тем же именем, вместо того, чтобы фактически проверять sys.pathналичие модуля, соответствующего этому имени. sys.pathв этом случае корректность была изменена, Python просто не проверял это из-за того, что модуль с тем же именем уже был загружен.
user5359531
Я думаю, что это точная ссылка на проблему, с которой я столкнулся: docs.python.org/3/reference/import.html#the-module-cache
user5359531
Некоторая полезная информация из 5.3.1. Кэш модуля на странице документации : во время импорта имя модуля ищется в sys.modules ... sys.modules доступны для записи. Удаление ключа сделает недействительной запись в кеше для названного модуля, в результате чего Python начнет новый поиск при следующем импорте. ... Остерегайтесь, хотя, как если бы вы сохранили ссылку на объект модуля, сделали недействительным его кеш, а затем повторно импортировали, эти два объекта будут разными. Напротив, importlib.reload()будет повторно использовать и повторно инициализировать содержимое модуля ...
Эдвард
Еще короче os.path.realpath(getsourcefile(lambda:0) + "/../.."). Получит текущий исходный файл с двумя родительскими символами каталога. Я не использовал, os.path.sepпоскольку getsourcefileбыл возвращает строку, используя /даже в Windows. realpathпозаботится о выделении двух записей каталога из полного пути (в данном случае, имени файла, а затем текущего каталога), который дает родительский каталог.
Коберн
28

Вот более общее решение, которое включает родительский каталог в sys.path (работает для меня):

import os.path, sys
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
Майк
источник
import os, sys\n sys.path.insert(0,os.path.pardir) то же самое, но сортировщик :) (без комментариев в комментариях)
Anti Veeranna
@antiveeranna, если вы используете, os.path.pardirвы не получите реальный путь родителя, просто относительный путь от того места, куда вы звонитеsys.path.insert()
alvas
1
В этом ответе используется __file__переменная, которая может быть ненадежной (не всегда полный путь к файлу, не работает в каждой операционной системе и т. Д.), Как часто упоминают пользователи StackOverflow. Изменение ответа, чтобы не включать его, вызовет меньше проблем и будет более совместимым. Для получения дополнительной информации см. Stackoverflow.com/a/33532002/3787376 .
Эдвард
23

Я обнаружил, что следующий способ работает для импорта пакета из родительского каталога скрипта. В этом примере я хотел бы импортировать функции env.pyиз app.dbпакета.

.
└── my_application
    └── alembic
        └── env.py
    └── app
        ├── __init__.py
        └── db
import os
import sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
Ю. Н.
источник
Итак, хороший однострочник:sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
Juzzlin
Модификация sys.pathвообще - плохая идея. Вам нужен какой-то способ ( например , PYTHONPATH), чтобы ваша библиотека была доступна для другого кода (иначе это на самом деле не библиотека); просто используйте это все время!
Дэвис Херринг
14

Вышеупомянутые решения тоже подойдут. Другое решение этой проблемы

Если вы хотите импортировать что-либо из каталога верхнего уровня. Затем,

from ...module_name import *

Также, если вы хотите импортировать какой-либо модуль из родительского каталога. Затем,

from ..module_name import *

Также, если вы хотите импортировать какой-либо модуль из родительского каталога. Затем,

from ...module_name.another_module import *

Таким образом, вы можете импортировать любой конкретный метод, если хотите.

Равин Гупта
источник
6
Похоже, что так и должно быть, но по какой-то причине это работает не так, как sys.path.appendописано выше, и я не могу импортировать свою библиотеку таким образом. Это то, что я впервые попытался сделать, прежде чем начать Google :) Хорошо, это былоValueError: attempted relative import beyond top-level package
juzzlin
10

Для меня самый короткий и мой любимый oneliner для доступа к родительскому каталогу:

sys.path.append(os.path.dirname(os.getcwd()))

или:

sys.path.insert(1, os.path.dirname(os.getcwd()))

os.getcwd () возвращает имя текущего рабочего каталога, os.path.dirname (directory_name) возвращает имя каталога для переданного.

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

Другой способ - добавить родительский каталог в системную переменную среды PYTHONPATH.

Звиад
источник
2
+1 за упоминание о возможности реструктуризации проекта (в отличие от взлома проблемы)
semore_1267
это не получает родителя, это получает текущий
Рикки Леви
9

В ноутбуке Jupyter

Пока вы работаете в ноутбуке Jupyter, это краткое решение может быть полезным:

%cd ..
import nib

Работает даже без __init__.pyфайла.

Я протестировал его с Anaconda3 на Linux и Windows 7.

Томас Р
источник
2
Не имеет ли смысла добавлять %cd -после импорта, поэтому текущий каталог ноутбука остается неизменным.
Дрор
8

import sys sys.path.append('../')

Андон Жан
источник
8
это не будет работать при выполнении из другого каталога.
Шатлык Ашыралыев
5

Когда __init__.pyбиблиотека не находится в пакетной среде с файлами, библиотека pathlib (включенная в> = Python 3.4) делает очень кратким и интуитивно понятным добавление пути родительского каталога в PYTHONPATH:

import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))
это имеет значение
источник
Можно ли использовать init .py, чтобы обойти эту проблему?
Искусственно
4

тот же стиль, что и в прошлом ответе - но в меньшем количестве строк: P

import os,sys
parentdir = os.path.dirname(__file__)
sys.path.insert(0,parentdir)

файл возвращает местоположение, в котором вы работаете

YFP
источник
Для меня файл - это имя файла без включенного пути. Я запускаю его, используя «ipy filename.py».
Кертис Яллоп
Привет @CurtisYallop в приведенном выше примере мы добавляем dir, который содержит файл [в котором мы сейчас находимся], в котором находится файл python. Вызов os.path.dirname с именем файла должен вернуть путь к файлу, и мы добавляем ТО к пути, а не к файлу явно - HTH :-)
YFP
Вы предполагаете, что __file__ всегда содержит путь плюс файл. Иногда он содержит только имя файла без пути.
Кертис Яллоп
@CurtisYallop - совсем нет, сэр, я предполагаю, что файл - это имя файла, и я использую os.path.dirname (), чтобы получить путь к файлу. У вас есть пример этого не работает? Я хотел бы, чтобы шаги воспроизводились
YFP
1
@CurtisYallop - Нет, я не! Я говорю, что: print / _ / file / _ / даст вам имя файла в вашем примере выше "file.py" - я тогда говорю, что вы можете использовать os.path.dirname (), чтобы извлечь полный путь из этого , Если вы звоните из какого-то странного места и вам нужен этот реляционный код, вы можете легко найти свой рабочий каталог через модуль os - Боже!
YFP
1

Работа с библиотеками. Создайте библиотеку с именем nib, установите ее с помощью setup.py, пусть она будет находиться в пакетах сайтов, и ваши проблемы будут решены. Вам не нужно собирать все, что вы делаете в одной упаковке. Разбейте его на куски.

user3181121
источник
1
Ваша идея хороша, но некоторые люди могут захотеть связать свой модуль со своим кодом - возможно, сделать его переносимым и не требовать установки своих модулей site-packagesкаждый раз, когда они запускают его на другом компьютере.
Эдвард
0

В системе Linux вы можете создать программную ссылку из папки «life» на файл nib.py. Затем вы можете просто импортировать его как:

import nib
Эрель Сегал-Халеви
источник