นำเข้าฟังก์ชันโลคัลจากโมดูลที่อยู่ในไดเร็กทอรีอื่นที่มีการนำเข้าแบบสัมพัทธ์ใน Jupyter Notebook โดยใช้ Python 3


127

ฉันมีโครงสร้างไดเรกทอรีคล้ายกับสิ่งต่อไปนี้

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

เมื่อทำงานในnotebook.jpynbถ้าฉันพยายามใช้การนำเข้าสัมพัทธ์เพื่อเข้าถึงฟังก์ชันfunction()ในmodule.py:

from ..project1.lib.module import function

ฉันได้รับข้อผิดพลาดต่อไปนี้:

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

มีวิธีใดบ้างที่จะทำให้สิ่งนี้ทำงานโดยใช้การนำเข้าแบบสัมพัทธ์

หมายเหตุเซิร์ฟเวอร์โน้ตบุ๊กถูกสร้างอินสแตนซ์ที่ระดับของmeta_projectไดเร็กทอรีดังนั้นจึงควรเข้าถึงข้อมูลในไฟล์เหล่านั้น

โปรดทราบว่าอย่างน้อยที่สุดตามที่ตั้งใจไว้เดิมproject1ไม่ได้คิดว่าเป็นโมดูลดังนั้นจึงไม่มี__init__.pyไฟล์จึงมีความหมายเพียงแค่เป็นไดเร็กทอรีระบบไฟล์ หากวิธีการแก้ปัญหาต้องการให้ถือว่าเป็นโมดูลและรวม__init__.pyไฟล์ (แม้แต่ไฟล์เปล่า ๆ ) ก็ใช้ได้ แต่การทำเช่นนั้นไม่เพียงพอที่จะแก้ปัญหา

ฉันแชร์ไดเร็กทอรีนี้ระหว่างเครื่องและการนำเข้าแบบสัมพัทธ์ทำให้ฉันสามารถใช้รหัสเดียวกันได้ทุกที่และฉันมักจะใช้โน้ตบุ๊กในการสร้างต้นแบบอย่างรวดเร็วดังนั้นคำแนะนำที่เกี่ยวข้องกับการแฮ็คด้วยกันพา ธ สัมบูรณ์ไม่น่าจะเป็นประโยชน์


แก้ไข: สิ่งนี้ไม่เหมือนกับการนำเข้าแบบสัมพัทธ์ใน Python 3ซึ่งพูดถึงการนำเข้าแบบสัมพัทธ์ใน Python 3 โดยทั่วไปและโดยเฉพาะอย่างยิ่งการเรียกใช้สคริปต์จากภายในไดเร็กทอรีแพ็คเกจ สิ่งนี้เกี่ยวข้องกับการทำงานภายในสมุดบันทึก jupyter ที่พยายามเรียกใช้ฟังก์ชันในโมดูลภายในเครื่องในไดเร็กทอรีอื่นซึ่งมีทั้งลักษณะทั่วไปและลักษณะเฉพาะที่แตกต่างกัน


1
มี__init__ไฟล์ในไดเร็กทอรีแพ็กเกจของคุณหรือไม่
Iron Fist

ใช่ในlibไดเร็กทอรี
mpacer

โปรดระบุไว้ในโครงสร้างไดเรกทอรีของคุณในคำถามของคุณ
Iron Fist

เพิ่งทำการแก้ไขทันทีที่ฉันเห็นความคิดเห็นแรกของคุณ :) ขอบคุณที่จับได้
mpacer

คำตอบ:


174

ฉันมีเกือบจะเป็นตัวอย่างเดียวกับคุณในสมุดบันทึกนี้ซึ่งฉันต้องการแสดงให้เห็นถึงการใช้ฟังก์ชันของโมดูลที่อยู่ติดกันในลักษณะแห้ง

วิธีแก้ปัญหาของฉันคือบอก Python ถึงเส้นทางการนำเข้าโมดูลเพิ่มเติมนั้นโดยการเพิ่มตัวอย่างข้อมูลเช่นนี้ลงในสมุดบันทึก:

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

สิ่งนี้ช่วยให้คุณนำเข้าฟังก์ชันที่ต้องการจากลำดับชั้นของโมดูล:

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

โปรดทราบว่าจำเป็นต้องเพิ่ม__init__.pyไฟล์ว่างในproject1 /และlib /โฟลเดอร์หากคุณยังไม่มี


