วิธีการนำเข้าคลาส Python ที่อยู่ในไดเรกทอรีด้านบน


211

ฉันต้องการรับช่วงจากคลาสในไฟล์ที่อยู่ในไดเรกทอรีเหนือคลาสปัจจุบัน

เป็นไปได้หรือไม่ที่จะนำเข้าไฟล์นั้น?

คำตอบ:


177

from ..subpkg2 import mod

สำหรับเอกสาร Python: เมื่ออยู่ในลำดับชั้นของแพคเกจให้ใช้จุดสองจุดดังที่doc statement importกล่าวว่า:

เมื่อระบุโมดูลที่จะนำเข้าคุณไม่จำเป็นต้องระบุชื่อแบบสัมบูรณ์ของโมดูล เมื่อโมดูลหรือแพ็กเกจมีอยู่ในแพคเกจอื่นมันเป็นไปได้ที่จะทำให้การนำเข้าที่เกี่ยวข้องภายในแพคเกจด้านบนเดียวกันโดยไม่ต้องพูดถึงชื่อแพคเกจ โดยการใช้จุดนำในโมดูลหรือแพ็กเกจที่fromระบุหลังจากที่คุณสามารถระบุระดับความสูงของการสำรวจลำดับชั้นของแพ็คเกจปัจจุบันโดยไม่ต้องระบุชื่อที่แน่นอน จุดนำหนึ่งจุดหมายถึงแพคเกจปัจจุบันที่มีโมดูลที่นำเข้าอยู่ สองจุดหมายถึงระดับหนึ่งแพคเกจ จุดสามจุดขึ้นสองระดับ ฯลฯ ดังนั้นหากคุณดำเนินการfrom . import modจากโมดูลในส่วนแพคเกจแล้วคุณจะจบลงด้วยการนำเข้าpkg pkg.modหากคุณดำเนินการfrom ..subpkg2 import modจากภายในpkg.subpkg1คุณจะนำเข้าpkg.subpkg2.mod. ข้อกำหนดสำหรับการนำเข้าญาติอยู่ภายในPEP 328

PEP 328เกี่ยวข้องกับการนำเข้าแบบสัมบูรณ์ / สัมพัทธ์


4
up1 = os.path.abspath ('.. ') sys.path.insert (0, up1)
rp

Pep 328 แสดงเฉพาะ Python-Version: 2.4, 2,5, 2.6 เวอร์ชั่น 3 ถูกปล่อยให้วิญญาณที่มีความรู้มากขึ้น
gimel

22
สิ่งนี้ทริกเกอร์ข้อผิดพลาด ValueError: พยายามนำเข้าที่เกี่ยวข้องเกินกว่าแพ็คเกจระดับบนสุด
Carlo

4
ผลลัพธ์ใน ImportError: พยายามนำเข้าที่เกี่ยวข้องโดยไม่ทราบแพ็คเกจหลัก
Rotkiv

115
import sys
sys.path.append("..") # Adds higher directory to python modules path.

1
มันใช้งานได้สำหรับฉัน หลังจากเพิ่มสิ่งนี้ฉันสามารถนำเข้าโมดูลหลักโดยตรงไม่จำเป็นต้องใช้ ".. "
Evan Hu

8
ซึ่งใช้งานได้โดยประมาณหากค่า PWD ของแอปพลิเคชั่น - directeroy ปัจจุบันเป็นลูกของผู้ปกครอง ดังนั้นแม้ว่าจะใช้งานได้การเปลี่ยนแปลงหลายอย่างในระบบสามารถขับออกไปได้
Phlip

สิ่งนี้ใช้ได้สำหรับฉันเช่นกันสำหรับการนำเข้าโมดูลในระดับที่สูงขึ้น ฉันใช้สิ่งนี้กับ os.chdir (".. ") เพื่อโหลดไฟล์อื่น ๆ ในระดับที่สูงขึ้น
Surendra Shrestha

ดูเหมือนว่าจะทำให้เกิดข้อผิดพลาดเดียวกับคำตอบข้างต้นจาก gimel
คาร์โล

