วิธีการกำหนดไดเรกทอรีสคริปต์ปัจจุบันอย่างถูกต้อง?


260

ฉันต้องการจะดูว่าวิธีที่ดีที่สุดในการกำหนดไดเรกทอรีสคริปต์ปัจจุบันในหลามคืออะไร?

ฉันค้นพบว่าเนื่องจากการเรียกรหัสไพ ธ อนหลายวิธีทำให้หาทางออกที่ดีได้ยาก

นี่คือปัญหาบางอย่าง:

  • __file__ไม่ได้กำหนดว่าสคริปต์ที่จะดำเนินการกับexec,execfile
  • __module__ ถูกกำหนดในโมดูลเท่านั้น

ใช้กรณี:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (จากสคริปต์อื่นที่สามารถอยู่ในไดเรกทอรีอื่นและสามารถมีไดเรกทอรีปัจจุบันอื่น

ฉันรู้ว่าไม่มีวิธีการแก้ปัญหาที่สมบูรณ์แบบ แต่ฉันกำลังมองหาวิธีที่ดีที่สุดที่สามารถแก้ไขกรณีส่วนใหญ่ได้

วิธีการที่ใช้มากที่สุดคือแต่นี้จริงๆไม่ทำงานถ้าคุณรันสคริปต์จากอีกคนหนึ่งด้วยos.path.dirname(os.path.abspath(__file__))exec()

คำเตือน

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


1
คุณจะเจาะจงเจาะจงมากขึ้นในกรณีที่คุณต้องรู้ว่าไฟล์มาจากไหน? - ในรหัสที่นำเข้าไฟล์ (รวมโฮสต์ทราบ) หรือในไฟล์ที่นำเข้า? (ทาสที่รู้จักตนเอง)
synthesizerpatel

3
ดูpathlibโซลูชันของ Ron Kalian หากคุณใช้ python 3.4 ขึ้นไป: stackoverflow.com/a/48931294/1011724
Dan

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

การค้นพบที่น่าสนใจผมเพิ่งทำ: เมื่อทำpython myfile.pyจากเปลือกหอยก็ทำงาน แต่ทั้งสอง:!python %และ:!python myfile.pyจากภายในกลุ่มล้มเหลวด้วยระบบไม่สามารถหาเส้นทางที่ระบุไว้ มันค่อนข้างน่ารำคาญ ทุกคนสามารถแสดงความคิดเห็นเกี่ยวกับเหตุผลที่อยู่เบื้องหลังสิ่งนี้และวิธีแก้ปัญหาที่อาจเกิดขึ้นได้หรือไม่
inVader

คำตอบ:


231
os.path.dirname(os.path.abspath(__file__))

เป็นสิ่งที่ดีที่สุดที่คุณจะได้รับ

เป็นเรื่องผิดปกติที่จะเรียกใช้สคริปต์ด้วยexec/ execfile; โดยปกติคุณควรใช้โครงสร้างพื้นฐานของโมดูลเพื่อโหลดสคริปต์ หากคุณต้องใช้วิธีการเหล่านี้ฉันขอแนะนำให้ตั้งค่า__file__ในสิ่งที่globalsคุณส่งให้สคริปต์เพื่อให้สามารถอ่านชื่อไฟล์นั้นได้

ไม่มีวิธีอื่นในการรับชื่อไฟล์ในรหัสที่ถูกเรียกใช้: ดังที่คุณทราบ CWD อาจแตกต่างไปจากเดิมอย่างสิ้นเชิง


2
ไม่เคยพูดว่าไม่เคย? ตามนี้: stackoverflow.com/a/18489147 คำตอบโซลูชันข้ามแพลตฟอร์มคือ abspath (getsourcefile (แลมบ์ดา: 0))? หรือมีอย่างอื่นที่ฉันหายไป?
Jeff Ellen

131

หากคุณต้องการปกปิดกรณีที่สคริปต์ถูกเรียกผ่านexecfile(...)คุณสามารถใช้inspectโมดูลเพื่ออนุมานชื่อไฟล์ (รวมถึงเส้นทาง) เท่าที่ฉันทราบนี่จะใช้งานได้กับทุกกรณีที่คุณระบุไว้:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))

