เกินข้อผิดพลาดของแพคเกจระดับสูงสุดในการนำเข้าที่เกี่ยวข้อง


316

ดูเหมือนว่ามีคำถามอยู่บ้างแล้วที่นี่เกี่ยวกับการนำเข้าสัมพัทธ์ในหลาม 3 แต่หลังจากผ่านหลาย ๆ คำถามแล้วฉันยังไม่พบคำตอบสำหรับปัญหาของฉัน ดังนั้นนี่คือคำถาม

ฉันมีแพ็คเกจที่แสดงด้านล่าง

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

และฉันมีบรรทัดเดียวใน test.py:

from ..A import foo

ตอนนี้ฉันอยู่ในโฟลเดอร์ของpackageและฉันเรียกใช้

python -m test_A.test

ฉันได้รับข้อความ

"ValueError: attempted relative import beyond top-level package"

แต่ถ้าฉันอยู่ในโฟลเดอร์พาเรนต์ของpackageเช่นฉันเรียกใช้:

cd ..
python -m package.test_A.test

ทุกอย่างปกติดี.

ตอนนี้คำถามของฉันคือ เมื่อผมอยู่ในโฟลเดอร์ของpackageและฉันเรียกใช้โมดูลภายใน test_A ย่อยแพคเกจเป็นtest_A.testอยู่บนพื้นฐานของความเข้าใจของฉัน..Aขึ้นไปเพียงระดับหนึ่งซึ่งยังคงอยู่ในโฟลเดอร์ทำไมจะให้ข้อความบอกว่าpackage beyond top-level packageอะไรคือสาเหตุที่ทำให้เกิดข้อความแสดงข้อผิดพลาดนี้?


49
โพสต์นั้นไม่ได้อธิบายข้อผิดพลาด "เกินแพ็คเกจระดับสูงสุด" ของฉัน
พักพิง

4
ฉันมีความคิดที่นี่ดังนั้นเมื่อรัน test_A.test ในฐานะโมดูล '.. ' เหนือกว่า test_A ซึ่งเป็นระดับสูงสุดของการนำเข้า test_A.test แล้วฉันคิดว่าระดับแพ็คเกจไม่ได้อยู่ในระดับไดเรกทอรี แต่มีจำนวนเท่าไร ระดับที่คุณนำเข้าแพ็คเกจ
พักพิง

2
ผมสัญญาว่าคุณจะเข้าใจทุกอย่างที่เกี่ยวกับญาตินำเข้าหลังจากดูคำตอบนี้stackoverflow.com/a/14132912/8682868
pzjzeason

ดูที่ValueError: พยายามนำเข้าที่เกี่ยวข้องเกินกว่าแพ็คเกจระดับบนสุดสำหรับคำอธิบายโดยละเอียดเกี่ยวกับปัญหานี้
napuzba

มีวิธีหลีกเลี่ยงการนำเข้าที่สัมพันธ์กันหรือไม่ เช่นเดียวกับวิธีที่ PyDev ใน Eclipse เห็นแพ็คเกจทั้งหมดภายใน <PydevProject> / src?
Mushu909

คำตอบ:


172

แก้ไข: มีคำตอบที่ดีกว่า / มากขึ้นสำหรับคำถามนี้ในคำถามอื่น ๆ :


ทำไมมันไม่ทำงาน เป็นเพราะไพ ธ อนไม่ได้บันทึกตำแหน่งที่โหลดแพคเกจ ดังนั้นเมื่อคุณทำเช่นpython -m test_A.testนั้นเพียงแค่ทิ้งความรู้ที่test_A.testเก็บอยู่ในนั้นpackage(เช่นpackageไม่ถือว่าเป็นแพ็กเกจ) ความพยายามfrom ..A import fooกำลังพยายามเข้าถึงข้อมูลที่ไม่มีอยู่อีกต่อไป (เช่นไดเร็กทอรีพี่น้องของตำแหน่งที่โหลด) มันเป็นแนวคิดที่คล้ายกับการอนุญาตให้อยู่ในไฟล์ในfrom ..os import path mathสิ่งนี้จะไม่ดีเพราะคุณต้องการให้แพ็กเกจแตกต่างกัน หากพวกเขาจำเป็นต้องใช้อะไรบางอย่างจากแพคเกจอื่นแล้วพวกเขาก็ควรจะหมายถึงพวกเขาทั่วโลกที่มีfrom os import pathและให้การทำงานหลามออกที่ที่อยู่กับและ$PATH$PYTHONPATH

