การนำเข้าสัมพัทธ์เป็นพันล้านครั้ง


716

ฉันเคยมาที่นี่:

และ URL จำนวนมากที่ฉันไม่ได้คัดลอกบางส่วนอยู่ใน SO บางแห่งในไซต์อื่น ๆ กลับมาเมื่อฉันคิดว่าฉันจะมีวิธีแก้ปัญหาอย่างรวดเร็ว

คำถามที่เกิดขึ้นตลอดไปคือ: เมื่อใช้ Windows 7, 32-bit Python 2.7.3 ฉันจะแก้ปัญหาข้อความ "พยายามนำเข้าที่เกี่ยวข้องในแพ็คเกจที่ไม่ใช่แพ็คเกจ" ได้อย่างไร? ฉันสร้างแบบจำลองที่แน่นอนของแพ็คเกจใน pep-0328:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

การนำเข้าเสร็จจากคอนโซล

ฉันสร้างฟังก์ชันที่ชื่อสแปมและไข่ในโมดูลที่เหมาะสม ตามธรรมชาติมันไม่ทำงาน เห็นได้ชัดว่าคำตอบนั้นอยู่ใน URL ที่ 4 ที่ฉันระบุไว้ แต่เป็นศิษย์เก่าทุกคนสำหรับฉัน มีการตอบสนองนี้กับหนึ่งใน URL ที่ฉันเข้าชม:

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

คำตอบข้างต้นนั้นดูมีแนวโน้มดี แต่มันเป็นตัวอักษรอียิปต์โบราณทั้งหมดสำหรับฉัน ดังนั้นคำถามของฉันฉันจะทำให้ Python ไม่กลับมาหาฉันได้อย่างไร "พยายามนำเข้าที่เกี่ยวข้องในแพ็คเกจที่ไม่ใช่แพคเกจ"? มีคำตอบที่เกี่ยวข้องกับ -m ตามที่คาดคะเน

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


5
คุณพยายามใช้ไฟล์ที่คุณแสดงอย่างไร คุณใช้รหัสอะไร
BrenBarn

ดูpython.org/dev/peps/pep-0328 ฉันใช้รูปแบบแพ็คเกจที่ฉันอธิบายไว้ในโพสต์ของฉัน initไฟล์ .py จะว่างเปล่า moduleY.py มีdef spam(): pass, moduleA.py def eggs(): passมี ฉันพยายามรันคำสั่ง "จากบางสิ่งที่นำเข้าบางสิ่ง" แต่พวกเขาไม่ทำงาน อีกครั้งดู pep-0328

6
ดูคำตอบของฉัน คุณยังไม่ได้อธิบายอย่างชัดเจนถึงสิ่งที่คุณทำ แต่ถ้าคุณพยายามทำfrom .something import somethingในล่ามแบบโต้ตอบนั่นจะไม่ทำงาน การนำเข้าสัมพัทธ์สามารถใช้ได้ภายในโมดูลเท่านั้นไม่ใช่แบบโต้ตอบ
BrenBarn

105
ข้อเท็จจริงที่ว่า "ผู้คนหลายพันล้านคน" ตกลง 83,136 จากความคิดเห็นนี้ - มีปัญหาในการนำเข้าเพื่อค้นหาคำถามนี้ เราสามารถสรุปได้ว่าการนำเข้าไพ ธ อนนั้นตอบโต้ได้ง่ายสำหรับโปรแกรมเมอร์จำนวนมากหากไม่ใช่โปรแกรมเมอร์ส่วนใหญ่ กุยโด้บางทีคุณควรยอมรับสิ่งนี้และขอให้คณะกรรมการออกแบบกลไกการนำเข้าใหม่ อย่างน้อยที่สุดไวยากรณ์นี้ควรใช้งานได้หาก x.py และ z.py อยู่ในไดเรกทอรีเดียวกัน คือถ้า x.py มีคำสั่ง "จาก. z นำเข้า MyZebraClass" x ควรนำเข้า z EVEN หากมีการเรียกใช้เป็นหลัก ! ทำไมถึงยากขนาดนั้น
Steve L

4
หลังจากอ่านหัวข้อนี้มากแม้ว่าจะไม่ใช่คำตอบสำหรับคำถาม "ดูเหมือนว่าใช้การนำเข้าแบบสัมบูรณ์" ดูเหมือนจะเป็นทางออก ...
CodeJockey

คำตอบ:


1042

สคริปต์กับโมดูล

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

มีสองวิธีในการโหลดไฟล์ Python: เป็นสคริปต์ระดับบนสุดหรือเป็นโมดูล ไฟล์จะถูกโหลดเป็นสคริปต์ระดับสูงสุดหากคุณเรียกใช้งานโดยตรงตัวอย่างเช่นโดยพิมพ์python myfile.pyบนบรรทัดคำสั่ง มันถูกโหลดเป็นโมดูลถ้าคุณทำpython -m myfileหรือถ้ามันถูกโหลดเมื่อimportพบคำสั่งในไฟล์อื่น ๆ ในแต่ละครั้งสามารถมีสคริปต์ระดับบนสุดได้ครั้งเดียวเท่านั้น สคริปต์ระดับบนสุดคือไฟล์ Python ที่คุณเรียกใช้เพื่อเริ่มการทำงาน

การตั้งชื่อ

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

ตัวอย่างเช่นในตัวอย่างของคุณ:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

ถ้าคุณนำเข้าmoduleX(หมายเหตุ: ที่นำเข้าไม่ได้ดำเนินการโดยตรง) package.subpackage1.moduleXชื่อของมันจะเป็น หากคุณนำเข้าชื่อของมันจะเป็นmoduleA package.moduleAแต่ถ้าคุณเรียกใช้โดยตรง moduleXจากบรรทัดคำสั่งชื่อของมันจะเป็นแทน__main__และถ้าคุณเรียกใช้โดยตรงจากบรรทัดคำสั่งชื่อของมันจะเป็นmoduleA เมื่อมีการเรียกใช้โมดูลเป็นสคริปต์ระดับบนสุดจะเสียชื่อปกติและชื่อของมันแทน__main____main__

การเข้าถึงโมดูลไม่ผ่านแพ็คเกจที่มีอยู่

มีริ้วรอยเพิ่มเติม: ชื่อโมดูลขึ้นอยู่กับว่ามันถูกนำเข้า "โดยตรง" จากไดเรกทอรีที่มันอยู่ในหรือนำเข้าผ่านแพคเกจ สิ่งนี้สร้างความแตกต่างถ้าคุณเรียกใช้ Python ในไดเรกทอรีและพยายามนำเข้าไฟล์ในไดเรกทอรีเดียวกันนั้น (หรือไดเรกทอรีย่อยของมัน) ตัวอย่างเช่นถ้าคุณเริ่มต้นล่ามหลามในไดเรกทอรีpackage/subpackage1แล้วทำimport moduleXชื่อของmoduleXก็จะเป็นและไม่moduleX package.subpackage1.moduleXนี่เป็นเพราะ Python เพิ่มไดเรกทอรีปัจจุบันลงในพา ธ การค้นหาเมื่อเริ่มต้น หากพบโมดูลที่ต้องนำเข้าในไดเรกทอรีปัจจุบันจะไม่ทราบว่าไดเรกทอรีนั้นเป็นส่วนหนึ่งของแพ็คเกจและข้อมูลแพ็คเกจจะไม่กลายเป็นส่วนหนึ่งของชื่อโมดูล

กรณีพิเศษคือถ้าคุณเรียกใช้ล่ามแบบโต้ตอบ (เช่นพิมพ์pythonและเริ่มป้อนรหัส Python ได้ทันที) __main__ในกรณีนี้ชื่อของเซสชั่นแบบโต้ตอบที่เป็น

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

ตอนนี้ดูข้อความที่คุณรวมไว้ในคำถามของคุณ:

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

การนำเข้าญาติ ...

การอิมพอร์ตสัมพัทธ์ใช้ชื่อของโมดูลเพื่อพิจารณาว่ามันอยู่ที่ไหนในแพคเกจ เมื่อคุณใช้การนำเข้าแบบสัมพัทธ์เช่นfrom .. import fooจุดต่าง ๆ จะเพิ่มระดับจำนวนขึ้นในลำดับชั้นของแพ็คเกจ ตัวอย่างเช่นถ้าชื่อโมดูลปัจจุบันของคุณเป็นpackage.subpackage1.moduleXแล้วจะหมายถึง..moduleA package.moduleAสำหรับ a from .. importถึงทำงานชื่อของโมดูลจะต้องมีจุดอย่างน้อยที่สุดเท่าที่มีอยู่ในimportคำสั่ง

