Python: วิธีที่ดีที่สุดในการเพิ่มลงใน sys.path โดยสัมพันธ์กับสคริปต์ที่รันอยู่ในปัจจุบัน


103

ฉันมีไดเรกทอรีที่เต็มไปด้วยสคริปต์ (สมมติว่าproject/bin) ฉันยังมีไลบรารีอยู่project/libและต้องการให้สคริปต์โหลดโดยอัตโนมัติ นี่คือสิ่งที่ฉันใช้ตามปกติที่ด้านบนของแต่ละสคริปต์:

#!/usr/bin/python
from os.path import dirname, realpath, sep, pardir
import sys
sys.path.append(dirname(realpath(__file__)) + sep + pardir + sep + "lib")

# ... now the real code
import mylib

มันยุ่งยากน่าเกลียดและต้องวางที่จุดเริ่มต้นของทุกไฟล์ มีวิธีที่ดีกว่านี้หรือไม่?

สิ่งที่ฉันหวังไว้จริงๆคือสิ่งที่ราบรื่นเช่นนี้:

#!/usr/bin/python
import sys.path
from os.path import pardir, sep
sys.path.append_relative(pardir + sep + "lib")

import mylib

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

#!/usr/bin/python --relpath_append ../lib
import mylib

นั่นจะไม่พอร์ตโดยตรงไปยังแพลตฟอร์มที่ไม่ใช่ posix แต่จะทำให้ทุกอย่างสะอาด


1
ดูเพิ่มเติมที่: stackoverflow.com/questions/2349991/…
dreftymac

คำตอบ:


25

หากคุณไม่ต้องการแก้ไขแต่ละไฟล์

  • ติดตั้งไลบรารีของคุณเหมือน python libray ทั่วไป
    หรือ
  • ตั้งค่าPYTHONPATHเป็นไฟล์lib

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

import import_my_lib

เก็บไว้import_my_lib.pyใน bin และimport_my_libสามารถกำหนดเส้นทาง python ได้อย่างถูกต้องตามที่libคุณต้องการ


120

นี่คือสิ่งที่ฉันใช้:

import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))

2
ฉันจะทำ sys.path.insert (0, .. ) เพื่อที่จะไม่ถูกแทนที่ด้วยเส้นทางอื่น
John Jiang

ไม่มีทางที่จะทำให้สิ่งนี้ทำงานโดยอัตโนมัติได้จริงหรือ?
Denis de Bernardy

1
นี่เป็นความเสี่ยงเล็กน้อย หาก__file__เป็นชื่อไฟล์สัมพัทธ์ที่สัมพันธ์กับไดเร็กทอรีการทำงานปัจจุบัน (เช่นsetup.py) os.path.dirname(__file__)จะเป็นสตริงว่าง สำหรับวันนี้และกังวลที่คล้ายกันเกิดขึ้นโดยจอห์นเจียง , ekhumoro 's เพิ่มเติมการแก้ปัญหาวัตถุประสงค์ทั่วไปเป็นที่นิยมอย่างมาก
Cecil Curry

31

ฉันกำลังใช้:

import sys,os
sys.path.append(os.getcwd())

5
ฉันมักจะทำsys.path.append('.')
kimbo

1
จะเกิดอะไรขึ้นถ้าสคริปต์ถูกเรียกใช้จากไดเร็กทอรีอื่น ตัวอย่างเช่นจากไดเรกทอรีรากโดยให้เส้นทางระบบแบบเต็ม? แล้ว os.getcwd () จะกลับมา "/"
obayhan

2
อย่าทำเช่นนี้ ไม่รับประกันไดเร็กทอรีการทำงานปัจจุบัน (CWD) ว่าเป็นอย่างที่คุณคิด - โดยเฉพาะอย่างยิ่งภายใต้กรณีที่คาดไม่ถึงที่คุณควรจะได้เห็นในระยะทางหนึ่งไมล์ เพียงแค่อ้างอิง__file__แทนเช่นเดียวกับนักพัฒนาที่มีเหตุผล
Cecil Curry

15

สร้างโมดูล Wrapper project/bin/libซึ่งประกอบด้วยสิ่งนี้:

import sys, os