เมื่อคุณใช้python -m package.test_A.testแล้วใช้การfrom ..A import fooแก้ปัญหาได้ดีเพราะมันติดตามสิ่งที่อยู่ในนั้นpackageและคุณเพียงแค่เข้าถึงไดเรกทอรีลูกของตำแหน่งที่โหลด

ทำไมหลามไม่คิดว่าไดเรกทอรีการทำงานปัจจุบันเป็นแพคเกจ? ไม่มีเงื่อนงำแต่เอ้ยมันจะมีประโยชน์


2
ฉันได้แก้ไขคำตอบของฉันเพื่ออ้างถึงคำตอบที่ดีกว่าสำหรับคำถามที่มีค่าเท่ากัน มีวิธีแก้ไขเฉพาะหน้า สิ่งเดียวที่ฉันได้เห็นงานจริงคือสิ่งที่ OP ได้ทำซึ่งใช้การ-mตั้งค่าสถานะและเรียกใช้จากไดเรกทอรีด้านบน
Multihunter

1
ควรสังเกตว่าคำตอบนี้จากลิงก์ที่ Multihunter ให้ไว้ไม่เกี่ยวข้องกับการsys.pathแฮ็ก แต่เป็นการใช้setuptoolsซึ่งน่าสนใจกว่าในความคิดของฉัน
Angelo Cardellicchio

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

ลองสิ่งนี้ ทำงานให้ฉัน


10
อืม ... มันทำงานได้อย่างไร ไฟล์ทดสอบทุกไฟล์จะมีสิ่งนี้หรือไม่
George Mauer

นี่คือปัญหาถ้าเช่นA/bar.pyมีอยู่และในที่คุณทำfoo.py from .bar import X
user1834164

9
ฉันต้องลบ .. จาก "จาก .. นำเข้า ... " หลังจากเพิ่ม sys.path.append (".. ")
Jake OPJ

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

นี่เป็นตัวเลือกที่ดีที่สุดซับซ้อนน้อยที่สุด
อเล็กซ์ R

43

ข้อสันนิษฐาน:
หากคุณอยู่ในpackageไดเรกทอรีAและtest_Aเป็นแพคเกจแยกต่างหาก

สรุป:
..Aการนำเข้าจะได้รับอนุญาตภายในแพ็คเกจเท่านั้น

บันทึกเพิ่มเติม:
ทำการนำเข้าญาติใช้ได้เฉพาะภายในแพคเกจจะเป็นประโยชน์ถ้าคุณต้องการที่จะบังคับว่าแพคเกจที่สามารถวางบนเส้นทางใด ๆ sys.pathที่ตั้งอยู่บน

แก้ไข:

ฉันเป็นคนเดียวที่คิดว่านี่เป็นบ้า! เหตุใดในโลกไดเรกทอรีการทำงานปัจจุบันจึงไม่ถือว่าเป็นแพคเกจ - Multihunter

ไดเรกทอรีการทำงานปัจจุบันมักจะอยู่ใน sys.path ดังนั้นไฟล์ทั้งหมดที่มีสิ่งที่นำเข้าได้ นี่เป็นพฤติกรรมตั้งแต่ Python 2 เมื่อแพ็คเกจยังไม่มีอยู่ การทำไดเร็กทอรีที่กำลังรันอยู่แพ็คเกจจะอนุญาตให้นำเข้าโมดูลเป็น "import .A" และเป็น "import A" ซึ่งจะเป็นสองโมดูลที่แตกต่างกัน บางทีนี่อาจเป็นสิ่งที่ไม่สอดคล้องกันที่ต้องพิจารณา


86
ฉันเป็นคนเดียวที่คิดว่านี่เป็นบ้า! เหตุใดในโลกไดเรกทอรีที่ใช้งานจึงไม่ถือว่าเป็นแพคเกจ
Multihunter

13
ไม่เพียง แต่สติปัญญาเท่านั้นมันไม่ช่วยเหลือ ... ดังนั้นคุณจะทำการทดสอบได้อย่างไร? เห็นได้ชัดว่าสิ่งที่ OP ถามและทำไมฉันแน่ใจว่าหลายคนอยู่ที่นี่เช่นกัน
George Mauer