6
วิธีนี้ช่วยแก้ปัญหาในการนำเข้าแพ็กเกจโดยใช้ตำแหน่งที่สัมพันธ์กันมากหรือน้อย แต่โดยทางอ้อมเท่านั้น ฉันบังเอิญรู้จัก Matthias Bussonier (@matt บน SE) และ Yuvi Panda (@yuvi บน SE) กำลังพัฒนาgithub.com/ipython/ipynbซึ่งจะกล่าวถึงสิ่งนี้โดยตรงมากขึ้น (เช่นโดยอนุญาตให้นำเข้าแบบสัมพัทธ์โดยใช้ไวยากรณ์มาตรฐานเมื่อแพ็คเกจของพวกเขา นำเข้า) ตอนนี้ฉันจะยอมรับคำตอบของคุณและเมื่อโซลูชันของพวกเขาพร้อมให้คนอื่นใช้ฉันอาจจะเขียนคำตอบเกี่ยวกับวิธีใช้หรือขอให้คนใดคนหนึ่งทำเช่นนั้น
mpacer

ขอบคุณที่ชี้ให้เห็นว่าinitว่างเปล่าฉันเป็นมือใหม่หัด python และประสบปัญหาในการนำเข้าคลาส ฉันได้รับบันทึกโมดูลพบข้อผิดพลาดการเพิ่มinitว่างเปล่าช่วยแก้ปัญหาได้!
Pat Grady

5
ไม่จำเป็นต้องใช้ไฟล์init. py ที่ว่างเปล่าใน Python 3 อีกต่อไป
CathyQian

FYI: มีโปรแกรมดูสำหรับโน้ตบุ๊ก: nbviewer.jupyter.org/github/qPRC/qPRC/blob/master/notebook/…
thoroc

26

มาที่นี่เพื่อค้นหาแนวทางปฏิบัติที่ดีที่สุดในการแยกโค้ดไปยังโมดูลย่อยเมื่อทำงานใน Notebooks ฉันไม่แน่ใจว่ามีแนวทางปฏิบัติที่ดีที่สุด ฉันได้รับการเสนอนี้

ลำดับชั้นของโครงการดังต่อไปนี้:

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

และจาก20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

สิ่งนี้ได้ผลเนื่องจากโดยค่าเริ่มต้น Jupyter Notebook สามารถแยกวิเคราะห์cdคำสั่งได้ โปรดทราบว่าสิ่งนี้ไม่ได้ใช้ประโยชน์จากเวทย์มนตร์ Python Notebook มันใช้งานได้โดยไม่ต้องจ่าย%bashล่วงหน้า

พิจารณาว่า 99 ครั้งจาก 100 ฉันกำลังทำงานในการเทียบท่าโดยใช้หนึ่งในภาพโครงการ Jupyter เทียบท่า , การแก้ไขดังต่อไปนี้คือ idempotent

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

ขอบคุณ น่ากลัวจริงๆกับข้อ จำกัด ของการนำเข้าญาตินี้
Michael

ฉันใช้chdirมากกว่าที่จะเพิ่มไปยังเส้นทางเนื่องจากฉันทั้งคู่สนใจที่จะนำเข้าจาก repo หลักและเชื่อมต่อกับไฟล์บางไฟล์ที่นั่น
TheGrimmScientist

น่าเศร้าที่สิ่งที่ฉันถูกแฮ็กที่สุดใน python แต่ฉันไม่พบทางออกที่ดีกว่านี้
TheGrimmScientist

สำหรับ idempotence ง่าย ๆ (อนุญาตให้เซลล์เดียวกันทำงานหลายครั้งและได้ผลลัพธ์เดียวกัน) if os.path.isdir('../lib/'): os.chdir('../lib'):; หรือดีกว่าการใช้งาน../lib/db/ด้วยpostgres.pyเพื่อที่จะไม่ตั้งใจ chdir libถึงไดเรกทอรีที่สูงขึ้นนอกจากนี้ยังมีอีก
michael

1
ฉันชอบวิธีแก้ปัญหานี้จนกระทั่งฉันเผลอดำเนินการcd ..สองครั้ง
minhle_r7

15

จนถึงตอนนี้คำตอบที่ได้รับการยอมรับได้ผลดีที่สุดสำหรับฉัน อย่างไรก็ตามความกังวลของฉันคือมักจะมีสถานการณ์ที่เป็นไปได้ที่ฉันอาจจะ refactor notebooksไดเร็กทอรีเป็นไดเร็กทอรีย่อยซึ่งจำเป็นต้องเปลี่ยนไดเร็กทอรีmodule_pathทุกเครื่อง ฉันตัดสินใจเพิ่มไฟล์ python ในสมุดบันทึกแต่ละไดเร็กทอรีเพื่อนำเข้าโมดูลที่ต้องการ

ดังนั้นจึงมีโครงสร้างโครงการดังต่อไปนี้:

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

ฉันเพิ่มไฟล์project_path.pyในไดเร็กทอรีย่อยของโน้ตบุ๊กแต่ละตัว ( notebooks/exploreและnotebooks/explain) ไฟล์นี้มีรหัสสำหรับการนำเข้าแบบสัมพัทธ์ (จาก @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)