4
ฉันคิดว่านี่เป็นวิธีที่แข็งแกร่งที่สุด แต่ฉันถามถึงความต้องการที่ระบุไว้ของ OP สำหรับสิ่งนี้ ฉันมักจะเห็นนักพัฒนาทำสิ่งนี้เมื่อพวกเขาใช้ไฟล์ข้อมูลในสถานที่ซึ่งสัมพันธ์กับโมดูลการดำเนินการ แต่ไฟล์ข้อมูล IMO ควรอยู่ในตำแหน่งที่รู้จัก
Ryan Ginstrom

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

1
ยังแก้ปัญหาไม่ดีเพียงลองโทรchdir()ก่อนฟังก์ชั่นก็จะเปลี่ยนผลลัพธ์ การเรียกสคริปต์ python จากไดเรกทอรีอื่นจะเปลี่ยนผลลัพธ์ด้วยดังนั้นจึงไม่ใช่วิธีที่ดี
sorin

2
os.path.expanduser("~")เป็นวิธีข้ามแพลตฟอร์มเพื่อรับไดเรกทอรีของผู้ใช้ น่าเสียดายที่นี่ไม่ใช่วิธีปฏิบัติที่ดีที่สุดของ Windows สำหรับการติดข้อมูลแอปพลิเคชัน
Ryan Ginstrom

6
@ โซริน: ฉันได้ลองchdir()ก่อนใช้งานสคริปต์; มันสร้างผลลัพธ์ที่ถูกต้อง ฉันได้ลองเรียกใช้สคริปต์จากไดเรกทอรีอื่นและใช้งานได้ ผลลัพธ์ที่ได้จะเป็นเช่นเดียวกับการแก้ปัญหาชั่นinspect.getabsfile()
jfs

43
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

มันใช้งานได้กับ CPython, Jython, Pypy มันทำงานได้ถ้าสคริปต์ถูกดำเนินการโดยใช้execfile()( sys.argv[0]และ__file__- ตามโซลูชั่นจะล้มเหลวที่นี่) มันทำงานได้ถ้าสคริปต์ที่อยู่ภายในไฟล์ซิปปฏิบัติการ (/ ไข่) มันจะทำงานถ้าสคริปต์นั้น "import" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) จากไฟล์ zip; มันจะคืนค่าเส้นทางไฟล์เก็บถาวรในกรณีนี้ มันจะทำงานถ้าสคริปต์ถูกคอมไพล์เป็นไฟล์เรียกทำงานแบบสแตนด์อโลน ( sys.frozen) มันใช้งานได้กับ symlinks ( realpathกำจัดลิงก์สัญลักษณ์) มันทำงานในล่ามแบบโต้ตอบ; ส่งคืนไดเร็กทอรีการทำงานปัจจุบันในกรณีนี้


ทำงานได้อย่างสมบูรณ์แบบกับ PyInstaller
gaborous

1
มีเหตุผลว่าทำไมgetabsfile(..)ไม่ได้กล่าวถึงในเอกสารสำหรับinspect ? มันจะปรากฏในแหล่งที่มาที่เชื่อมโยงออกจากหน้านั้น
Evgeni Sergeev

@EvgeniSergeev อาจเป็นข้อผิดพลาด มันเป็นกระดาษห่อง่ายรอบgetsourcefile(), getfile()ได้รับการบันทึก
jfs

24

ใน Python 3.4+ คุณสามารถใช้pathlibโมดูลที่ง่ายกว่านี้:

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent

2
ความเรียบง่ายที่ยอดเยี่ยม!
Cometsong

3
คุณสามารถใช้Path(__file__)(ไม่จำเป็นสำหรับinspectโมดูล)
Peque