... เป็นเพียงความสัมพันธ์ในแพ็คเกจ

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

สคริปต์ไม่สามารถนำเข้าแบบสัมพันธ์

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

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

สองวิธี:

  1. จริงๆถ้าคุณต้องการที่จะทำงานmoduleXโดยตรง python -m package.subpackage1.moduleXแต่คุณยังคงต้องการที่จะเป็นส่วนหนึ่งของแพคเกจที่คุณสามารถทำได้ -mบอกหลามโหลดมันเป็นโมดูลไม่เป็นสคริปต์ระดับบนสุด

  2. หรือบางทีคุณอาจไม่จริงต้องการที่จะเรียก moduleXคุณเพียงแค่ต้องการที่จะทำงานบางสคริปต์อื่น ๆ กล่าวmyfile.pyว่าการใช้งานmoduleXฟังก์ชั่นภายใน หากเป็นกรณีนี้ให้ใส่ที่myfile.py อื่น - ไม่ได้อยู่ในpackageไดเรกทอรี - และเรียกใช้ หากภายในmyfile.pyคุณทำสิ่งต่าง ๆ เช่นfrom package.moduleA import spamมันจะทำงานได้ดี

หมายเหตุ

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

  • ตั้งแต่ Python 2.6 "ชื่อ" ของโมดูลสำหรับวัตถุประสงค์ในการแก้ปัญหาแพ็กเกจไม่ได้ถูกกำหนดโดย__name__คุณสมบัติเท่านั้น แต่ยังรวมถึง__package__แอตทริบิวต์นั้นด้วย นั่นเป็นเหตุผลที่ฉันหลีกเลี่ยงการใช้สัญลักษณ์ที่ชัดเจน__name__เพื่ออ้างถึง "ชื่อ" ของโมดูล ตั้งแต่ Python 2.6 "ชื่อ" ของโมดูลนั้นมีประสิทธิภาพ__package__ + '.' + __name__หรือ__name__ถ้า__package__เป็นNone)


62
Its name has no dots, and therefore you cannot use from .. import statements inside it. If you try to do so, you will get the "relative-import in non-package" error.นี่คือการรบกวนพื้นฐาน มีอะไรยากเกี่ยวกับการดูไดเรกทอรีปัจจุบัน Python ควรมีความสามารถในสิ่งนี้ ได้รับการแก้ไขในรุ่น 3x หรือไม่

7
@Stopforgettingmyaccounts ... : PEP 366 แสดงวิธีการทำงาน ภายในไฟล์คุณสามารถทำ__package__ = 'package.subpackage1'หรือสิ่งที่ชอบ จากนั้นไฟล์เท่านั้นที่จะเสมอได้รับการพิจารณาเป็นส่วนหนึ่งของแพคเกจที่แม้ว่าเรียกใช้โดยตรง หากคุณมีคำถามอื่น ๆ เกี่ยวกับ__package__คุณอาจต้องการถามคำถามแยกต่างหากเนื่องจากเรากำลังตัดปัญหาคำถามเดิมของคุณที่นี่
BrenBarn

108
นี่ควรเป็นคำตอบสำหรับคำถามการนำเข้า Python ที่เกี่ยวข้องทั้งหมด สิ่งนี้ควรอยู่ในเอกสารด้วยซ้ำ
edsioufi

10
ดูpython.org/dev/peps/pep-0366 - "โปรดทราบว่าสำเร็จรูปนี้เพียงพอหากแพคเกจระดับบนสุดสามารถเข้าถึงได้ผ่าน sys.path จำเป็นต้องใช้รหัสเพิ่มเติมที่จัดการ sys.path เพื่อดำเนินการโดยตรง ทำงานโดยไม่มีแพ็คเกจระดับบนสุดที่นำเข้าได้แล้ว " - นี่เป็นบิตที่น่ารำคาญที่สุดสำหรับฉันเนื่องจาก "รหัสเพิ่มเติม" นี้ค่อนข้างยาวและไม่สามารถเก็บที่อื่นในแพ็คเกจเพื่อให้ทำงานได้อย่างง่ายดาย
Michael Scott Cuthbert

