Імпорт модулів із батьківської папки


624

Я запускаю 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 . Так, це довго читати. Так, це виглядає як багато роботи. Але це єдина відповідь, яка насправді вирішує проблему правильно і чисто.
Аран-Фей

Відповіді:


118

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

Вам потрібно додати каталог, який містить, ptdraftдо PYTHONPATH

Ви сказали, що import nibпрацювали з вами, це, ймовірно, означає, що ви додали ptdraftсебе (а не його батьків) до PYTHONPATH.


15
Додавання всього до pythonpath не є гарним рішенням, якщо ви працюєте з великою кількістю пакетів, які ніколи не збираються повторно використовувати.
Jason Cheng

@JasonCheng: Тоді чому вони пакети ?
Девіс Оселедець

6
Для мене ця відповідь спрацювала. Однак, щоб зробити це більш явним, процедура полягає в тому, import sysа потімsys.path.append("..\<parent_folder>")
BCJuan

603

Ви можете використовувати відносний імпорт (python> = 2,5):

from ... import nib

(Що нового в Python 2.5) PEP 328: абсолютний та відносний імпорт

EDIT : додано ще одну крапку "." піднятися на два пакети


177
ValueError: Attempted relative import in non-package
ендоліт

18
@endolith: Ви повинні бути в пакеті, тобто у вас повинен бути файл init .py.
kirbyfan64sos

31
Спроба відносного імпорту за межі пакету toplevel
Користувач

291
Щоб бути ще більш точним, вам потрібен __init__.pyфайл.
kara deniz

17
Дивіться також таку відповідь, оскільки додавання __init__.py- не єдине, що вам потрібно зробити: stackoverflow.com/questions/11536764/…
Ben Farmer

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__)я зараз запропонував використовувати модуль перевірки для отримання імені (та шляху) поточного файлу


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

177

Я опублікував аналогічну відповідь також на питання щодо імпорту з пакетів побратимів. Ви можете побачити тут .

Рішення без 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) встановіть проект у редагованому стані

Встановіть пакет верхнього рівня 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!

1
Чому це працює? Чи змінюється ПІТОНПАТ за кулісами?
Хомеро Есмеральдо

2
Крім того, чому це краще чи витонченіше, ніж використання підходу sys.path?
Хомеро Есмеральдо

5
@HomeroBarrocasSEsmeraldo Хороші запитання. PYTHONPATH env var недоторканий. Це встановлюється в пакети сайтів , які вже ввімкнено, sys.pathі де зазвичай код встановлюється кінцевими користувачами. Це краще, ніж sys.pathхаки (і ви можете включити використання PYTHONPATH у цій категорії хакерських маршрутів), оскільки це означає, що код у розробці повинен вести себе так само, як і для інших користувачів. На відміну від цього, при використанні модифікацій шляху імпорти операторів імпортування будуть вирішені по-іншому.
Вім

5
Це, безумовно, найкраще рішення, яке я читав. Це створює посилання на ваш поточний каталог, тому всі оновлення коду безпосередньо відображаються у вашому робочому каталозі. У випадку, якщо вам потрібно буде видалити цей запис, видаліть яєчний файл вручну та видаліть його з простої установки в dist-пакетах (site-пакети, якщо ви використовували pip).
Ракшит Котарі

2
чому ще більше ускладнити головний біль імпорту пітона? Треба написати файл налаштування, віртуальну програму env, встановити файл pip? О Боже мій!
Емерсон Сю

68

Ви можете використовувати шлях залежно від ОС у "шляху пошуку модуля", який вказаний у sys.path . Таким чином, ви можете легко додати батьківський каталог, як описано нижче

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

Якщо ви хочете додати батьківський батьківський каталог,

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

Це працює як у python 2, так і в 3.


6
Це відносно поточного каталогу, навіть не обов'язково того, що містить основний скрипт.
Девіс-оселедець

57

Якщо додавання папки вашого модуля в PYTHONPATH не спрацювало, Ви можете змінити список sys.path у своїй програмі, де інтерпретатор Python шукає модулі для імпорту, документація python говорить:

Коли імпортується модуль з іменем спаму , інтерпретатор спочатку шукає вбудований модуль з цим ім'ям. Якщо його не знайти, то він шукає файл з іменем 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її доведеться редагувати. Є рішення, які опрацьовують поточний каталог файлу та імпортують його з батьківської папки і таким чином.
Едвард

@Edward, що це за методи?
Shuklaswag

