Impor fungsi lokal dari modul yang ditempatkan di direktori lain dengan impor relatif di Notebook Jupyter menggunakan Python 3

127

Saya memiliki struktur direktori yang mirip dengan berikut ini

meta_project
    project1
        __init__.py
        lib
            module.py
            __init__.py
    notebook_folder
        notebook.jpynb

Ketika bekerja di notebook.jpynbjika saya mencoba untuk menggunakan impor relatif untuk mengakses fungsi function()di module.pydengan:

from ..project1.lib.module import function

Saya mendapatkan kesalahan berikut:

SystemError                               Traceback (most recent call last)
<ipython-input-7-6393744d93ab> in <module>()
----> 1 from ..project1.lib.module import function

SystemError: Parent module '' not loaded, cannot perform relative import

Apakah ada cara untuk membuatnya bekerja menggunakan impor relatif?

Catatan, server buku catatan dibuat pada tingkat meta_projectdirektori, jadi harus memiliki akses ke informasi di file tersebut.

Perhatikan, juga, bahwa setidaknya seperti yang dimaksudkan semula project1tidak dianggap sebagai modul dan oleh karena itu tidak memiliki __init__.pyfile, itu hanya dimaksudkan sebagai direktori sistem file. Jika solusi untuk masalah memerlukan memperlakukannya sebagai modul dan menyertakan __init__.pyfile (bahkan yang kosong) itu baik-baik saja, tetapi melakukannya tidak cukup untuk menyelesaikan masalah.

Saya membagikan direktori ini antara mesin dan impor relatif memungkinkan saya menggunakan kode yang sama di mana-mana, & saya sering menggunakan notebook untuk pembuatan prototipe cepat, jadi saran yang melibatkan meretas jalur absolut tidak mungkin membantu.


Sunting: Ini tidak seperti impor Relatif di Python 3 , yang berbicara tentang impor relatif di Python 3 secara umum dan - khususnya - menjalankan skrip dari dalam direktori paket. Ini ada hubungannya dengan bekerja dalam notebook jupyter yang mencoba memanggil fungsi dalam modul lokal di direktori lain yang memiliki aspek umum dan khusus yang berbeda.

mpacer
sumber
1
apakah ada __init__file di direktori paket Anda?
Iron Fist
Ya, di libdirektori.
mpacer
Tolong, sebutkan dalam struktur direktori Anda dalam pertanyaan Anda
Iron Fist
Baru saja mengeditnya begitu saya melihat komentar pertama Anda :). Terima kasih sudah mengerti.
mpacer
Kemungkinan duplikat impor Relatif dengan Python 3
baldr

Jawaban:

174

Saya memiliki contoh yang hampir sama seperti Anda di notebook ini di mana saya ingin mengilustrasikan penggunaan fungsi modul yang berdekatan dengan cara KERING.

Solusi saya adalah memberi tahu Python tentang jalur impor modul tambahan itu dengan menambahkan cuplikan seperti ini ke notebook:

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)

Ini memungkinkan Anda untuk mengimpor fungsi yang diinginkan dari hierarki modul:

from project1.lib.module import function
# use the function normally
function(...)

Perhatikan bahwa Anda perlu menambahkan __init__.pyfile kosong ke folder project1 / dan lib / jika Anda belum memilikinya.

metakermit
sumber
6
Ini memecahkan masalah untuk dapat mengimpor paket menggunakan apa yang kurang lebih merupakan lokasi relatif, tetapi hanya secara tidak langsung. Saya kebetulan tahu Matthias Bussonier (@matt di SE) dan Yuvi Panda (@yuvi di SE) sedang mengembangkan github.com/ipython/ipynb yang akan membahas ini lebih langsung (misalnya, dengan mengizinkan impor relatif menggunakan sintaks standar setelah paket mereka) diimpor). Saya akan menerima jawaban Anda untuk saat ini, dan ketika solusi mereka benar-benar siap untuk digunakan orang lain, saya mungkin akan menulis jawaban tentang cara menggunakannya, atau meminta salah satu dari mereka untuk melakukannya.
mpacer
terima kasih telah menunjukkan init .py yang kosong. Saya adalah pemula python dan mengalami masalah saat mengimpor kelas saya. Saya mendapatkan catatan modul menemukan kesalahan, menambahkan init .py kosong memperbaiki masalah!
Pat Grady
5
File init .py kosong tidak lagi diperlukan di Python 3.
CathyQian
FYI: ada penampil untuk notebook: nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/…
thoroc
26

Datang ke sini mencari praktik terbaik dalam mengabstraksi kode ke submodul saat bekerja di Notebook. Saya tidak yakin ada praktik terbaik. Saya telah mengusulkan ini.

Hierarki proyek seperti:

├── ipynb
   ├── 20170609-Examine_Database_Requirements.ipynb
   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

Dan dari 20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

Ini berfungsi karena secara default Notebook Jupyter dapat mengurai cdperintah. Perhatikan bahwa ini tidak menggunakan sihir Notebook Python. Ini hanya berfungsi tanpa persiapan %bash.

Mengingat 99 kali dari 100 saya bekerja di Docker menggunakan salah satu image Project Jupyter Docker , modifikasi berikut adalah idempotent

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection
Joshua Cook
sumber
Terima kasih. Benar-benar mengerikan pembatasan impor relatif ini.
Michael
Saya juga menggunakan chdirdaripada menambahkan ke path, karena saya tertarik untuk mengimpor dari repo utama serta berinteraksi dengan beberapa file di sana.
TheGrimmScientist
Sayangnya, hal yang paling banyak diretas yang saya lakukan dengan python. Namun, saya tidak dapat menemukan solusi yang lebih baik.
TheGrimmScientist
untuk idempotensi sederhana (memungkinkan sel yang sama untuk berjalan beberapa kali & mendapatkan hasil yang sama) if os.path.isdir('../lib/'): os.chdir('../lib'):; atau, lebih baik, gunakan ../lib/db/dengan Anda postgres.pyagar tidak secara tidak sengaja berpindah ke direktori yang lebih tinggi yang juga berisi direktori lain lib.
michael
1
Saya suka solusi ini sampai saya tidak sengaja mengeksekusi cd ..dua kali.
minhle_r7
15