14
คำตอบนี้เป็นอยู่ในปัจจุบันออกในรายละเอียดที่สำคัญบางประการเกี่ยวกับการและ__name__ sys.pathโดยเฉพาะกับpython -m pkg.mod, __name__การตั้งค่าเพื่อ__main__ไม่pkg.mod; การนำเข้าที่เกี่ยวข้องได้รับการแก้ไขโดยใช้__package__แทน__name__ในกรณีนี้ นอกจากนี้หลามเพิ่มไดเรกทอรีสคริปต์มากกว่าไดเรกทอรีปัจจุบันไปsys.pathเมื่อทำงานpython path/to/script.py; มันจะเพิ่มไดเรกทอรีปัจจุบันไปsys.pathเมื่อใช้วิธีการอื่น ๆ python -m pkg.modมากที่สุดรวมทั้ง
user2357112 รองรับ Monica

42

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

ตัวอย่างเช่นเมื่อคุณเขียนในfaa.py :

from .. import foo

สิ่งนี้มีความหมายเฉพาะถ้าfaa.pyถูกระบุและโหลดโดย python ระหว่างการดำเนินการซึ่งเป็นส่วนหนึ่งของแพคเกจ ในกรณีที่ชื่อของโมดูล สำหรับfaa.pyจะเป็นเช่นsome_packagename.faa หากไฟล์ถูกโหลดเพียงเพราะอยู่ในไดเรกทอรีปัจจุบันเมื่อรัน python ชื่อของมันจะไม่อ้างถึงแพ็คเกจใด ๆ และในที่สุดการนำเข้าแบบสัมพัทธ์จะล้มเหลว

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

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo

5
วิธีการแก้ไขที่ถูกต้องคือ from __future__ import absolute_importบังคับให้ผู้ใช้ใช้รหัสของคุณอย่างถูกต้อง ... เพื่อให้คุณสามารถทำเช่นนั้นได้ตลอดเวลาfrom . import foo
Giacomo Alzetta

@Giacomo: คำตอบที่ถูกต้องสำหรับปัญหาของฉัน ขอบคุณ!
ฟาบิโอ

8

ต่อไปนี้เป็นสูตรทั่วไปที่ปรับเปลี่ยนให้เหมาะกับตัวอย่างที่ฉันใช้อยู่ตอนนี้เพื่อจัดการกับไลบรารี Python ที่เขียนเป็นแพ็คเกจซึ่งมีไฟล์พึ่งพาซึ่งฉันต้องการทดสอบชิ้นส่วนต่างๆ โทร Let 's นี้lib.fooและบอกว่ามันต้องการเข้าถึงlib.fileAฟังก์ชั่นf1และf2และสำหรับการเรียนlib.fileBClass3

ฉันได้รวมการprintโทรไปสองสามครั้งเพื่อช่วยอธิบายวิธีการทำงานของมัน ในทางปฏิบัติคุณต้องการลบออก (และอาจเป็นfrom __future__ import print_functionเส้น)

sys.pathตัวอย่างนี้โดยเฉพาะอย่างยิ่งเป็นเรื่องง่ายเกินไปที่จะแสดงให้เห็นเมื่อเราต้องการจริงๆที่จะแทรกเข้าไปในรายการ (ดูคำตอบของลาร์สในกรณีที่เราไม่จำเป็นต้องใช้มันเมื่อเรามีสองคนหรือมากกว่าระดับของไดเรกทอรีแพคเกจและจากนั้นเราจะใช้os.path.dirname(os.path.dirname(__file__))-but มันไม่ได้โดดเจ็บใดที่นี่.) นอกจากนี้ยังพอที่ปลอดภัยที่จะทำเช่นนี้ได้โดยไม่ต้องif _i in sys.pathทดสอบ. อย่างไรก็ตามหากไฟล์ที่นำเข้าแต่ละไฟล์แทรกพา ธ เดียวกัน - เช่นถ้าทั้งคู่fileAและfileBต้องการนำเข้ายูทิลิตี้จากแพ็คเกจ - กลุ่มนี้ขึ้นsys.pathกับเส้นทางเดียวกันหลายครั้งดังนั้นจึงเป็นเรื่องดีที่จะมีif _i not in sys.pathในสำเร็จรูป

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

