การนำเข้าแพ็คเกจพี่น้อง


200

ฉันได้ลองอ่านคำถามเกี่ยวกับการนำเข้าของพี่น้องและแม้แต่ เอกสารแพคเกจแต่ฉันยังไม่พบคำตอบ

ด้วยโครงสร้างต่อไปนี้:

├── LICENSE.md
├── README.md
├── api
   ├── __init__.py
   ├── api.py
   └── api_key.py
├── examples
   ├── __init__.py
   ├── example_one.py
   └── example_two.py
└── tests
   ├── __init__.py
   └── test_one.py

สคริปต์ใน examplesและtestsไดเรกทอรีสามารถนำเข้าจาก apiโมดูลและสามารถเรียกใช้จาก commandline ได้อย่างไร

นอกจากนี้ฉันต้องการหลีกเลี่ยงการsys.path.insertแฮ็กที่น่าเกลียดสำหรับทุกไฟล์ แน่นอนสิ่งนี้สามารถทำได้ใน Python ใช่ไหม


7
ฉันขอแนะนำให้ข้ามsys.pathแฮ็กที่ผ่านมาทั้งหมดและอ่านวิธีแก้ปัญหาจริงเท่านั้นที่โพสต์จนถึงขณะนี้ (หลังจาก 7 ปี!)
Aran-Fey

1
ยังไงก็ตามก็ยังมีช่องทางสำหรับการแก้ปัญหาที่ดีอีกวิธีหนึ่ง: การแยกรหัสที่สามารถเรียกใช้งานได้ออกจากรหัสห้องสมุด เวลาส่วนใหญ่ของสคริปต์ภายในแพคเกจไม่ควรจะเป็นปฏิบัติการเพื่อเริ่มต้นด้วย
Aran-Fey

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

@ Aran-Fey นั่นเป็นการเตือนความจำที่ต่ำกว่าในข้อผิดพลาดเกี่ยวกับการนำเข้าที่เกี่ยวข้อง Q & As ฉันกำลังมองหาการแฮ็คตลอดเวลา แต่ลึกฉันรู้ว่ามีวิธีง่ายๆในการออกแบบวิธีการของฉันออกจากปัญหา ไม่ได้บอกว่ามันเป็นคำตอบสำหรับทุกคนที่นี่การอ่าน แต่เป็นการเตือนที่ดีเนื่องจากอาจมีหลายคน
colorlace

คำตอบ:


70

เจ็ดปีหลังจากนั้น

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

  • การติดตั้งแพคเกจ (ใน virtualenv หรือไม่) จะให้สิ่งที่คุณต้องการแม้ว่าฉันจะแนะนำให้ใช้ pip เพื่อทำมันแทนที่จะใช้ setuptools โดยตรง (และใช้setup.cfgเพื่อจัดเก็บข้อมูลเมตา)
  • การใช้-mธงและเรียกใช้เป็นแพคเกจทำงานเกินไป (แต่จะออกบิตอึดอัดถ้าคุณต้องการแปลงไดเรกทอรีการทำงานของคุณเป็นแพคเกจที่ติดตั้งได้)
  • สำหรับการทดสอบโดยเฉพาะpytestสามารถหาแพคเกจ API ในสถานการณ์นี้และดูแลsys.pathแฮ็กสำหรับคุณ

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

คำตอบเก่า

ตามที่ระบุไว้แล้วที่อื่นความจริงอันยิ่งใหญ่คือคุณต้องทำแฮ็กน่าเกลียดเพื่อให้นำเข้าจากโมดูลพี่น้องหรือแพคเกจผู้ปกครองจาก__main__โมดูล ปัญหาที่เป็นรายละเอียดในPEP 366 PEP 3122พยายามจัดการการนำเข้าด้วยวิธีที่มีเหตุผลมากกว่านี้ แต่กุยโดได้ปฏิเสธบัญชีดังกล่าว

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

( ที่นี่ )

แม้ว่าฉันจะใช้รูปแบบนี้เป็นประจำด้วย

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

นี่path[0]คือโฟลเดอร์หลักของสคริปต์ที่ใช้งานและdir(path[0])โฟลเดอร์ระดับบนสุดของคุณ

ฉันยังคงไม่สามารถใช้การนำเข้าแบบสัมพัทธ์กับสิ่งนี้ได้ แต่จะอนุญาตการนำเข้าแบบสัมบูรณ์จากระดับสูงสุด (ในapiโฟลเดอร์หลักตัวอย่างของคุณ)



