วิธีแก้ไข“ พยายามนำเข้าที่เกี่ยวข้องในแพ็คเกจที่ไม่ใช่” แม้จะมี __init__.py


744

ฉันพยายามติดตามPEP 328ด้วยโครงสร้างไดเรกทอรีต่อไปนี้:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

ในcore_test.pyฉันมีคำสั่งการนำเข้าต่อไปนี้

from ..components.core import GameLoopEvents

อย่างไรก็ตามเมื่อฉันเรียกใช้ฉันได้รับข้อผิดพลาดต่อไปนี้:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

การค้นหารอบ ๆ ฉันพบ " เส้นทางสัมพัทธ์ไม่ทำงานแม้จะมี __init__.py " และ " นำเข้าโมดูลจากเส้นทางสัมพัทธ์ " แต่ก็ไม่ได้ช่วยอะไร

มีอะไรฉันหายไปไหม


17
ฉันยังสับสนมากด้วยวิธีการสร้างโครงสร้างunittestโครงการที่หลากหลายดังนั้นฉันจึงเขียนโครงการตัวอย่างที่ค่อนข้างละเอียดซึ่งครอบคลุมการทำรังอย่างลึกซึ้งของโมดูลการนำเข้าแบบสัมพัทธ์และการนำเข้าสัมบูรณ์ แพคเกจรวมถึงการนำเข้าระดับเดียว, คู่และระดับแพคเกจ ช่วยเคลียร์สิ่งที่ถูกต้องสำหรับฉัน!
cod3monk3y

1
ฉันไม่สามารถรับการทดสอบของคุณได้ รับno module named myimports.fooเมื่อฉันเรียกใช้พวกเขา
Blairg23

@ Blairg23 ฉันคาดเดาภาวนาตั้งใจคือการcdเข้าPyImportsและการทำงานpython -m unittest tests.test_absเช่น
duozmo

7
ฉันเห็นด้วยกับยีน ฉันหวังว่าจะมีกลไกสำหรับการดีบักกระบวนการนำเข้าที่มีประโยชน์มากกว่านี้เล็กน้อย ในกรณีของฉันฉันมีสองไฟล์ในไดเรกทอรีเดียวกัน ฉันกำลังพยายามนำเข้าไฟล์หนึ่งไปยังไฟล์อื่น ถ้าฉันมีไฟล์init .py ในไดเรกทอรีนั้นฉันจะได้รับ ValueError: พยายามนำเข้าแบบสัมพัทธ์ในข้อผิดพลาดที่ไม่ใช่แพ็คเกจ หากฉันลบไฟล์init .py ฉันจะได้รับข้อผิดพลาดไม่มีโมดูลชื่อข้อผิดพลาด 'NAME'
user1928764

ในกรณีของฉันฉันมีสองไฟล์ในไดเรกทอรีเดียวกัน ฉันกำลังพยายามนำเข้าไฟล์หนึ่งไปยังไฟล์อื่น ถ้าฉันมีไฟล์init .py ในไดเรกทอรีนั้นฉันจะได้รับ ValueError: พยายามนำเข้าแบบสัมพัทธ์ในข้อผิดพลาดที่ไม่ใช่แพ็คเกจ หากฉันลบไฟล์init .py ฉันจะได้รับข้อผิดพลาดไม่มีโมดูลชื่อข้อผิดพลาด 'NAME' สิ่งที่น่าผิดหวังจริงๆคือฉันใช้การทำงานนี้แล้วฉันก็ยิงตัวเองด้วยการลบไฟล์. bashrc ซึ่งตั้งค่า PYTHONPATH เป็นบางอย่างและตอนนี้มันไม่ทำงาน
user1928764

คำตอบ:


443

ใช่. คุณไม่ได้ใช้มันเป็นแพ็คเกจ

python -m pkg.tests.core_test

51
gotcha: โปรดทราบว่าไม่มีที่สิ้นสุด '.py'!
mindthief

497
ฉันไม่ได้เป็นผู้ลงมือคนใดคนหนึ่ง แต่ฉันรู้สึกว่านี่สามารถใช้รายละเอียดได้มากกว่านี้เล็กน้อยเนื่องจากความนิยมของคำถามและคำตอบนี้ การสังเกตสิ่งต่าง ๆ เช่นจากไดเรกทอรีใดที่จะดำเนินการคำสั่งเชลล์ด้านบนความจริงที่ว่าคุณต้องการ__init__.pyทุกอย่างที่จำเป็นและ__package__-modifying trickery (อธิบายด้านล่างโดย BrenBarn) ที่จำเป็นเพื่ออนุญาตการนำเข้าเหล่านี้สำหรับสคริปต์ปฏิบัติการ (เช่นเมื่อใช้ shebang และ การทำ./my_script.pyที่ Unix shell) จะเป็นประโยชน์ ปัญหาทั้งหมดนี้ค่อนข้างยุ่งยากสำหรับฉันที่จะคิดออกหรือค้นหาเอกสารที่กระชับและเข้าใจได้
Mark Amery