ความคิดที่นี่คือ (และโปรดทราบว่าสิ่งเหล่านี้ทำงานเหมือนกันใน python2.7 และ python 3.x):

  1. ถ้าทำงานเป็นimport libหรือfrom lib import fooเป็นการนำเข้าแพคเกจปกติจากรหัสสามัญ__packageเป็นlibและเป็น__name__ lib.fooเราใช้พา ธ โค้ดแรกการนำเข้าจาก.fileAและอื่น ๆ

  2. ถ้าทำงานเป็นpython lib/foo.py, __package__จะไม่มีและจะ__name____main__

    เราใช้เส้นทางรหัสที่สอง libไดเรกทอรีแล้วจะอยู่ในsys.pathดังนั้นจึงไม่มีความจำเป็นที่จะต้องเพิ่ม เรานำเข้าจากfileAฯลฯ

  3. หากทำงานในlibไดเรกทอรีเช่นpython foo.pyพฤติกรรมจะเหมือนกับกรณีที่ 2

  4. หากเรียกใช้ภายในlibไดเรกทอรีเป็นpython -m fooพฤติกรรมจะคล้ายกับกรณีที่ 2 และ 3 อย่างไรก็ตามเส้นทางไปยังlibไดเรกทอรีไม่ได้อยู่ในsys.pathดังนั้นเราจึงเพิ่มมันก่อนที่จะนำเข้า import fooเช่นเดียวกับถ้าเราเรียกงูใหญ่แล้ว

    (เนื่องจาก. อยู่ในsys.pathเราไม่จำเป็นต้องเพิ่มรุ่นที่แน่นอนของเส้นทางที่นี่นี่เป็นที่ซึ่งโครงสร้างการซ้อนของแพ็กเกจที่เราต้องการทำfrom ..otherlib.fileC import ...มีความแตกต่างหากคุณไม่ได้ทำเช่นนี้คุณสามารถ ละเว้นการsys.pathจัดการทั้งหมดอย่างสิ้นเชิง)

หมายเหตุ

ยังมีการเล่นโวหาร หากคุณดำเนินการทั้งหมดนี้จากภายนอก:

$ python2 lib.foo

หรือ:

$ python3 lib.foo

lib/__init__.pyลักษณะการทำงานขึ้นอยู่กับเนื้อหาของ หากสิ่งนั้นมีอยู่และว่างเปล่าทั้งหมดนั้นก็เป็นอย่างดี:

Package named 'lib'; __name__ is '__main__'

แต่ถ้านำเข้าlib/__init__.py เองroutineเพื่อให้สามารถส่งออกroutine.nameได้โดยตรงlib.nameคุณจะได้รับ:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

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

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

เตือนเป็นของใหม่ แต่เตือนเกี่ยวกับพฤติกรรมไม่ได้ มันเป็นส่วนหนึ่งของสิ่งที่บางคนเรียกดักนำเข้าคู่ (สำหรับรายละเอียดเพิ่มเติมดูปัญหา 27487 ) Nick Coghlan พูดว่า:

กับดักต่อไปนี้มีอยู่ใน Python เวอร์ชันปัจจุบันทั้งหมดรวมถึง 3.3 และสามารถสรุปได้ในแนวทางทั่วไปต่อไปนี้: "อย่าเพิ่มไดเรกทอรีแพ็คเกจหรือไดเรกทอรีใด ๆ ภายในแพ็คเกจโดยตรงไปยังเส้นทาง Python"

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

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

นั่นคือเราปรับเปลี่ยนได้sys.pathนานพอที่จะบรรลุการนำเข้าของเราจากนั้นนำกลับมาใช้ใหม่ (ลบหนึ่งสำเนา_iหากและถ้าเราเพิ่มอีกหนึ่งสำเนา_i)


7

ดังนั้นหลังจากหาเรื่องเกี่ยวกับเรื่องนี้พร้อมกับคนอื่น ๆ ฉันพบเจอข้อความที่โพสต์โดยDorian Bในบทความนี้ที่แก้ไขปัญหาเฉพาะที่ฉันมีที่ฉันจะพัฒนาโมดูลและคลาสสำหรับใช้กับบริการเว็บ แต่ฉันก็ต้องการ สามารถทดสอบพวกเขาในขณะที่ฉันเขียนโค้ดโดยใช้เครื่องมือดีบักเกอร์ใน PyCharm ในการรันการทดสอบในชั้นเรียนที่มีในตัวเองฉันจะรวมสิ่งต่อไปนี้ไว้ท้ายไฟล์คลาสของฉัน:

if __name__ == '__main__':
   # run test code here...

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

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

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


1
ที่จริงแก้มัน แต่มันก็น่ารังเกียจจริงๆ ทำไมนี่ไม่ใช่พฤติกรรมเริ่มต้น!
lo tolmencre

4

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

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()