ด้วยวิธีนี้ฉันต้องทำการอิมพอร์ตแบบสัมพัทธ์ภายในproject_path.pyไฟล์ไม่ใช่ในสมุดบันทึก ไฟล์โน๊ตบุ๊คก็จะเพียงแค่ต้องนำเข้าก่อนนำเข้าproject_path libตัวอย่างเช่นใน0.0-notebook.ipynb:

import project_path
import lib

ข้อแม้คือการย้อนกลับการนำเข้าจะไม่ได้ผล สิ่งนี้ไม่ทำงาน:

import lib
import project_path

ดังนั้นจึงต้องใช้ความระมัดระวังในระหว่างการนำเข้า


3

ฉันเพิ่งพบวิธีแก้ปัญหาที่สวยงามนี้:

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

คุณแค่ต้องการฟังก์ชันบางอย่างของไฟล์นั้น

from lib.store_load import your_function_name

หากเวอร์ชัน python> = 3.3 คุณไม่ต้องการไฟล์ init.py ในโฟลเดอร์


3
ฉันพบว่าสิ่งนี้มีประโยชน์มาก ฉันจะเพิ่มว่าควรเพิ่มการแก้ไขต่อไปนี้ ->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler

2

ค้นคว้าหัวข้อนี้ด้วยตัวเองและอ่านคำตอบแล้วฉันแนะนำให้ใช้ไลบรารี path.pyเนื่องจากมีตัวจัดการบริบทสำหรับการเปลี่ยนไดเร็กทอรีการทำงานปัจจุบัน

จากนั้นคุณมีสิ่งที่ต้องการ

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

แม้ว่าคุณอาจจะละเว้นisdirข้อความนั้น

ฉันจะเพิ่มข้อความการพิมพ์เพื่อให้ง่ายต่อการติดตามสิ่งที่เกิดขึ้น

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

ซึ่งผลลัพธ์ในตัวอย่างนี้ (โดยที่ lib อยู่ที่/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

เนื่องจากโซลูชันใช้ตัวจัดการบริบทคุณจึงรับประกันได้ว่าจะกลับไปที่ไดเร็กทอรีการทำงานก่อนหน้านี้ไม่ว่าเคอร์เนลของคุณจะอยู่ในสถานะใดก่อนเซลล์และไม่ว่าจะมีข้อยกเว้นใดก็ตามที่นำเข้ารหัสไลบรารีของคุณ


สิ่งนี้จะไม่ทำงานร่วมกับ% autoreload เนื่องจากจะไม่พบเส้นทางโมดูลเมื่อโหลดซ้ำ
Johannes

1

นี่คือ 2 เซ็นต์ของฉัน:

นำเข้า sys

แมปพา ธ ที่ไฟล์โมดูลอยู่ ในกรณีของฉันมันคือเดสก์ท็อป

sys.path.append ( '/ ผู้ใช้ / จอห์น / Desktop)

นำเข้าโมดูลการแมปทั้งหมด แต่คุณต้องใช้ .notation เพื่อแมปคลาสเช่น mapping.Shipping ()

import mapping # mapping.py คือชื่อไฟล์โมดูลของฉัน

shipit = mapping.Shipment () #Shipment คือชื่อของคลาสที่ฉันต้องใช้ในโมดูลการทำแผนที่

หรือนำเข้าคลาสเฉพาะจากโมดูลการแม็ป

จากการทำแผนที่การนำเข้า Mapping

shipit = Shipment () # ตอนนี้คุณไม่ต้องใช้. หมายเหตุ


0

ฉันพบว่าpython-dotenvช่วยแก้ปัญหานี้ได้อย่างมีประสิทธิภาพ โครงสร้างโครงการของคุณมีการเปลี่ยนแปลงเล็กน้อย แต่รหัสในสมุดบันทึกของคุณนั้นง่ายกว่าและสอดคล้องกันเล็กน้อยในสมุดบันทึก

สำหรับโครงการของคุณให้ทำการติดตั้งเล็กน้อย

pipenv install python-dotenv

จากนั้นโครงการจะเปลี่ยนเป็น:

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

และสุดท้ายการนำเข้าของคุณเปลี่ยนเป็น:

import os
import sys

from dotenv import find_dotenv


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

+1 สำหรับแพ็กเกจนี้คือโน้ตบุ๊กของคุณอาจมีไดเร็กทอรีที่มีความลึก python-dotenv จะค้นหาสิ่งที่ใกล้เคียงที่สุดในไดเร็กทอรีหลักและใช้มัน A +2 สำหรับวิธีนี้คือ jupyter จะโหลดตัวแปรสภาพแวดล้อมจากไฟล์. env เมื่อเริ่มต้น คำสาปคู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.