ต่อไปนี้เป็นสูตรทั่วไปที่ปรับเปลี่ยนให้เหมาะกับตัวอย่างที่ฉันใช้อยู่ตอนนี้เพื่อจัดการกับไลบรารี Python ที่เขียนเป็นแพ็คเกจซึ่งมีไฟล์พึ่งพาซึ่งฉันต้องการทดสอบชิ้นส่วนต่างๆ โทร Let 's นี้lib.foo
และบอกว่ามันต้องการเข้าถึงlib.fileA
ฟังก์ชั่นf1
และf2
และสำหรับการเรียนlib.fileB
Class3
ฉันได้รวมการ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):
ถ้าทำงานเป็นimport lib
หรือfrom lib import foo
เป็นการนำเข้าแพคเกจปกติจากรหัสสามัญ__package
เป็นlib
และเป็น__name__
lib.foo
เราใช้พา ธ โค้ดแรกการนำเข้าจาก.fileA
และอื่น ๆ
ถ้าทำงานเป็นpython lib/foo.py
, __package__
จะไม่มีและจะ__name__
__main__
เราใช้เส้นทางรหัสที่สอง lib
ไดเรกทอรีแล้วจะอยู่ในsys.path
ดังนั้นจึงไม่มีความจำเป็นที่จะต้องเพิ่ม เรานำเข้าจากfileA
ฯลฯ
หากทำงานในlib
ไดเรกทอรีเช่นpython foo.py
พฤติกรรมจะเหมือนกับกรณีที่ 2
หากเรียกใช้ภายใน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
)