2

ฉันมีปัญหาที่คล้ายกันซึ่งฉันไม่ต้องการเปลี่ยนเส้นทางการค้นหาโมดูล Python และจำเป็นต้องโหลดโมดูลที่ค่อนข้างมาจากสคริปต์ (ทั้งๆที่"สคริปต์ไม่สามารถนำเข้าเทียบกับทั้งหมด"ตามที่ BrenBarn อธิบายไว้ข้างต้น)

ดังนั้นฉันใช้แฮ็คต่อไปนี้ โชคไม่ดีที่มันต้องอาศัยimpโมดูลที่เลิกใช้งานตั้งแต่เวอร์ชัน 3.4 ถูกยกเลิกimportlibไป (เป็นไปได้ด้วยimportlibหรือไม่ฉันไม่รู้) ถึงกระนั้นแฮ็คก็ใช้ได้ตอนนี้

ตัวอย่างสำหรับการเข้าถึงสมาชิกmoduleXในsubpackage1จากสคริปต์ที่อาศัยอยู่ในที่subpackage2โฟลเดอร์:

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

วิธีทำความสะอาดที่ดูเหมือนจะปรับเปลี่ยน sys.path ที่ใช้สำหรับการโหลดโมดูลตามที่ Federico กล่าวถึง

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *

ที่ดูดีกว่า ... เลวร้ายเกินไปคุณยังต้องฝังชื่อของไดเรกทอรีหลักในไฟล์ ... อาจจะปรับปรุงได้ด้วย importlib บางทีการนำเข้าลิปอาจจะเป็นรูปลิงที่ทำให้การนำเข้าแบบสัมพัทธ์ "ทำงานได้" สำหรับกรณีการใช้งานที่เรียบง่าย ฉันจะถอดมัน
Andrew Wagner

ฉันใช้ python 2.7.14 บางสิ่งเช่นนี้ยังคงใช้ได้ไหม
user3474042

ฉันเพิ่งทดสอบทั้งสองวิธีใน python 2.7.10 และมันใช้ได้ดีสำหรับฉัน หากความจริงคุณไม่มีปัญหาเกี่ยวกับโมดูล imp ที่เลิกใช้แล้วใน 2.7 ดังนั้นดีกว่าทั้งหมด
ลาร์ส

2

__name__ การเปลี่ยนแปลงขึ้นอยู่กับว่ารหัสในคำถามจะถูกเรียกใช้ใน namespace ส่วนกลางหรือเป็นส่วนหนึ่งของโมดูลที่นำเข้า

หากรหัสไม่ได้ทำงานในพื้นที่ส่วนกลาง__name__จะเป็นชื่อของโมดูล หากมีการทำงานใน namespace โลก - ตัวอย่างเช่นถ้าคุณพิมพ์ลงในคอนโซลหรือเรียกใช้โมดูลที่เป็นสคริปต์ที่ใช้python.exe yourscriptnamehere.pyแล้วจะกลายเป็น __name__"__main__"

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

คุณพยายามนำเข้าเหล่านี้จากคอนโซลหรือไม่


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

ฉันพยายามทำการนำเข้าเหล่านี้จากคอนโซลโดยไฟล์ที่ใช้งานอยู่เป็นโมดูลที่ถูกต้อง

@Stopforgettingmyaccounts ... : คุณหมายถึง "active file"?
BrenBarn

ฉันใช้ Pyscripter ฉันอยู่ใน moduleX.py เมื่อฉันเรียกใช้การนำเข้าเหล่านี้: จาก. moduleY นำเข้าสแปมและจาก นำเข้า ModuleY

ไม่ได้นำเข้า. โมดูลตามด้วย moduleY.spam ()?
theodox

2

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

กรณี

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

ใช้ตัวอย่างที่เราคุ้นเคยและเพิ่มเข้าไปในนั้น moduleX.py มีการนำเข้าที่สัมพันธ์กับ .. โมดูล เนื่องจากฉันพยายามเขียนสคริปต์ทดสอบในไดเรกทอรี subpackage1 ที่นำเข้า moduleX แต่มีข้อผิดพลาดที่หวั่นอธิบายโดย OP

สารละลาย

ย้ายสคริปต์ทดสอบไปที่ระดับเดียวกับแพคเกจและนำเข้า package.subpackage1.moduleX

คำอธิบาย

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