ไดเร็กทอรีที่รันอยู่มักจะอยู่ใน sys.path ดังนั้นไฟล์ทั้งหมดที่มีสิ่งที่นำเข้าได้ สิ่งนี้เป็นพฤติกรรมตั้งแต่ Python 2 เมื่อแพ็คเกจยังไม่มีอยู่ - คำตอบที่แก้ไข
ผู้ใช้

ฉันไม่ทำตามความไม่ลงรอยกัน พฤติกรรมของpython -m package.test_A.testดูเหมือนจะทำในสิ่งที่ต้องการและข้อโต้แย้งของฉันคือสิ่งนั้นควรเป็นค่าเริ่มต้น ดังนั้นคุณสามารถยกตัวอย่างความไม่สอดคล้องนี้ให้ฉันได้หรือไม่?
Multihunter

ฉันกำลังคิดจริงๆมีการร้องขอคุณสมบัติสำหรับสิ่งนี้หรือไม่? นี่มันบ้าจริงๆ สไตล์ C / C ++ #includeจะมีประโยชน์มาก!
นิโคลัสฮัมฟรีย์

29

ไม่มีวิธีแก้ปัญหาเหล่านี้สำหรับฉันใน 3.6 ด้วยโครงสร้างโฟลเดอร์เช่น:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

เป้าหมายของฉันคือการนำเข้าจาก module1 เป็น module2 สิ่งที่ได้ผลสำหรับฉันในที่สุดก็แปลกพอ:

import sys
sys.path.append(".")

จดบันทึกจุดเดียวตรงข้ามกับโซลูชันสองจุดที่กล่าวถึง


แก้ไข: ข้อความต่อไปนี้ช่วยอธิบายให้ฉันฟัง:

import os
print (os.getcwd())

ในกรณีของฉันไดเรกทอรีทำงานคือ (โดยไม่คาดคิด) รูทของโครงการ


2
มันใช้งานได้ในพื้นที่ แต่ไม่ได้ทำงานกับอินสแตนซ์ของ aws ec2 มันสมเหตุสมผลหรือไม่?
thebeancounter

สิ่งนี้ใช้ได้สำหรับฉันเช่นกัน - ในกรณีของฉันไดเรกทอรีการทำงานก็เช่นเดียวกันกับรูทโครงการ ฉันใช้ทางลัดเรียกใช้จากเครื่องมือแก้ไขการเขียนโปรแกรม (TextMate)
JeremyDouglass

@thebeancounter เดียวกัน! ทำงานได้ในเครื่องบน mac ของฉัน แต่ไม่ทำงานบน ec2 จากนั้นฉันก็รู้ว่าฉันกำลังรันคำสั่งในส่วนย่อยบน ec2 และรันที่ root ในเครื่อง เมื่อฉันเรียกใช้จาก root บน ec2 มันทำงานได้
Logan Yang

สิ่งนี้ได้ผลกับฉันด้วยเช่นกัน จากวิธีการที่ sys ตอนนี้ฉันสามารถเพียงแค่เรียกแพคเกจโดยไม่ต้อง ".. "
#:

sys.path.append(".")ทำงานได้เพราะคุณกำลังเรียกมันในไดเรกทอรีหลักโปรดทราบว่า.จะเป็นตัวแทนของไดเรกทอรีที่คุณเรียกใช้คำสั่งหลามมา
KevinZhou

13

from package.A import foo

ฉันคิดว่ามันชัดเจนกว่า

import sys
sys.path.append("..")

4
ก็อ่านได้มากขึ้นอย่างแน่นอน sys.path.append("..")แต่ยังคงความต้องการ ทดสอบกับ python 3.6
MFA

เช่นเดียวกับคำตอบที่เก่ากว่า
nrofis

12

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

คุณสามารถแก้ไขได้โดยการเปลี่ยนการนำเข้าที่สัมพันธ์กันของคุณเป็นสัมบูรณ์แล้วเริ่มด้วย:

PYTHONPATH=/path/to/package python -m test_A.test

หรือบังคับให้พา ธ ของไพ ธ อนเมื่อเรียกใช้วิธีนี้เนื่องจาก:

ด้วยpython -m test_A.testคุณกำลังดำเนินการtest_A/test.pyกับ__name__ == '__main__'และ__file__ == '/absolute/path/to/test_A/test.py'