16
หมายเหตุ: คุณต้องอยู่นอกไดเรกทอรีpkgณ จุดที่คุณเรียกสายนี้จาก CLI จากนั้นควรทำงานตามที่คาดไว้ หากคุณอยู่ข้างในpkgและโทรหาคุณpython -m tests.core_testจะไม่ทำงาน อย่างน้อยมันก็ไม่ได้สำหรับฉัน
Blairg23

94
อย่างจริงจังคุณสามารถอธิบายสิ่งที่เกิดขึ้นในคำตอบของคุณ?
Pinocchio

18
@ MarkAmery เกือบจะสูญเสียความคิดของฉันพยายามที่จะค้นหาวิธีการทำงานทั้งหมดญาตินำเข้าภายในโครงการที่มีไดเรกทอรีย่อยที่มีไฟล์ py ที่มี__init__.pyไฟล์ แต่คุณยังคงได้รับValueError: Attempted relative import in non-packageข้อผิดพลาด ฉันจะจ่ายเงินที่ดีจริง ๆ สำหรับใครบางคนเพื่ออธิบายในภาษาอังกฤษในที่สุดว่าทั้งหมดนี้ทำงานอย่างไร
AdjunctProfessorFalcon

635

หากต้องการรายละเอียดเกี่ยวกับคำตอบของ Ignacio Vazquez-Abrams :

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

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

ดูhttp://www.python.org/dev/peps/pep-0366/เพื่อดูรายละเอียด


55
ใช้เวลาสักครู่เพื่อให้ฉันรู้ว่าคุณไม่สามารถเรียกใช้python -m core_testจากภายในtestsไดเรกทอรีย่อยได้ - ต้องมาจากแม่หรือคุณต้องเพิ่มพาเรนต์ลงในพา ธ
Aram Kocharyan

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

2
ฉันหมายถึงถ้า__package__สัญลักษณ์ถูกตั้งค่าเป็น "parent.child" ดังนั้นคุณจะสามารถนำเข้า "parent.other_child" บางทีฉันอาจจะไม่ได้พูดออกมาได้ดีนัก
Danny Staple

5
@DannyStaple: มันอธิบายได้อย่างไรในเอกสารที่เชื่อมโยง หากคุณมีสคริปต์script.pyในแพคเกจpack.subpackแล้วการตั้งค่าก็__package__เพื่อpack.subpackจะช่วยให้คุณทำจะนำเข้าจากบางสิ่งบางอย่างfrom ..module import something pack.moduleโปรดทราบว่าตามเอกสารระบุว่าคุณยังต้องมีแพ็คเกจระดับบนเส้นทางของระบบ นี่เป็นวิธีทำงานสำหรับโมดูลที่นำเข้าแล้ว สิ่งเดียวที่__package__ทำได้คือให้คุณใช้ลักษณะการทำงานนั้นกับสคริปต์ที่เรียกใช้โดยตรงเช่นกัน
BrenBarn

3
ฉันใช้__package__ในสคริปต์ที่ดำเนินการโดยตรง แต่น่าเสียดายที่ฉันได้รับข้อผิดพลาดต่อไปนี้: "โมดูลหลัก 'xxx' ไม่ได้โหลดไม่สามารถนำเข้าแบบสัมพัทธ์"
mononoke

202

คุณสามารถใช้import components.coreโดยตรงหากคุณผนวกไดเรกทอรีปัจจุบันไปที่sys.path:

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

35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))นี้จะทำงาน
ajay

26
from os import sysดูเหมือนว่าการโกง :)
แกะบิน

3
@Piotr: มันอาจจะถือว่าดีกว่าเพราะมันจะแสดงให้เห็นชัดเจนยิ่งขึ้นว่ากำลังถูกผนวกเข้ากับอะไรsys.path- ผู้ปกครองของไดเรกทอรีที่ไฟล์ปัจจุบันอยู่ในนั้น
martineau

8
@flyingsheep: import sys, os.path as pathตกลงฉันเพียงแค่ต้องการใช้เป็นประจำ
martineau

10
FYI เพื่อใช้ในโน๊ตบุ๊ค ipython import os; os.sys.path.append(os.path.dirname(os.path.abspath('.')))ผมดัดแปลงคำตอบนี้ไปที่: จากนั้นตรงimport components.coreทำงานสำหรับฉันนำเข้าจากไดเรกทอรีหลักของสมุดบันทึกตามต้องการ
Racing Tadpole