เมื่อฉันนำเข้า moduleX จากด้านบนแล้วชื่อข้างใน moduleX คือ package.subpackage1.moduleX และสามารถพบการนำเข้าที่เกี่ยวข้อง


หวังว่าคุณจะสามารถแนะนำฉันเกี่ยวกับเรื่องนี้ ในลิงก์ต่อไปนี้ถ้าคุณไปที่กรณีที่ 3 จะบอกว่าเป็นไปไม่ได้ที่โซลูชัน 1 โปรดตรวจสอบเรื่องนี้และแจ้งให้เราทราบ มันจะช่วยฉันอย่างมาก chrisyeh96.github.io/2017/08/08/…
ตัวแปร

@ ตัวแปรมีการพิมพ์ผิดในลิงก์และฉันไม่ได้รับอนุญาตให้แก้ไข ดูที่กรณีที่ 3 และไม่ได้ทำตามสิ่งที่คุณได้รับ เมื่อฉันลองตัวอย่างใน python 2 ไม่มีปัญหาที่ทำให้ฉันคิดว่าฉันพลาดอะไรบางอย่าง บางทีคุณควรโพสต์คำถามใหม่ แต่ต้องให้ตัวอย่างที่ชัดเจนยิ่งขึ้น กรณีที่ 4 สัมผัสกับสิ่งที่ฉันกำลังพูดถึงในคำตอบของฉันที่นี่: คุณไม่สามารถสร้างไดเรกทอรีสำหรับการนำเข้าแบบสัมพัทธ์ได้เว้นแต่ว่าล่ามจะเริ่มต้นในไดเรกทอรีหลัก
Brad Dre

ขอบคุณฉันหมายถึง python 3 และที่นี่คำถามstackoverflow.com/questions/58577767/ …
ตัวแปร

1

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

เขียนแพคเกจหลามเล็ก ๆ น้อย ๆ เพื่อ PyPi ที่อาจช่วยให้ผู้ชมของคำถามนี้ แพ็คเกจทำหน้าที่เป็นวิธีแก้ปัญหาหากผู้ใช้ต้องการเรียกใช้ไฟล์หลามที่มีการนำเข้าที่มีแพ็คเกจระดับบนจากภายในแพ็คเกจ / โครงการโดยไม่ต้องอยู่ในไดเรกทอรีของการนำเข้าไฟล์โดยตรง https://pypi.org/project/import-anywhere/


-2

เพื่อให้ Python ไม่กลับมาหาฉัน "พยายามนำเข้าที่เกี่ยวข้องในแพ็คเกจที่ไม่ใช่" แพคเกจ /

init .py subpackage1 / init .py moduleX.py moduleY.py subpackage2 / init .py moduleZ.py moduleA.py

ข้อผิดพลาดนี้เกิดขึ้นเฉพาะเมื่อคุณใช้การนำเข้าแบบสัมพัทธ์กับไฟล์พาเรนต์ ตัวอย่างเช่นไฟล์หลักส่งคืนหลักหลังจากคุณโค้ด "print ( name )" ใน moduleA.py .so ไฟล์นี้เป็นไฟล์หลักแล้วไม่สามารถส่งคืนแพกเกจหลักใด ๆ เพิ่มเติมได้ จำเป็นต้องมีการนำเข้าแบบสัมพัทธ์ในไฟล์ของแพ็คเกจย่อย subpackage1 และ subpackage2 คุณสามารถใช้ ".. " เพื่ออ้างถึงไดเรกทอรีหลักหรือโมดูล แต่พาเรนต์หลักคือถ้าแพ็คเกจระดับบนสุดไม่สามารถไปได้ไกลกว่าไดเรกทอรีหลัก (แพ็คเกจ) ไฟล์ดังกล่าวที่คุณใช้การนำเข้าแบบสัมพันธ์กับผู้ปกครองสามารถทำงานกับแอปพลิเคชันของการนำเข้าแบบสัมบูรณ์เท่านั้น หากคุณจะใช้ ABSOLUTE IMPORT ในแพ็คเกจผู้ปกครองจะไม่มีข้อผิดพลาดเกิดขึ้นเนื่องจาก python รู้ว่าใครอยู่ในระดับสูงสุดของแพ็คเกจแม้ว่าไฟล์ของคุณจะอยู่ในแพ็คเกจย่อยเนื่องจากแนวคิดของเส้นทาง PYTHON ซึ่งกำหนดระดับบนสุดของโครงการ

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