ซึ่งหมายความว่าtest.pyคุณสามารถใช้importการป้องกันแบบกึ่งสมบูรณ์ในสภาพเคสหลักและทำการจัดการพา ธ แบบ Python แบบครั้งเดียว:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())

8

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


หากบางคนยังคงดิ้นรนเล็กน้อยหลังจากได้รับคำตอบที่ยอดเยี่ยมแล้วฉันพบคำแนะนำในเว็บไซต์ที่ไม่มีให้บริการอีกต่อไป

พูดที่สำคัญจากเว็บไซต์ที่ฉันพูดถึง:

"สามารถระบุโปรแกรมเดียวกันด้วยวิธีนี้:

sys นำเข้า

sys.path.append ( '..')

แน่นอนว่าต้องเขียนโค้ดข้างต้นก่อน ข้อความสั่งการนำเข้าอื่น ๆ

เห็นได้ชัดว่ามันต้องเป็นแบบนี้โดยคิดตามความเป็นจริง ฉันพยายามใช้ sys.path.append ('.. ') ในการทดสอบของฉัน แต่พบปัญหาที่โพสต์โดย OP ด้วยการเพิ่มการนำเข้าและข้อบกพร่อง sys.path ก่อนการนำเข้าอื่น ๆ ของฉันฉันสามารถแก้ไขปัญหาได้


ลิงค์ที่คุณโพสต์นั้นตายแล้ว
Baxx

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

5

หากคุณมี__init__.pyในโฟลเดอร์บนคุณสามารถเริ่มต้นการนำเข้าเช่นเดียวกับ import file/path as aliasในไฟล์ init นั้น จากนั้นคุณสามารถใช้มันในสคริปต์ที่ต่ำกว่าเป็น:

import alias

0

ในความเห็นที่ต่ำต้อยของฉันฉันเข้าใจคำถามนี้ด้วยวิธีนี้:

[กรณีที่ 1] เมื่อคุณเริ่มต้นการนำเข้าแบบสัมบูรณ์

python -m test_A.test

หรือ

import test_A.test

หรือ

from test_A import test

คุณกำลังตั้งจริงนำเข้ายึดที่จะเป็นtest_Aในคำอื่น ๆ test_Aแพคเกจระดับบนสุดคือ ดังนั้นเมื่อเรามี test.py from ..A import xxxแล้วคุณกำลังหลบหนีจากจุดยึดและ Python ไม่อนุญาตสิ่งนี้

[กรณีที่ 2] เมื่อคุณทำ

python -m package.test_A.test

หรือ

from package.test_A import test

สมอของคุณจะกลายเป็นpackageดังนั้นpackage/test_A/test.pyการfrom ..A import xxxหลีกเลี่ยงสมอ (ยังอยู่ในpackageโฟลเดอร์) และ Python ยอมรับสิ่งนี้อย่างมีความสุข

ในระยะสั้น:

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

นอกจากนี้เราสามารถใช้ชื่อโมดูลที่ผ่านการรับรอง (FQMN) เพื่อตรวจสอบปัญหานี้

ตรวจสอบ FQMN ในแต่ละกรณี:

  • [CASE2] test.__name__=package.test_A.test
  • [CASE1] test.__name__=test_A.test

ดังนั้นสำหรับ CASE2 an from .. import xxxจะส่งผลให้โมดูลใหม่ที่มี FQMN = package.xxxซึ่งเป็นที่ยอมรับ

ในขณะที่สำหรับ CASE1, ..จากภายในfrom .. import xxxจะกระโดดจากโหนดเริ่มต้น (สมอ) ของtest_Aและไม่ได้รับอนุญาตจาก Python


2
นี่เป็นวิธีที่ซับซ้อนกว่าที่ควรจะเป็น มากสำหรับ Zen of Python
AtilioA

0

ไม่แน่ใจใน python 2.x แต่ใน python 3.6 สมมติว่าคุณพยายามเรียกใช้ทั้งชุดคุณเพียงแค่ต้องใช้ -t

-t, - ไดเรกทอรีไดเรกทอรีระดับบนสุดไดเรกทอรีระดับบนสุดของโครงการ (ค่าเริ่มต้นเพื่อเริ่มไดเรกทอรี)

ดังนั้นในโครงสร้างเช่น

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

หนึ่งสามารถใช้ตัวอย่าง:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

และยังคงนำเข้ารายการที่my_module.my_classไม่มีละครหลัก

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