195

ขึ้นอยู่กับว่าคุณต้องการเปิดตัวสคริปท์อย่างไร

หากคุณต้องการเปิดใช้ UnitTest ของคุณจากบรรทัดคำสั่งด้วยวิธีคลาสสิกนั่นคือ:

python tests/core_test.py

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

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

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

python -m pkg.tests.core_test

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

from ..components.core import GameLoopEvents

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

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

3
ฉันควรทำอย่างไรถ้าฉันพยายามใช้ pdb สำหรับการดีบัก เนื่องจากคุณใช้python -m pdb myscript.pyเพื่อเปิดเซสชันการดีบัก
แดนนี่

1
@dannynjust - เป็นคำถามที่ดีเพราะคุณไม่มีโมดูล 2 โมดูล โดยทั่วไปเมื่อทำการดีบั๊กฉันชอบที่จะทำการดีบักด้วยตนเองในจุดแรกที่ฉันต้องการเริ่มการดีบั๊ก คุณสามารถทำได้โดยการใส่import pdb; pdb.set_trace()รหัส (inline)
mgilson

3
ดีกว่าที่จะใช้insertแทนappendหรือไม่? นั่นคือsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine

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

1
คุณควรจะพูดถึงว่าคุณไม่สามารถอยู่ในไดเรกทอรีที่มี core_test เมื่อคุณเรียกใช้เป็นโมดูล (ที่จะง่ายเกินไป)
โจเซฟ Garvin


10

หากกรณีการใช้งานของคุณใช้สำหรับการทดสอบและเห็นว่าเป็นเช่นนั้นคุณสามารถทำสิ่งต่อไปนี้ได้ แทนการใช้สคริปต์การทดสอบของคุณเป็นใช้กรอบการทดสอบเช่นpython core_test.py pytestจากนั้นในบรรทัดคำสั่งคุณสามารถป้อน

$$ py.test

ที่จะทำการทดสอบในไดเรกทอรีของคุณ สิ่งนี้ได้รับปัญหาเกี่ยวกับ__name__การเป็น__main__ที่ถูกระบุโดย @BrenBarn จากนั้นวาง__init__.pyไฟล์ว่างในไดเรกทอรีทดสอบของคุณซึ่งจะทำให้ไดเรกทอรีทดสอบเป็นส่วนหนึ่งของแพ็คเกจของคุณ จากนั้นคุณจะสามารถทำ

from ..components.core import GameLoopEvents

อย่างไรก็ตามหากคุณรันสคริปต์ทดสอบเป็นโปรแกรมหลักสิ่งต่างๆจะล้มเหลวอีกครั้ง ดังนั้นเพียงแค่ใช้ผู้ทดสอบ บางทีนี่อาจใช้งานร่วมกับนักทดสอบอื่น ๆ เช่นnosetestsแต่ฉันไม่ได้ตรวจสอบ หวังว่านี่จะช่วยได้


9

การแก้ไขด่วนของฉันคือการเพิ่มไดเรกทอรีไปยังเส้นทาง:

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

6
วิธีการของคุณจะไม่สามารถใช้งานได้ในทุกกรณีเพราะส่วนของ '../' ได้รับการแก้ไขจากไดเรกทอรีที่คุณเรียกใช้สคริปต์ของคุณ (core_test.py) ด้วยวิธีการของคุณคุณจะถูกบังคับให้ cd เป็น 'การทดสอบ' ก่อนที่จะรัน core_test.py scritp
xyman

7

ปัญหาเกิดขึ้นกับวิธีทดสอบของคุณ

คุณพยายาม python core_test.py

จากนั้นคุณจะได้รับข้อผิดพลาดนี้ ValueError: พยายามนำเข้าที่เกี่ยวข้องในแพคเกจที่ไม่ใช่

เหตุผล: คุณกำลังทดสอบบรรจุภัณฑ์ของคุณจากแหล่งที่มาที่ไม่ใช่แพ็คเกจ

ดังนั้นทดสอบโมดูลของคุณจากแหล่งแพ็กเกจ

ถ้านี่คือโครงสร้างโครงการของคุณ

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

pkg cd

python -m tests.core_test # dont use .py

หรือจากนอก pkg /

python -m pkg.tests.core_test

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

hi/
  hello.py
how.py

ใน how.py

from .hi import hello

กรณีถ้าคุณต้องการนำเข้าจาก hello.py

from .. import how

1
ดีกว่าคำตอบที่ยอมรับ
GabrielBB