2
pytest ค้นหาแพ็คเกจ api สำหรับคุณอย่างไร ฉันพบเธรดนี้เพราะฉันพบปัญหานี้โดยเฉพาะกับการนำเข้าแพคเกจ pytest และพี่น้อง
JuniorIncanter

1
ฉันมีสองคำถามโปรด 1. รูปแบบของคุณดูเหมือนว่าจะทำงานโดยไม่มี__package__ = "examples"ฉัน ทำไมคุณใช้มัน 2. ในสถานการณ์อะไร__name__ == "__main__"แต่__package__ไม่ใช่None?
actual_panda

@actual_panda การตั้งค่า__packages__ช่วยถ้าคุณต้องการเส้นทางที่แน่นอนเช่นexamples.apiการทำงานใน iirc (แต่มันเป็นเวลานานแล้วที่ฉันทำอย่างนั้นมาก่อน) และการตรวจสอบแพ็กเกจนั้นไม่ใช่ไม่ใช่ส่วนใหญ่จะไม่ปลอดภัยสำหรับสถานการณ์แปลก ๆ และการป้องกันในอนาคต
Evpok

เอ๊ะถ้าเพียงภาษาอื่นจะทำให้กระบวนการเดียวกันเป็นเรื่องง่ายเหมือนใน Python ฉันเห็นว่าทำไมทุกคนรักภาษานี้ Btw เอกสารก็ยอดเยี่ยมเช่นกัน ฉันชอบที่จะแยกประเภทคืนจากข้อความที่ไม่มีโครงสร้างมันเป็นการเปลี่ยนแปลงที่ดีจาก Javadoc และ phpdoc ffs ....
แมตต์

168

เบื่อ sys.path hacks?

มีมากมาย sys.path.appendแฮ็กพร้อมใช้งาน แต่ฉันพบวิธีอื่นในการแก้ปัญหาในมือ

สรุป

  • ล้อมโค้ดไว้ในหนึ่งโฟลเดอร์ (เช่นpackaged_stuff)
  • ใช้สร้างsetup.pyสคริปต์ที่คุณใช้setuptools.setup ()
  • Pip ติดตั้งแพคเกจในสถานะที่แก้ไขได้ด้วย pip install -e <myproject_folder>
  • นำเข้าโดยใช้ from packaged_stuff.modulename import function_name

ติดตั้ง

myprojectจุดเริ่มต้นคือโครงสร้างของไฟล์ที่คุณได้ให้ห่อในโฟลเดอร์ที่เรียกว่า

.
└── myproject
    ├── api
       ├── api_key.py
       ├── api.py
       └── __init__.py
    ├── examples
       ├── example_one.py
       ├── example_two.py
       └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

ผมจะเรียกโฟลเดอร์รากและในกรณีตัวอย่างของฉันมันตั้งอยู่ที่.C:\tmp\test_imports\

api.py

ในกรณีทดสอบลองใช้. /api/api.py ต่อไปนี้

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

ลองเรียกใช้ test_one:

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\myproject\tests\test_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

ยังพยายามนำเข้าญาติจะไม่ทำงาน:

การใช้from ..api.api import function_from_apiย่อมส่งผลให้

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\tests\test_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

ขั้นตอน

  1. ทำไฟล์ setup.py ไปยังไดเรกทอรีระดับราก

เนื้อหาสำหรับsetup.pyจะเป็น *

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. ใช้สภาพแวดล้อมเสมือนจริง

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

  • สร้าง env เสมือน
    • python -m venv venv
  • เปิดใช้งาน env เสมือน
    • source ./venv/bin/activate(Linux, macOS) หรือ./venv/Scripts/activate(Win)

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับสิ่งนี้เพียงแค่ Google ออก "python virtual env tutorial" หรือที่คล้ายกัน คุณอาจไม่ต้องการคำสั่งอื่นใดนอกจากสร้างเปิดใช้งานและปิดใช้งาน

เมื่อคุณสร้างและเปิดใช้งานสภาพแวดล้อมเสมือนแล้วคอนโซลของคุณควรให้ชื่อของสภาพแวดล้อมเสมือนในวงเล็บ

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

และแผนผังโฟลเดอร์ของคุณควรมีลักษณะเช่นนี้ **

.
├── myproject
   ├── api
      ├── api_key.py
      ├── api.py
      └── __init__.py
   ├── examples
      ├── example_one.py
      ├── example_two.py
      └── __init__.py
   ├── LICENCE.md
   ├── README.md
   └── tests
       ├── __init__.py
       └── test_one.py
├── setup.py
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]
  1. 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
  1. เพิ่มmyproject.เข้าไปในการนำเข้าของคุณ