@Peque การทำเช่นนั้นจะสร้างพา ธ รวมถึงชื่อไฟล์ปัจจุบันไม่ใช่ไดเรกทอรีหลัก หากฉันพยายามรับไดเรกทอรีสคริปต์ปัจจุบันเพื่อให้ชี้ไปที่ไฟล์ในไดเรกทอรีเดียวกันเช่นคาดว่าจะโหลดไฟล์กำหนดค่าในไดเรกทอรีเดียวกันกับสคริปต์Path(__file__)ให้/path/to/script/currentscript.pyเมื่อ OP ต้องการที่จะได้รับ/path/to/script/
Davos

8
โอ้ฉันเข้าใจผิดคุณหมายถึงการหลีกเลี่ยงโมดูลตรวจสอบและใช้สิ่งparent = Path(__file__).resolve().parent ที่ดีกว่า
Davos

3
A. @Dut คุณควรใช้.joinpath()(หรือ/ผู้ประกอบการ) +สำหรับเรื่องนี้ไม่ได้
Eugene Yarmash

13

os.path...วิธีการเป็น 'สิ่งที่ทำ' ในหลาม 2

ใน Python 3 คุณสามารถค้นหาไดเรกทอรีของสคริปต์ดังนี้

from pathlib import Path
cwd = Path(__file__).parents[0]

11
Path(__file__).parentหรือเพียงแค่ แต่cwdเป็นความเข้าใจผิดที่ไม่ได้เป็นไดเรกทอรีการทำงานปัจจุบันแต่ไดเรกทอรีของแฟ้ม พวกเขาอาจเหมือนกัน แต่นั่นไม่ใช่กรณีปกติ
Nuno André

5

เพียงใช้os.path.dirname(os.path.abspath(__file__))และตรวจสอบอย่างรอบคอบว่ามีความต้องการที่แท้จริงสำหรับกรณีที่execมีการใช้หรือไม่ อาจเป็นสัญญาณของการออกแบบที่มีปัญหาหากคุณไม่สามารถใช้สคริปต์ของคุณเป็นโมดูล

โปรดจำไว้ว่าZen of Python # 8และหากคุณเชื่อว่ามีข้อโต้แย้งที่ดีสำหรับกรณีการใช้งานที่ต้องใช้งานexecโปรดแจ้งให้เราทราบรายละเอียดเพิ่มเติมเกี่ยวกับพื้นหลังของปัญหา


2
หากคุณไม่ได้ทำงานกับ exec () คุณจะหลวมบริบทดีบักเกอร์ และ exec () ควรจะเร็วกว่าการเริ่มต้นกระบวนการใหม่อย่างมาก
โซริน

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

4

หากว่า

import os
cwd = os.getcwd()

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


3
มันไม่ได้ช่วยอะไร ฉันเชื่อว่า @bogdan กำลังมองหาไดเรกทอรีสำหรับสคริปต์ที่อยู่ด้านบนของ call stack เช่นในทุกกรณีเขา / เธอควรพิมพ์ไดเรกทอรีที่ 'myfile.py' ตั้งอยู่ แต่วิธีการของคุณเท่านั้นที่จะพิมพ์ไดเรกทอรีของแฟ้มที่สายexec('myfile.py')เช่นเดียวกับและ__file__ sys.argv[0]
Zhang18

ใช่มันสมเหตุสมผลแล้ว ฉันแค่อยากให้แน่ใจว่า @bogdan ไม่ได้มองอะไรที่ง่ายและฉันไม่สามารถบอกได้ว่าพวกเขาต้องการอะไร
Will McCutchen

3

ก่อน .. สองกรณีการใช้งานที่ขาดหายไปที่นี่ถ้าเรากำลังพูดถึงวิธีการฉีดรหัสไม่ระบุชื่อ ..

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

แต่คำถามที่แท้จริงคือเป้าหมายของคุณคืออะไร - คุณพยายามบังคับใช้การรักษาความปลอดภัยบางประเภทหรือไม่? หรือคุณเพียงแค่สนใจในสิ่งที่กำลังโหลด