sys.path.insert(0, os.path.join(
    os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib'))

import mylib

del sys.path[0], sys, os

จากนั้นคุณสามารถแทนที่ส่วนท้ายทั้งหมดที่ด้านบนของสคริปต์ของคุณด้วย:

#!/usr/bin/python
from lib import mylib

9

หากคุณไม่ต้องการเปลี่ยนเนื้อหาสคริปต์ไม่ว่าด้วยวิธีใดก็ตามให้นำหน้าไดเรกทอรีการทำงานปัจจุบัน.ไปที่ $ PYTHONPATH (ดูตัวอย่างด้านล่าง)

PYTHONPATH=.:$PYTHONPATH alembic revision --autogenerate -m "First revision"

และเรียกมันว่าวัน!


docs.python.org/3/tutorial/modules.html#the-module-search-pathกล่าวว่า: "sys.path เริ่มต้นจากตำแหน่งเหล่านี้: - ไดเร็กทอรีที่มีสคริปต์อินพุต" ฉันคิดว่านี่ไม่ใช่เรื่องจริง

ฉันมักจะทำสิ่งนี้โดยเฉพาะใน. envrc เพื่อให้กับ Direnv นั้นเป็นแบบอัตโนมัติและแยกได้เช่นกัน
EdvardM

8

การใช้ python 3.4+ จำกัด
การใช้ cx_freeze หรือใช้ใน IDLE 😃

import sys
from pathlib import Path

sys.path.append(Path(__file__).parent / "lib")

เข้ากันได้กับ Python2: import pathlib import os sys.path.append (os.path.dirname ( file ))
michael

6

คุณสามารถรันสคริปต์python -mจาก root dir ที่เกี่ยวข้อง และส่ง "เส้นทางโมดูล" เป็นอาร์กิวเมนต์

ตัวอย่าง: $ python -m module.sub_module.main # Notice there is no '.py' at the end.


ตัวอย่างอื่น:

$ tree  # Given this file structure
.
├── bar
│   ├── __init__.py
│   └── mod.py
└── foo
    ├── __init__.py
    └── main.py

$ cat foo/main.py
from bar.mod import print1
print1()

$ cat bar/mod.py
def print1():
    print('In bar/mod.py')

$ python foo/main.py  # This gives an error
Traceback (most recent call last):
  File "foo/main.py", line 1, in <module>
    from bar.mod import print1
ImportError: No module named bar.mod

$ python -m foo.main  # But this succeeds
In bar/mod.py

2
การใช้สวิตช์ m เป็นวิธีที่แนะนำในการทำสิ่งนี้ไม่ใช่การแฮ็กเส้นทาง sys
Mr_and_Mrs_D

4

มีปัญหากับทุกคำตอบที่สามารถสรุปได้ว่า "เพียงแค่เพิ่มคาถาวิเศษนี้ที่จุดเริ่มต้นของสคริปต์ของคุณดูสิ่งที่คุณสามารถทำได้โดยใช้โค้ดเพียงบรรทัดหรือสองบรรทัด" พวกเขาจะไม่ทำงานในทุกสถานการณ์!

ตัวอย่างเช่นหนึ่งคาถาวิเศษเช่นใช้ไฟล์ ขออภัยหากคุณจัดแพคเกจสคริปต์ของคุณโดยใช้ cx_Freeze หรือคุณกำลังใช้ IDLE สิ่งนี้จะทำให้เกิดข้อยกเว้น

คาถาวิเศษอีกอย่างใช้ os.getcwd () สิ่งนี้จะใช้ได้เฉพาะเมื่อคุณเรียกใช้สคริปต์จากพรอมต์คำสั่งและไดเร็กทอรีที่มีสคริปต์ของคุณเป็นไดเร็กทอรีการทำงานปัจจุบัน (นั่นคือคุณใช้คำสั่ง cd เพื่อเปลี่ยนเป็นไดเร็กทอรีก่อนที่จะรันสคริปต์) เอ๊ะเทพ! ฉันหวังว่าฉันจะไม่ต้องอธิบายว่าทำไมสิ่งนี้ถึงใช้ไม่ได้ถ้าสคริปต์ Python ของคุณอยู่ใน PATH ที่ไหนสักแห่งและคุณเรียกใช้โดยพิมพ์ชื่อไฟล์สคริปต์ของคุณ

โชคดีที่มีคาถาวิเศษที่ใช้ได้ผลในทุกกรณีที่ฉันได้ทดสอบ น่าเสียดายที่คาถาวิเศษเป็นมากกว่าโค้ดเพียงบรรทัดหรือสองบรรทัด

import inspect
import os
import sys

# Add script directory to sys.path.
# This is complicated due to the fact that __file__ is not always defined.

def GetScriptDirectory():
    if hasattr(GetScriptDirectory, "dir"):
        return GetScriptDirectory.dir
    module_path = ""
    try:
        # The easy way. Just use __file__.
        # Unfortunately, __file__ is not available when cx_freeze is used or in IDLE.
        module_path = __file__
    except NameError:
        if len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0]):
            module_path = sys.argv[0]
        else:
            module_path = os.path.abspath(inspect.getfile(GetScriptDirectory))
            if not os.path.exists(module_path):
                # If cx_freeze is used the value of the module_path variable at this point is in the following format.
                # {PathToExeFile}\{NameOfPythonSourceFile}. This makes it necessary to strip off the file name to get the correct
                # path.
                module_path = os.path.dirname(module_path)
    GetScriptDirectory.dir = os.path.dirname(module_path)
    return GetScriptDirectory.dir