Sejauh ini, jawaban yang diterima paling berhasil untuk saya. Namun, kekhawatiran saya selalu adalah bahwa ada kemungkinan skenario di mana saya mungkin merefaktor notebooksdirektori menjadi subdirektori, memerlukan untuk mengubah module_pathdi setiap notebook. Saya memutuskan untuk menambahkan file python dalam setiap direktori notebook untuk mengimpor modul yang diperlukan.

Dengan demikian, memiliki struktur proyek berikut:

project
|__notebooks
   |__explore
      |__ notebook1.ipynb
      |__ notebook2.ipynb
      |__ project_path.py
   |__ explain
       |__notebook1.ipynb
       |__project_path.py
|__lib
   |__ __init__.py
   |__ module.py

Saya menambahkan file project_path.pydi setiap subdirektori notebook ( notebooks/exploredan notebooks/explain). File ini berisi kode untuk impor relatif (dari @metakermit):

import sys
import os

module_path = os.path.abspath(os.path.join(os.pardir, os.pardir))
if module_path not in sys.path:
    sys.path.append(module_path)

Dengan cara ini, saya hanya perlu melakukan impor relatif di dalam project_path.pyfile, dan bukan di buku catatan. File buku catatan kemudian hanya perlu diimpor project_pathsebelum diimpor lib. Misalnya di 0.0-notebook.ipynb:

import project_path
import lib

Peringatan di sini adalah bahwa pembalikan impor tidak akan berhasil. INI TIDAK BEKERJA:

import lib
import project_path

Jadi kehati-hatian harus dilakukan selama impor.

Gerges
sumber
3

Saya baru saja menemukan solusi cantik ini:

import sys; sys.path.insert(0, '..') # add parent folder path where lib folder is
import lib.store_load # store_load is a file on my library folder

Anda hanya menginginkan beberapa fungsi dari file itu

from lib.store_load import your_function_name

Jika python version> = 3.3 Anda tidak membutuhkan file init.py di folder

Victor Callejas
sumber
3
Saya menemukan ini sangat membantu. Saya akan menambahkan bahwa modifikasi berikut harus ditambahkan ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler
2

Meneliti topik ini sendiri dan setelah membaca jawabannya saya sarankan menggunakan pustaka path.py karena ini menyediakan pengelola konteks untuk mengubah direktori kerja saat ini.

Anda kemudian memiliki sesuatu seperti

import path
if path.Path('../lib').isdir():
    with path.Path('..'):
        import lib

Meskipun, Anda mungkin mengabaikan isdirpernyataan itu.

Di sini saya akan menambahkan pernyataan cetak agar mudah mengikuti apa yang terjadi

import path
import pandas

print(path.Path.getcwd())
print(path.Path('../lib').isdir())
if path.Path('../lib').isdir():
    with path.Path('..'):
        print(path.Path.getcwd())
        import lib
        print('Success!')
print(path.Path.getcwd())

yang mana keluaran dalam contoh ini (di mana lib berada di /home/jovyan/shared/notebooks/by-team/data-vis/demos/lib):

/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart
/home/jovyan/shared/notebooks/by-team/data-vis/demos
/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart

Karena solusinya menggunakan manajer konteks, Anda dijamin untuk kembali ke direktori kerja Anda sebelumnya, tidak peduli apa status kernel Anda sebelum sel dan tidak peduli pengecualian apa yang dilemparkan dengan mengimpor kode perpustakaan Anda.

marr75
sumber
Ini tidak akan bekerja dalam kombinasi dengan% autoreload, karena jalur modul tidak akan ditemukan pada waktu muat ulang
Johannes
1

Ini 2 sen saya:

impor sys

memetakan jalur tempat file modul berada. Dalam kasus saya itu adalah desktop

sys.path.append ('/ Users / John / Desktop')

Impor seluruh modul pemetaan TETAPI Anda harus menggunakan .notation untuk memetakan kelas seperti pemetaan.Shipping ()

import mapping # mapping.py adalah nama file modul saya

shipit = mapping.Shipment () #Shipment adalah nama kelas yang perlu saya gunakan dalam modul pemetaan

Atau impor kelas tertentu dari modul pemetaan

dari pemetaan impor Mapping

shipit = Shipment () # Sekarang Anda tidak perlu menggunakan .notation

Polisi
sumber
0

Saya telah menemukan bahwa python-dotenv membantu menyelesaikan masalah ini dengan cukup efektif. Struktur proyek Anda akhirnya sedikit berubah, tetapi kode di buku catatan Anda sedikit lebih sederhana dan konsisten di seluruh buku catatan.

Untuk proyek Anda, lakukan sedikit penginstalan.

pipenv install python-dotenv

Kemudian, proyek berubah menjadi:

├── .env (this can be empty)
├── ipynb
   ├── 20170609-Examine_Database_Requirements.ipynb
   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

Dan terakhir, impor Anda berubah menjadi:

import os
import sys

from dotenv import find_dotenv


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

Sebuah +1 untuk paket ini adalah bahwa buku catatan Anda dapat memiliki beberapa direktori. python-dotenv akan menemukan yang terdekat di direktori induk dan menggunakannya. A +2 untuk pendekatan ini adalah bahwa jupyter akan memuat variabel lingkungan dari file .env saat startup. Whammy ganda.

t.perk
sumber