โปรดทราบว่าคุณจะต้องเพิ่มmyproject.ลงในการนำเข้าเท่านั้นที่จะไม่ทำงานอย่างอื่น การนำเข้าที่ทำงานโดยไม่มีsetup.py& pip installจะทำงานได้ดี ดูตัวอย่างด้านล่าง


ทดสอบการแก้ปัญหา

ทีนี้มาทดสอบวิธีแก้ปัญหาโดยใช้คำapi.pyนิยามข้างบนและtest_one.pyนิยามด้านล่าง

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

ทำการทดสอบ

(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!

* ดูที่setuptools docsสำหรับตัวอย่าง verbose setup.py เพิ่มเติม

** ในความเป็นจริงคุณสามารถวางสภาพแวดล้อมเสมือนจริงของคุณได้ทุกที่บนฮาร์ดดิสก์ของคุณ


13
ขอบคุณสำหรับการโพสต์รายละเอียด นี่คือปัญหาของฉัน หากฉันทำทุกสิ่งที่คุณพูดและทำปิปแข็งฉันจะได้รับบรรทัด-e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myprojectความคิดใด ๆ เกี่ยวกับวิธีการแก้ไขหรือไม่
Si Mon

2
เหตุใดโซลูชันการนำเข้าที่สัมพันธ์กันจึงไม่ทำงาน ฉันเชื่อคุณ แต่ฉันพยายามเข้าใจระบบที่ซับซ้อนของ Python
Jared Nielsen

8
มีใครมีปัญหาเกี่ยวกับ a ModuleNotFoundErrorหรือไม่? ผมได้ติดตั้ง 'MyProject' เป็น virtualenv ทำตามขั้นตอนเหล่านี้และเมื่อฉันเข้าสู่เซสชั่นตีความและเรียกimport myprojectฉันได้รับModuleNotFoundError: No module named 'myproject'? pip list installed | grep myprojectแสดงให้เห็นว่ามันอยู่ที่นั่นไดเรกทอรีถูกต้องและทั้งรูปแบบของpipและpythonมีการตรวจสอบว่าถูกต้อง
ThatKind

2
สวัสดี @ np8 ใช้งานได้ฉันติดตั้งโดยไม่ตั้งใจใน venv และใน os :) pip listแสดงแพ็คเกจในขณะที่pip freezeแสดงชื่อแปลก ๆ หากติดตั้งด้วยธง-e
Grzegorz Krug

3
ใช้เวลาประมาณ 2 ชั่วโมงพยายามหาวิธีทำให้การนำเข้าแบบสัมพันธ์ทำงานได้และคำตอบนี้เป็นคำตอบที่ในที่สุดก็ทำสิ่งที่สมเหตุสมผล 👍👍
Graham Lea

43

นี่เป็นอีกทางเลือกหนึ่งที่ฉันใส่ที่ด้านบนของไฟล์ Python ในtestsโฟลเดอร์:

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

1
+1 ง่ายมากและทำงานได้อย่างสมบูรณ์ คุณต้องเพิ่มคลาสพาเรนต์ลงในการนำเข้า (เช่น api.api, example.example_two) แต่ฉันชอบวิธีนั้น
Evan Plaice

10
ฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึง newbies (เช่นตัวฉันเอง) ว่าที่..นี่สัมพันธ์กับไดเรกทอรีที่คุณเรียกใช้จาก --- ไม่ใช่ไดเรกทอรีที่มีไฟล์ทดสอบ / ตัวอย่าง ฉันกำลังดำเนินการจากไดเรกทอรีโครงการและฉันต้องการ./แทน หวังว่านี่จะช่วยคนอื่นได้
Joshua Detwiler

@JoshDetwiler ใช่แน่นอน ฉันไม่ทราบว่า ขอบคุณ
doak

1
นี่เป็นคำตอบที่ไม่ดี การแฮ็กเส้นทางไม่ใช่วิธีปฏิบัติที่ดี มันเป็นเรื่องอื้อฉาวที่ใช้กันมากในโลกหลาม หนึ่งในประเด็นหลักของคำถามนี้คือการดูว่าการนำเข้าสามารถทำได้อย่างไรในขณะที่หลีกเลี่ยงการแฮ็คประเภทนี้
jtcotton63

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwiler
vldbnc

31

คุณไม่ต้องการและไม่ควรแฮ็กsys.pathเว้นแต่ว่าจำเป็นและในกรณีนี้มันไม่เป็นเช่นนั้น ใช้:

import api.api_key # in tests, examples

เรียกใช้จากไดเรกทอรีโครงการ: python -m tests.test_oneเรียกใช้จากไดเรกทอรีโครงการ:

คุณอาจจะย้ายtests(ถ้าพวกเขาเป็น unittests ของ api) apiและเรียกใช้python -m api.testเพื่อรันการทดสอบทั้งหมด (สมมติว่ามี__main__.py) หรือpython -m api.test.test_oneเพื่อรันtest_oneแทน

นอกจากนี้คุณยังสามารถลบ__init__.pyจากexamples(มันไม่ได้เป็นแพคเกจ Python) และเรียกใช้ตัวอย่างใน virtualenv ที่ที่apiมีการติดตั้งเช่นpip install -e .ใน virtualenv จะติดตั้ง inplace แพคเกจถ้าคุณมีที่เหมาะสมapisetup.py


@ Alex คำตอบไม่ได้คิดว่าการทดสอบการทดสอบ API ยกเว้นย่อหน้าที่ได้กล่าวอย่างชัดเจน"หากพวกเขาเป็น unittests API ของ"
jfs

แต่น่าเสียดายที่คุณติดอยู่กับการเรียกใช้จาก root dir และ PyCharm ยังคงไม่พบไฟล์สำหรับฟังก์ชั่นที่ดี
mhstnsc

@mhstnsc: ไม่ถูกต้อง คุณควรจะสามารถเรียกใช้python -m api.test.test_oneจากที่ใดก็ได้เมื่อเปิดใช้งาน virtualenv หากคุณไม่สามารถกำหนดค่า PyCharm เพื่อทำการทดสอบให้ลองถามคำถาม Stack Overflow ใหม่ (หากคุณไม่พบคำถามที่มีอยู่ในหัวข้อนี้)
jfs

@jfs ฉันพลาดเส้นทาง env เสมือน แต่ฉันไม่ต้องการใช้อะไรมากกว่าเส้น Shebang เพื่อเรียกใช้สิ่งนี้จากไดเรกทอรีใด ๆ เลย มันไม่เกี่ยวกับการทำงานกับ PyCharm ผู้พัฒนาซอฟต์แวร์กับ PyCharm จะรู้ว่าพวกเขามีความสมบูรณ์และกระโดดผ่านฟังก์ชั่นที่ฉันไม่สามารถใช้งานได้กับโซลูชันใด ๆ
mhstnsc

@mhstnsc shebang ที่เหมาะสมนั้นเพียงพอในหลาย ๆ กรณี (ชี้ไปที่ virtualenv python binary Python IDE ที่ดีควรสนับสนุน virtualenv
jfs

9

ฉันยังไม่มีความเข้าใจเกี่ยวกับ Pythonology ที่จำเป็นเพื่อดูวิธีการแบ่งปันโค้ดระหว่างโครงการที่ไม่เกี่ยวข้องโดยไม่ต้องมีการแฮ็กข้อมูลนำเข้าพี่น้อง / ญาติ นี่คือทางออกของฉันจนถึงวันนั้น สำหรับexamplesหรือtestsนำเข้าสิ่งต่าง ๆ จาก..\apiนั้นจะมีลักษณะดังนี้:

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

นี่จะให้ไดเรกทอรีหลักของ api กับคุณและคุณไม่จำเป็นต้องใช้ "/ .. " concatenation sys.path.append (os.path.dirname (os.path.dirname (os.path.abspath ( ไฟล์ ))) )
Camilo Sanchez

4

สำหรับการอิมพอร์ตแพ็กเกจ siblings คุณสามารถใช้การแทรกหรือวิธีผนวกของโมดูล[sys.path] [2] :

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

สิ่งนี้จะใช้งานได้หากคุณเปิดใช้งานสคริปต์ดังนี้:

python examples/example_one.py
python tests/test_one.py

ในทางกลับกันคุณสามารถใช้การนำเข้าแบบสัมพัทธ์:

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

ในกรณีนี้คุณจะต้องเปิดสคริปต์ด้วยอาร์กิวเมนต์ '-m' (โปรดทราบว่าในกรณีนี้คุณจะต้องไม่ให้ส่วนขยาย'.py' ):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

แน่นอนคุณสามารถผสมสองวิธีเข้าด้วยกันเพื่อให้สคริปต์ของคุณทำงานไม่ว่าจะถูกเรียกว่าอย่างไร:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

ฉันใช้กรอบการคลิกซึ่งไม่ได้มี__file__ทั่วโลกดังนั้นฉันจึงต้องใช้สิ่งต่อไปนี้: sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))แต่มันทำงานได้ในไดเรกทอรีใด ๆ ตอนนี้
GammaGames

3

TLDR