ในตัวอย่างfrom .. import howคุณจะนำเข้าคลาส / วิธีเฉพาะจากไฟล์ 'how' ได้อย่างไร เมื่อฉันทำสิ่งที่เทียบเท่าfrom ..how import fooฉันจะได้รับ "พยายามนำเข้าที่เกี่ยวข้องเกินกว่าแพ็คเกจระดับบนสุด"
James Hulse

3

ด้ายเก่า ฉันพบว่าการเพิ่ม__all__= ['submodule', ...]ลงใน ไฟล์__init__.pyจากนั้นใช้from <CURRENT_MODULE> import *ในเป้าหมายทำงานได้ดี


3

คุณสามารถใช้from pkg.components.core import GameLoopEventsตัวอย่างเช่นฉันใช้ pycharm ด้านล่างเป็นรูปภาพโครงสร้างโครงการของฉันฉันเพิ่งนำเข้าจากแพ็คเกจรูทแล้วก็ใช้งานได้:

ป้อนคำอธิบายรูปภาพที่นี่


3
สิ่งนี้ไม่ได้ผลสำหรับฉัน คุณต้องกำหนดเส้นทางในการกำหนดค่าของคุณหรือไม่?
Mohammad Mahjoub

3

ดังที่เปาโลกล่าวเรามี 2 วิธีการภาวนา:

1) python -m tests.core_test
2) python tests/core_test.py

ข้อแตกต่างระหว่างหนึ่งคือสตริง sys.path [0] เนื่องจากการตีความจะค้นหา sys.path เมื่อทำการอิมพอร์ตเราจึงสามารถทำสิ่งต่อไปนี้tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

และหลังจากนี้เราสามารถเรียกใช้ core_test.py ด้วยวิธีการอื่น ๆ :

cd tests
python core_test.py
python -m core_test
...

หมายเหตุ py36 ทดสอบเท่านั้น


3

วิธีนี้ใช้ได้ผลสำหรับฉันและมีความยุ่งเหยิงน้อยกว่าโซลูชันบางตัว:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

ไดเรกทอรีหลักอยู่ใน PYTHONPATH ของฉันและมี__init__.pyไฟล์ในไดเรกทอรีหลักและไดเรกทอรีนี้

ด้านบนใช้งานได้ใน python 2 เสมอ แต่บางครั้ง python 3 กดปุ่ม ImportError หรือ ModuleNotFoundError (อันหลังเป็นของใหม่ใน python 3.6 และ subclass ของ ImportError) ดังนั้น tweak ต่อไปนี้ใช้ได้กับทั้ง python 2 และ 3

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents


1

หากใครบางคนกำลังมองหาวิธีแก้ปัญหาฉันก็สะดุดกับใครคนหนึ่ง นี่คือบริบทเล็กน้อย ฉันต้องการทดสอบวิธีการหนึ่งในแฟ้ม เมื่อฉันเรียกใช้จากภายใน

if __name__ == "__main__":

มันมักจะบ่นเรื่องการนำเข้าที่เกี่ยวข้อง ฉันพยายามใช้วิธีแก้ไขปัญหาด้านบน แต่ไม่สามารถใช้งานได้เนื่องจากมีไฟล์ซ้อนหลายไฟล์แต่ละไฟล์มีการนำเข้าหลายรายการ

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


0

นี่เป็นวิธีหนึ่งที่จะทำให้ทุกคนฉี่ แต่ทำงานได้ดี ในการทดสอบวิ่ง:

ln -s ../components components

จากนั้นเพียงนำเข้าส่วนประกอบต่างๆตามปกติ


0

นี่เป็นสิ่งที่ทำให้เกิดความสับสนและถ้าคุณใช้ IDE เช่น pycharm มันจะทำให้สับสนเล็กน้อย ทำงานได้ดีสำหรับฉัน: 1. ทำการตั้งค่าโครงการ pycharm (ถ้าคุณกำลังเรียกใช้ python จาก VE หรือจากไดเรกทอรี python) 2. ไม่มีวิธีผิดที่คุณกำหนดไว้ บางครั้งมันทำงานกับคลาสการอิมพอร์ตจาก folder1.file1

หากไม่ได้ผลให้ใช้ import folder1.file1 3. ตัวแปรสภาพแวดล้อมของคุณควรถูกกล่าวถึงอย่างถูกต้องในระบบหรือระบุไว้ในอาร์กิวเมนต์บรรทัดคำสั่งของคุณ


-2

เนื่องจากรหัสของคุณมีif __name__ == "__main__"ซึ่งไม่ได้นำเข้าเป็นแพคเกจคุณควรใช้sys.path.append()เพื่อแก้ปัญหา


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