1
@Shuklaswag, наприклад, відповідь stackoverflow.com/questions/714063/… .
Едвард

50

Не знаю багато про python 2.
У python 3 батьківську папку можна додати наступним чином:

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

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


13
Це працює лише в тому випадку, якщо ваш поточний робочий каталог такий, що '..'веде до каталогу з відповідним модулем.
користувач5359531

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.
користувач5359531

Я зрозумів, що один із toolsімпортів Росії також імпортується configз материнського режиму. Тож коли я пізніше спробував зробити sys.path.pop(0); import configвсередині submodule, очікуючи потрапити app/submodule/config, я насправді отримував app/config. Очевидно, Python повертає кешовану версію модуля з тим самим іменем, замість того, щоб насправді перевіряти sys.pathмодуль, який відповідає цьому імені. sys.pathв цьому випадку коректно змінювались, Python просто не перевіряв це через те, що вже завантажений одноіменний модуль.
користувач5359531

Я думаю, що це саме посилання на проблему, яку я мав: 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буде подбати про виведення двох записів каталогів з повного шляху (у цьому випадку - ім'я файлу, а потім поточного каталогу), який дає батьківський каталог.
Coburn

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()
дзвоните

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__))))
однолінійний

Модифікація 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 (ім'я_посібника) повертає ім'я каталогу для переданого.

Насправді, на мій погляд, архітектуру проекту Python слід робити так, як жоден модуль із дочірнього каталогу не буде використовувати жодного модуля з батьківського каталогу. Якщо щось подібне трапляється, варто задуматися про дерево проекту.

Інший спосіб - додати батьківський каталог до змінної системного середовища PYTHONPATH.


2
+1 для згадки про можливість реструктуризації проекту (на відміну від того, щоб кинути хаку на проблему)
semore_1267

це не отримує батьків, воно отримує течію
Рікі Леві

9

У зошиті Юпітера

Поки ви працюєте в зошиті Юпітера, це коротке рішення може бути корисним:

%cd ..
import nib

Він працює навіть без __init__.pyфайлу.

Я тестував його з Anaconda3 на Linux та Windows 7.


2
Чи не було б сенсу додавати %cd -після імпорту, тому поточний каталог ноутбука залишається незмінним.
Dror


5

Якщо не знаходитесь в пакетному середовищі з __init__.pyфайлами, бібліотека pathlib (включена до> = Python 3.4) дозволяє дуже стисло та інтуїтивно додавати шлях батьківського каталогу до PYTHONPATH:

import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))

чи можна використовувати init .py, щоб обійти цю проблему?
ArtificiallyIntelligence

4

такий же стиль, як і попередня відповідь - але меншою кількістю рядків: P

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

файл повертає місце, в якому ви працюєте


Для мене файл - це ім'я файлу без включеного шляху. Я запускаю його за допомогою "ipy filename.py".
Кертіс Яллоп

Привіт @CurtisYallop у наведеному вище прикладі ми додаємо редактор, який містить файл [у якому ми зараз перебуваємо], у якому знаходиться файл python. Виклик os.path.dirname з ім'ям файлу повинен повернути шлях до файлу, і ми додають ТО до шляху, а не до явного файлу - HTH :-)
YFP

Ви припускаєте, що __file__ завжди містить шлях плюс файл. Іноді воно містить лише ім'я файлу без шляху.
Кертіс Яллоп

@CurtisYallop - зовсім не сер, я припускаю, що файл - це ім'я файлу, і я використовую os.path.dirname (), щоб отримати шлях до файлу. У вас є приклад того, що це не працює? Я б хотів відтворити кроки
YFP,

1
@CurtisYallop - Ні, я не є! Я кажу, що: print / _ / файл / _ / дасть вам ім'я файлу у вашому прикладі вище "file.py" - я тоді кажу, що ви можете використовувати os.path.dirname (), щоб витягнути з цього повний шлях . Якщо вам дзвонять з якогось дивного місця і вам хотілося б, що це стосунок, ви можете легко знайти свій робочий реєр через модуль os - Jeez!
YFP

1

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


1
Ваша ідея хороша, але деякі люди можуть захотіти зв’язати свій модуль зі своїм кодом - можливо, зробити його портативним і не вимагати розміщення своїх модулів site-packagesщоразу, коли вони запускають його на іншому комп'ютері.
Едвард

0

В системі Linux можна створити м'яке посилання з папки "life" на файл nib.py. Потім ви можете просто імпортувати його так:

import nib
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.