วิธีนี้ไม่จำเป็นต้องมี setuptools แฮ็กพา ธ อาร์กิวเมนต์บรรทัดคำสั่งเพิ่มเติมหรือการระบุระดับบนสุดของแพ็กเกจในทุก ๆ ไฟล์ของโครงการของคุณ

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

คำอธิบาย

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

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

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

พิสูจน์แนวคิด ใน dir ด้วยโครงสร้างต่อไปนี้:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py มีรหัสต่อไปนี้:

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.py ประกอบด้วย:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

และ sib2 / callsib.py ประกอบด้วย:

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

ถ้าคุณทำซ้ำเช่นนี้คุณจะสังเกตเห็นเรียกว่าMain.pyจะส่งผลให้ "ได้เรียกว่า" ถูกพิมพ์ตามที่กำหนดไว้ในsib2/callsib.py แม้ว่าได้เรียกว่าผ่านsib2/callsib.py sib1/call.pyอย่างไรก็ตามหากมีการโทรโดยตรงsib1/call.py(หลังจากทำการเปลี่ยนแปลงการนำเข้าที่เหมาะสม) มันจะส่งข้อยกเว้น แม้ว่าจะทำงานเมื่อสคริปต์ถูกเรียกในไดเรกทอรีหลัก แต่จะไม่ทำงานหากเชื่อว่าตัวเองอยู่ในระดับบนสุดของแพ็คเกจ


2

ฉันได้ทำโครงการตัวอย่างเพื่อสาธิตวิธีจัดการเรื่องนี้ซึ่งก็คือแฮ็ค sys.path อื่นตามที่ระบุข้างต้น Python พี่น้องนำเข้าตัวอย่างซึ่งอาศัย:

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

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


ใช้งานได้เฉพาะเมื่อคุณเรียกใช้จากไดเรกทอรีหลักของสคริปต์
Evpok

1

คุณต้องดูว่าคำสั่งนำเข้าเขียนในรหัสที่เกี่ยวข้องได้อย่างไร หากexamples/example_one.pyใช้ข้อความสั่งการนำเข้าต่อไปนี้:

import api.api

... จากนั้นจึงคาดว่าไดเรกทอรีรากของโครงการจะอยู่ในเส้นทางของระบบ

วิธีที่ง่ายที่สุดในการสนับสนุนสิ่งนี้โดยไม่แฮ็ค (ตามที่คุณใส่ไว้) คือการเรียกใช้ตัวอย่างจากไดเรกทอรีระดับบนสุดเช่นนี้:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

กับงูหลาม 2.7.1 $ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.apiฉันจะได้รับต่อไปนี้: import api.apiฉันยังได้รับเหมือนกันกับ
zachwill

Updated คำตอบของฉัน ... คุณไม่จำเป็นต้องเพิ่มไดเรกทอรีปัจจุบันไปยังเส้นทางนำเข้าไม่มีทางรอบที่
AJ

1

เพียงในกรณีที่มีคนใช้ Pydev คราสจบลงที่นี่: คุณสามารถเพิ่มเส้นทางพ่อแม่พี่น้องของ (และผู้ปกครองโมดูลโทรฯ ) เป็นห้องสมุดภายนอกโฟลเดอร์โดยใช้โครงงาน> คุณสมบัติและการตั้งค่าห้องสมุดภายนอกภายใต้เมนูด้านซ้ายPydev-PYTHONPATH from sibling import some_classจากนั้นคุณสามารถนำเข้าจากพี่น้องของคุณเช่น


-3

ก่อนอื่นคุณควรหลีกเลี่ยงไฟล์ที่มีชื่อเดียวกันกับโมดูล มันอาจทำลายการนำเข้าอื่น ๆ

เมื่อคุณนำเข้าไฟล์ก่อนอื่นล่ามจะตรวจสอบไดเรกทอรีปัจจุบันแล้วค้นหาไดเรกทอรีทั่วโลก

ภายในexamplesหรือtestsคุณสามารถโทร:

from ..api import api

ฉันได้รับสิ่งต่อไปนี้ด้วย Python 2.7.1:Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
zachwill

2
โอ้คุณควรเพิ่ม__init__.pyไฟล์ไปยังไดเรกทอรีระดับบนสุด มิฉะนั้น Python จะไม่สามารถใช้เป็นโมดูลได้

8
มันจะไม่ทำงาน ปัญหาไม่ได้อยู่ที่โฟลเดอร์หลักไม่ได้เป็นแพคเกจก็คือตั้งแต่โมดูลเป็น__name__เป็น__main__แทนpackage.module, Python ไม่สามารถดูแพคเกจผู้ปกครองของตนเพื่อให้.จุดเพื่ออะไร
Evpok
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.