81

คำตอบของ @ gimel นั้นถูกต้องหากคุณสามารถรับประกันลำดับชั้นของแพ็คเกจที่เขากล่าวถึง หากคุณไม่สามารถ - ถ้าต้องการที่แท้จริงของคุณในขณะที่คุณแสดงมันผูกเฉพาะไดเรกทอรีและไม่มีความสัมพันธ์ใด ๆ ที่จำเป็นบรรจุภัณฑ์ - แล้วคุณจะต้องทำงานในการ__file__ที่จะหาไดเรกทอรีแม่ (คู่ของos.path.dirnameสายจะทำ; -) แล้ว (ถ้าไดเรกทอรีที่ไม่ได้อยู่ในsys.path) ย่อหน้าชั่วคราวแทรกกล่าวว่าผบที่เริ่มต้นมากsys.path, __import__ลบกล่าวว่าผบอีกครั้ง - งานยุ่งแน่นอน แต่ "เมื่อคุณต้องคุณต้อง" (และมุ่งมั่นที่จะ Pyhon อย่าหยุดโปรแกรมเมอร์จากการทำสิ่งที่ต้องทำเช่นเดียวกับมาตรฐาน ISO C ที่ระบุไว้ในส่วน "วิญญาณแห่ง C" ในคำนำ! -)

นี่คือตัวอย่างที่อาจเหมาะกับคุณ:

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

import module_in_parent_dir

2
สิ่งนี้อาจเพิ่มไดเรกทอรีที่อยู่ในแพ็คเกจ Python sys.pathดังนั้นจึงทำให้โมดูลเดียวกันพร้อมใช้งานภายใต้ชื่อที่แตกต่างกันและข้อบกพร่องที่เกี่ยวข้องทั้งหมด autopath.py ใน pypy หรือ _preamble.py ใน twisted แก้ปัญหาโดยใช้เกณฑ์การค้นหาที่ระบุแพ็คเกจระดับบนสุดขณะสำรวจไดเรกทอรีขึ้นด้านบน
jfs

4
คุณอาจต้องการทำบางสิ่งเช่นsys.path.remove(pathYouJustAdded)หลังจากการนำเข้าที่คุณต้องการเพื่อไม่ให้รักษาเส้นทางใหม่นี้ไว้
Matthias

35

นำเข้าโมดูลจากไดเรกทอรีซึ่งมีระดับเดียวกับไดเรกทอรีปัจจุบัน:

from .. import module