หากคุณสนใจเรื่องความปลอดภัยชื่อไฟล์ที่นำเข้าผ่าน exec / execfile นั้นไม่สำคัญคุณควรใช้rexecซึ่งมีสิ่งต่อไปนี้:

โมดูลนี้มีคลาส RExec ซึ่งรองรับ r_eval (), r_execfile (), r_exec () และ r_import () วิธีการซึ่งเป็นรุ่นที่ จำกัด ของฟังก์ชัน Python มาตรฐาน eval (), execfile () และคำสั่ง exec และการนำเข้า รหัสที่ดำเนินการในสภาพแวดล้อมที่ จำกัด นี้จะสามารถเข้าถึงโมดูลและฟังก์ชั่นที่ถือว่าปลอดภัยเท่านั้น คุณสามารถ subclass RExec เพิ่มหรือลบความสามารถตามต้องการ

อย่างไรก็ตามหากนี่เป็นการแสวงหาทางวิชาการมากกว่านี้ .. นี่เป็นวิธีที่โง่เขลาสองสามข้อที่คุณอาจจะสามารถเจาะลึกลงไปอีกเล็กน้อย ..

ตัวอย่างสคริปต์:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

เอาท์พุต

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

แน่นอนว่านี่เป็นวิธีที่ใช้ทรัพยากรมากในการดำเนินการคุณจะต้องติดตามทุกรหัสของคุณ .. ไม่มีประสิทธิภาพมากนัก แต่ฉันคิดว่ามันเป็นวิธีการที่แปลกใหม่เพราะมันยังคงทำงานต่อไปแม้คุณจะเข้าไปลึกในรัง คุณไม่สามารถแทนที่ 'eval' แม้ว่าคุณจะสามารถแทนที่ execfile ()

หมายเหตุวิธีการนี้ครอบคลุมเฉพาะ exec / execfile ไม่ใช่ 'นำเข้า' สำหรับการโหลด 'module' ระดับสูงขึ้นคุณอาจสามารถใช้ sys.path_hooks (ความอนุเคราะห์การเขียนของ PyMOTW)

นั่นคือทั้งหมดที่ฉันมีอยู่ด้านบนของหัวของฉัน


2

นี่คือวิธีการแก้ปัญหาบางส่วนยังดีกว่าที่เผยแพร่ทั้งหมดจนถึงตอนนี้

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

ตอนนี้การทำงานทั้งหมดจะโทร แต่ถ้ามีคนใช้chdir()เพื่อเปลี่ยนไดเรกทอรีปัจจุบันนี้จะล้มเหลว

หมายเหตุ:

  • sys.argv[0]จะไม่ทำงานจะกลับมา-cถ้าคุณรันสคริปต์ด้วยpython -c "execfile('path-tester.py')"
  • ฉันเผยแพร่การทดสอบฉบับสมบูรณ์ที่https://gist.github.com/1385555และคุณสามารถปรับปรุงได้

1

สิ่งนี้ควรใช้งานได้ในกรณีส่วนใหญ่:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))

5
โซลูชันนี้ใช้ไดเรกทอรีปัจจุบันและมีการระบุไว้อย่างชัดเจนในคำถามว่าโซลูชันดังกล่าวจะล้มเหลว
59

1

หวังว่านี่จะช่วยได้: - หากคุณเรียกใช้สคริปต์ / โมดูลจากที่ใดก็ได้คุณจะสามารถเข้าถึง__file__ตัวแปรซึ่งเป็นตัวแปรโมดูลที่แสดงตำแหน่งของสคริปต์

ในทางกลับกันหากคุณใช้ล่ามคุณจะไม่สามารถเข้าถึงตัวแปรนั้นได้ซึ่งคุณจะได้รับชื่อNameErrorและos.getcwd()จะให้ไดเรกทอรีที่ไม่ถูกต้องหากคุณเรียกใช้ไฟล์จากที่อื่น

วิธีนี้ควรให้สิ่งที่คุณต้องการในทุกกรณี:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

ฉันไม่ได้ทำการทดสอบอย่างละเอียด แต่มันแก้ไขปัญหาของฉันได้


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