sys.path.append(os.path.join(GetScriptDirectory(), "lib"))
print(GetScriptDirectory())
print(sys.path)

อย่างที่คุณเห็นนี่ไม่ใช่เรื่องง่าย!


1

อันนี้เหมาะกับฉันที่สุด ใช้:

os.path.abspath('')

บน Mac ควรพิมพ์สิ่งต่างๆเช่น:

'/Users/<your username>/<path_to_where_you_at>'

เพื่อให้ได้เส้นทาง abs ไปยัง wd ปัจจุบันอันนี้ดีกว่าเพราะตอนนี้คุณสามารถขึ้นไปได้หากต้องการดังนี้:

os.path.abspath('../')

และตอนนี้:

 '/Users/<your username>/'

ดังนั้นหากคุณต้องการนำเข้าutilsจากที่นี่'/Users/<your username>/'
สิ่งที่คุณต้องทำคือ:

import sys
sys.path.append(os.path.abspath('../'))

0

ฉันเห็น Shebang ในตัวอย่างของคุณ หากคุณกำลังเรียกใช้สคริปต์ bin ของคุณเป็น./bin/foo.pyแทนที่จะpython ./bin/foo.pyมีตัวเลือกในการใช้ shebang เพื่อเปลี่ยน$PYTHONPATHตัวแปร

คุณไม่สามารถเปลี่ยนตัวแปรสภาพแวดล้อมได้โดยตรงใน Shebangs ดังนั้นคุณจะต้องมีสคริปต์ตัวช่วยขนาดเล็ก ใส่python.shลงในbinโฟลเดอร์ของคุณ:

#!/usr/bin/env bash
export PYTHONPATH=$PWD/lib
exec "/usr/bin/python" "$@"

แล้วเปลี่ยน shebang ของคุณ./bin/foo.pyเป็น#!bin/python.sh


0

เมื่อเราพยายามเรียกใช้ไฟล์ python ด้วยเส้นทางจากเทอร์มินัล

import sys
#For file name
file_name=sys.argv[0]
#For first argument
dir= sys.argv[1]
print("File Name: {}, argument dir: {}".format(file_name, dir)

บันทึกไฟล์ (test.py)

เรียกใช้ระบบ

เปิดเทอร์มินัลและไปที่ dir ที่บันทึกไฟล์ จากนั้นเขียน

python test.py "/home/saiful/Desktop/bird.jpg"

กด Enter

เอาท์พุต:

File Name: test, Argument dir: /home/saiful/Desktop/bird.jpg

0

ฉันใช้:

from site import addsitedir

จากนั้นสามารถใช้ไดเร็กทอรีสัมพัทธ์! addsitedir('..\lib') ; จุดสองจุดหมายถึงการย้าย (ขึ้น) หนึ่งไดเร็กทอรีก่อน

โปรดจำไว้ว่าทุกอย่างขึ้นอยู่กับว่าไดเรกทอรีการทำงานปัจจุบันของคุณเริ่มต้นจากอะไร ถ้า C: \ Joe \ Jen \ Becky แล้วจะนำเข้า additedir ('.. \ lib') ไปยังเส้นทางของคุณ C: \ Joe \ Jen \ lib

C:\
  |__Joe
      |_ Jen
      |     |_ Becky
      |_ lib

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