38
ฉันได้รับ: พยายามนำเข้าที่เกี่ยวข้องเกินกว่าแพ็คเกจระดับบนสุด :(
RicardoE

25
ใช้ from .. import module ฉันได้รับข้อผิดพลาดValueError: พยายามนำเข้าที่เกี่ยวข้องในแพคเกจที่ไม่ใช่โดยทำตามคำแนะนำ
3428154

4

วิธีโหลดโมดูลที่เป็นไดเรกทอรี

คำนำ: ฉันเขียนคำตอบก่อนหน้านี้อย่างมีนัยสำคัญด้วยความหวังว่าจะช่วยให้ผู้คนเข้าสู่ระบบนิเวศของงูใหญ่และหวังว่าจะทำให้ทุกคนประสบความสำเร็จในการเปลี่ยนแปลงที่ดีที่สุดด้วยระบบการนำเข้าของงูใหญ่

สิ่งนี้จะครอบคลุมการนำเข้าที่เกี่ยวข้องภายในแพ็คเกจซึ่งฉันคิดว่าเป็นกรณีที่น่าจะเป็นไปได้มากที่สุดสำหรับคำถามของ OP

Python เป็นระบบโมดูลาร์

นี่คือเหตุผลที่เราเขียนimport fooเพื่อโหลดโมดูล "foo" จากรูทเนมสเปซแทนที่จะเขียน:

foo = dict();  # please avoid doing this
with open(os.path.join(os.path.dirname(__file__), '../foo.py') as foo_fh:  # please avoid doing this
    exec(compile(foo_fh.read(), 'foo.py', 'exec'), foo)  # please avoid doing this

Python ไม่ได้เชื่อมต่อกับระบบไฟล์

นี่คือเหตุผลที่เราสามารถฝัง python ในสภาพแวดล้อมที่ไม่มีระบบไฟล์ defacto โดยไม่ต้องมี virtual virtual เช่น Jython

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

ดังนั้นหากการนำเข้าไม่ได้เชื่อมโยงกับระบบไฟล์ "หนึ่งไดเรกทอรีขึ้น" หมายความว่าอย่างไร เราต้องเลือกฮิวริสติกบางอย่าง แต่เราสามารถทำได้ตัวอย่างเช่นเมื่อทำงานภายในแพ็คเกจฮิวริสติกบางตัวได้ถูกกำหนดไว้แล้วซึ่งทำให้การนำเข้าแบบสัมพัทธ์เหมือน.fooและ..fooทำงานภายในแพ็คเกจเดียวกัน เย็น!

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

ตัวอย่างการนำเข้าของไพ ธ อนมีลักษณะดังนี้:

import importlib.util
import sys

# For illustrative purposes.
file_path = os.path.join(os.path.dirname(__file__), '../foo.py')
module_name = 'foo'

foo_spec = importlib.util.spec_from_file_location(module_name, file_path)
# foo_spec is a ModuleSpec specifying a SourceFileLoader
foo_module = importlib.util.module_from_spec(foo_spec)
sys.modules[module_name] = foo_module
foo_spec.loader.exec_module(foo_module)

foo = sys.modules[module_name]
# foo is the sys.modules['foo'] singleton

บรรจุภัณฑ์

มีโครงการตัวอย่างที่ยอดเยี่ยมให้บริการอย่างเป็นทางการที่นี่: https://github.com/pypa/sampleproject

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

โครงสร้างสารบบ

ให้มีชื่อแพ็กเกจfooในบางไดเร็กทอรี (ควรเป็นไดเร็กทอรีว่าง)

some_directory/
    foo.py  # `if __name__ == "__main__":`  lives here

การตั้งค่าของฉันคือการสร้างsetup.pyเป็นพี่น้องfoo.pyเพราะมันทำให้การเขียนไฟล์ setup.py ง่ายขึ้น แต่คุณสามารถเขียนการกำหนดค่าเพื่อเปลี่ยน / เปลี่ยนเส้นทางทุกสิ่งที่ setuptools ทำได้ตามค่าเริ่มต้นหากคุณต้องการ ตัวอย่างเช่นการวางfoo.pyภายใต้ไดเรกทอรี "src /" ค่อนข้างเป็นที่นิยมไม่ครอบคลุมที่นี่

some_directory/
    foo.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    py_modules=['foo'],
)

.

python3 -m pip install --editable ./  # or path/to/some_directory/

"แก้ไขได้" aka -eจะเปลี่ยนเส้นทางเครื่องจักรนำเข้าอีกครั้งเพื่อโหลดไฟล์ต้นฉบับในไดเรกทอรีนี้แทนที่จะคัดลอกไฟล์ที่แน่นอนปัจจุบันไปยังไลบรารีของสภาพแวดล้อมการติดตั้ง สิ่งนี้อาจทำให้เกิดความแตกต่างทางพฤติกรรมในเครื่องของนักพัฒนาตรวจสอบให้แน่ใจว่าได้ทดสอบโค้ดของคุณแล้ว! มีเครื่องมืออื่นที่ไม่ใช่ pip แต่ฉันขอแนะนำ pip ให้เป็นคนเกริ่นนำ :)

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

some_directory/
    foo/
        __init__.py
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
)

ฉันยังต้องการที่จะทำให้การfoo/__main__.pyนี้จะช่วยให้งูหลามที่จะดำเนินการแพคเกจเป็นโมดูลเช่นpython3 -m fooจะดำเนินการเป็นfoo/__main__.py__main__

some_directory/
    foo/
        __init__.py
        __main__.py  # `if __name__ == "__main__":`  lives here, `def main():` too!
    setup.py

.

#!/usr/bin/env python3
# setup.py

import setuptools

setuptools.setup(
    name="foo",
    ...
    packages=['foo'],
    ...
    entry_points={
        'console_scripts': [
            # "foo" will be added to the installing-environment's text mode shell, eg `bash -c foo`
            'foo=foo.__main__:main',
        ]
    },
)

ให้เนื้อมีส่วนร่วมกับโมดูลอื่น ๆ : โดยทั่วไปคุณสามารถมีโครงสร้างไดเรกทอรีดังนี้:

some_directory/
    bar.py           # `import bar`
    foo/
        __init__.py  # `import foo`
        __main__.py
        baz.py       # `import foo.baz
        spam/           
            __init__.py  # `import foo.spam`
            eggs.py      # `import foo.spam.eggs`
    setup.py

setup.py ตามปกติเก็บข้อมูลเมตาดาต้าเกี่ยวกับซอร์สโค้ดภายในเช่น:

  • จำเป็นต้องมีการพึ่งพาอะไรเพื่อติดตั้งชื่อ "install_requires"
  • ชื่อใดที่ควรใช้สำหรับการจัดการแพ็คเกจ (ติดตั้ง / ถอนการติดตั้ง "ชื่อ") ฉันขอแนะนำให้ตรงกับชื่อแพคเกจหลามหลักของคุณในกรณีของเราfooแม้ว่าการแทนที่ขีดล่างสำหรับยัติภังค์เป็นที่นิยม
  • ข้อมูลสิทธิ์ใช้งาน
  • แท็กที่กำหนด (อัลฟ่า / เบต้า / ฯลฯ )
  • แท็กผู้ชม (สำหรับนักพัฒนาสำหรับการเรียนรู้ของเครื่อง ฯลฯ )
  • เนื้อหาเอกสารหน้าเดียว (เช่น README)
  • ชื่อเชลล์ (ชื่อที่คุณพิมพ์ที่เชลล์ผู้ใช้เช่น bash หรือชื่อที่คุณพบในเชลล์ผู้ใช้แบบกราฟิกเช่นเมนูเริ่มต้น)
  • รายการโมดูลของหลามแพ็คเกจนี้จะติดตั้ง (และถอนการติดตั้ง)
  • จุดเริ่มต้นของ defacto "run tests" python ./setup.py test

มันกว้างขวางมากมันสามารถรวบรวมคอมไพล์ได้ทันทีหากโมดูลซอร์สถูกติดตั้งบนเครื่องพัฒนา สำหรับตัวอย่างทุกวันฉันขอแนะนำsetup.py ของ PYPA Sample Repository

หากคุณกำลังปล่อยสิ่งประดิษฐ์สร้างเช่นสำเนาของรหัสที่มีวัตถุประสงค์เพื่อเรียกใช้คอมพิวเตอร์เกือบเหมือนกันไฟล์ requirements.txt เป็นวิธีที่นิยมในการถ่ายภาพข้อมูลการพึ่งพาที่แน่นอนโดยที่ "install_requires" เป็นวิธีที่ดีในการจับภาพขั้นต่ำและ รุ่นที่รองรับสูงสุด อย่างไรก็ตามเนื่องจากเครื่องเป้าหมายเกือบเหมือนกันอยู่แล้วฉันขอแนะนำให้สร้าง tarball ของคำนำหน้าหลามทั้งหมด นี่อาจเป็นเรื่องยุ่งยากและละเอียดเกินไปที่จะเข้าไปที่นี่ ตรวจสอบpip install's --targetตัวเลือกหรือ virtualenv aka venv สำหรับโอกาสในการขาย

กลับไปที่ตัวอย่าง

วิธีนำเข้าไฟล์หนึ่งไดเรกทอรี:

จาก foo / spam / eggs.py หากเราต้องการรหัสจาก foo / baz เราสามารถขอได้จาก namespace ที่แน่นอน:

import foo.baz

หากเราต้องการสำรองความสามารถในการย้ายไข่ไปสู่ไดเรกทอรีอื่น ๆ ในอนาคตด้วยbazการใช้งานแบบสัมพัทธ์อื่น ๆเราสามารถใช้การนำเข้าแบบสัมพัทธ์เช่น:

import ..baz

-5

Python เป็นระบบโมดูลาร์

Python ไม่ได้พึ่งพาระบบไฟล์

ในการโหลดรหัสไพ ธ อนได้อย่างน่าเชื่อถือให้มีรหัสนั้นในโมดูลและโมดูลนั้นติดตั้งในห้องสมุดของงูใหญ่

โมดูลที่ติดตั้งสามารถโหลดได้จากเนมสเปซระดับบนด้วยเสมอ import <name>


มีโครงการตัวอย่างที่ยอดเยี่ยมให้บริการอย่างเป็นทางการที่นี่: https://github.com/pypa/sampleproject

โดยทั่วไปคุณสามารถมีโครงสร้างไดเรกทอรีดังนี้:

the_foo_project/
    setup.py  

    bar.py           # `import bar`
    foo/
      __init__.py    # `import foo`

      baz.py         # `import foo.baz`

      faz/           # `import foo.faz`
        __init__.py
        daz.py       # `import foo.faz.daz` ... etc.

.

ให้แน่ใจว่าการประกาศของคุณsetuptools.setup()ในsetup.py,

ตัวอย่างเป็นทางการ: https://github.com/pypa/sampleproject/blob/master/setup.py

ในกรณีของเราเราอาจต้องการส่งออกbar.pyและfoo/__init__.pyตัวอย่างสั้น ๆ ของฉัน:

setup.py

#!/usr/bin/env python3

import setuptools

setuptools.setup(
    ...
    py_modules=['bar'],
    packages=['foo'],
    ...
    entry_points={}, 
        # Note, any changes to your setup.py, like adding to `packages`, or
        # changing `entry_points` will require the module to be reinstalled;
        # `python3 -m pip install --upgrade --editable ./the_foo_project
)

.

ตอนนี้เราสามารถติดตั้งโมดูลของเราลงในห้องสมุดหลาม ด้วย pip คุณสามารถติดตั้งthe_foo_projectในไลบรารี python ของคุณในโหมดแก้ไขเพื่อให้เราสามารถทำงานในเวลาจริง

python3 -m pip install --editable=./the_foo_project

# if you get a permission error, you can always use 
# `pip ... --user` to install in your user python library

.

ตอนนี้จากบริบทหลามใด ๆ เราสามารถโหลด py_modules และแพ็คเกจที่ใช้ร่วมกันของเรา

foo_script.py

#!/usr/bin/env python3

import bar
import foo

print(dir(bar))
print(dir(foo))

2
ควรพูดถึงว่าฉันมักจะติดตั้งโมดูลของฉันในขณะที่ทำงานกับมันpip install --edit fooเกือบตลอดเวลาใน virtualenv ฉันแทบไม่เคยเขียนโมดูลที่ไม่ได้ตั้งใจจะติดตั้ง ถ้าฉันเข้าใจผิดบางอย่างฉันก็อยากจะรู้
ThorSummoner

ฉันควรจะพูดถึงด้วยว่าการใช้ชุดทดสอบสร้าง venv-auto-creation เช่น tox มีประโยชน์มากเพราะeditableลิงก์ไข่และโมดูลหลามที่ติดตั้งนั้นไม่เหมือนกันทุกประการ ตัวอย่างเช่นการเพิ่มเนมสเปซใหม่ไปยังโมดูลที่แก้ไขได้จะพบได้ในการค้นหาพา ธ ของคุณ แต่หากไม่ได้เอ็กซ์พอร์ตในไฟล์ setup.py จะไม่ถูกบรรจุ / ติดตั้ง! ทดสอบกรณีการใช้ของคุณ :)
ThorSummoner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.