Làm thế nào để xác định đúng thư mục script hiện tại?


260

Tôi muốn xem cách tốt nhất để xác định thư mục script hiện tại trong python là gì?

Tôi phát hiện ra rằng, do nhiều cách gọi mã python, thật khó để tìm ra một giải pháp tốt.

Đây là một số vấn đề:

  • __file__không được xác định nếu tập lệnh được thực thi với exec,execfile
  • __module__ chỉ được định nghĩa trong các mô-đun

Trường hợp sử dụng:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (từ một tập lệnh khác, có thể được đặt trong một thư mục khác và có thể có một thư mục hiện tại khác.

Tôi biết rằng không có giải pháp hoàn hảo, nhưng tôi đang tìm cách tiếp cận tốt nhất để giải quyết hầu hết các trường hợp.

Cách tiếp cận được sử dụng nhiều nhất là os.path.dirname(os.path.abspath(__file__))nhưng cách này thực sự không hiệu quả nếu bạn thực thi tập lệnh từ một cách khác exec().

Cảnh báo

Bất kỳ giải pháp nào sử dụng thư mục hiện tại sẽ thất bại, điều này có thể khác nhau dựa trên cách tập lệnh được gọi hoặc nó có thể được thay đổi bên trong tập lệnh đang chạy.


1
Bạn có thể cụ thể hơn nơi bạn cần biết tập tin đến từ đâu? - trong mã đang nhập tệp (bao gồm máy chủ nhận biết) hoặc trong tệp được nhập? (nô lệ tự nhận thức)
tổng

3
Xem pathlibgiải pháp của Ron Kalian nếu bạn đang sử dụng python 3,4 trở lên: stackoverflow.com/a/48931294/1011724
Dan

Vì vậy, giải pháp là KHÔNG sử dụng bất kỳ thư mục hiện tại nào trong mã, mà là sử dụng một số tệp cấu hình?
ZhaoGang

Khám phá thú vị, tôi vừa thực hiện: Khi thực hiện python myfile.pytừ shell, nó hoạt động, nhưng cả hai :!python %:!python myfile.pytừ bên trong vim đều thất bại với Hệ thống không thể tìm thấy đường dẫn được chỉ định. Điều này khá khó chịu. Bất cứ ai có thể nhận xét về lý do đằng sau điều này và giải pháp tiềm năng?
inVader

Câu trả lời:


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

thực sự là tốt nhất bạn sẽ nhận được.

Thật bất thường khi thực thi một tập lệnh với exec/ execfile; thông thường bạn nên sử dụng cơ sở hạ tầng mô-đun để tải tập lệnh. Nếu bạn phải sử dụng các phương thức này, tôi khuyên bạn nên thiết lập __file__trong globalsbạn chuyển đến tập lệnh để nó có thể đọc tên tệp đó.

Không có cách nào khác để lấy tên tệp trong mã được thực thi: như bạn lưu ý, CWD có thể ở một nơi hoàn toàn khác.


2
Không bao giờ nói không bao giờ? Theo điều này: stackoverflow.com/a/18489147 trả lời một giải pháp đa nền tảng là abspath (gotourcefile (lambda: 0))? Hoặc có điều gì khác tôi đang thiếu?
Jeff Ellen

131

Nếu bạn thực sự muốn bao gồm trường hợp một tập lệnh được gọi thông qua execfile(...), bạn có thể sử dụng inspectmô-đun để suy ra tên tệp (bao gồm cả đường dẫn). Theo như tôi biết, điều này sẽ hoạt động cho tất cả các trường hợp bạn liệt kê:

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

4
Tôi nghĩ rằng đây thực sự là phương pháp mạnh mẽ nhất, nhưng tôi nghi ngờ nhu cầu đã nêu của OP về việc này. Tôi thường thấy các nhà phát triển làm điều này khi họ đang sử dụng các tệp dữ liệu ở các vị trí liên quan đến mô đun thực thi, nhưng các tệp dữ liệu IMO nên được đặt ở một vị trí đã biết.
Ryan Ginstrom

14
@Ryan LOL, nếu thật tuyệt vời nếu bạn có thể xác định một "vị trí đã biết" là đa nền tảng và cũng đi kèm với mô-đun. Tôi sẵn sàng đặt cược rằng vị trí an toàn duy nhất là vị trí tập lệnh. Lưu ý, điều này không phải là kịch bản nên ghi vào vị trí này, nhưng để đọc dữ liệu thì nó an toàn.
sorin

1
Tuy nhiên, giải pháp không tốt, chỉ cần thử gọi chdir()trước chức năng, nó sẽ thay đổi kết quả. Ngoài ra, việc gọi tập lệnh python từ một thư mục khác sẽ thay đổi kết quả, vì vậy đó không phải là một giải pháp tốt.
sorin

2
os.path.expanduser("~")là một cách đa nền tảng để có được thư mục của người dùng. Thật không may, đó không phải là cách tốt nhất để Windows gắn dữ liệu ứng dụng.
Ryan Ginstrom

6
@sorin: Tôi đã thử chdir()trước khi chạy tập lệnh; nó tạo ra kết quả chính xác Tôi đã thử gọi kịch bản từ một thư mục khác và nó cũng hoạt động. Các kết quả là giống như inspect.getabsfile()giải pháp dựa trên .
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())

Nó hoạt động trên CPython, Jython, Pypy. Nó hoạt động nếu tập lệnh được thực thi bằng cách sử dụng execfile()( sys.argv[0]__file__các giải pháp dựa trên sẽ thất bại ở đây). Nó hoạt động nếu tập lệnh nằm trong tệp zip thực thi (/ một quả trứng) . Nó hoạt động nếu tập lệnh được "nhập" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) từ tệp zip; nó trả về đường dẫn lưu trữ trong trường hợp này. Nó hoạt động nếu tập lệnh được biên dịch thành tệp thực thi độc lập ( sys.frozen). Nó hoạt động cho các liên kết realpathtượng trưng ( loại bỏ các liên kết tượng trưng). Nó hoạt động trong một trình thông dịch tương tác; nó trả về thư mục làm việc hiện tại trong trường hợp này.


Hoạt động hoàn toàn tốt với PyInstaller.
gabious

1
Có bất kỳ lý do tại sao getabsfile(..)không được đề cập trong tài liệu choinspect ? Nó xuất hiện trong nguồn được liên kết ngoài trang đó.
Evgeni Sergeev

@EvgeniSergeev nó có thể là một lỗi. Nó là một trình bao bọc đơn giản xung quanh getsourcefile(), getfile()được ghi lại.
jfs

24

Trong Python 3.4+, bạn có thể sử dụng pathlibmô-đun đơn giản hơn :

from inspect import currentframe, getframeinfo
from pathlib import Path

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

2
Đơn giản tuyệt vời!
Cometsong

3
Bạn có thể có thể sử dụng Path(__file__)(không cần inspectmô-đun).
Peque

@Peque làm điều đó tạo ra một đường dẫn bao gồm tên của tệp hiện tại, không phải thư mục mẹ. Nếu tôi đang cố lấy thư mục tập lệnh hiện tại để trỏ đến một tệp trong cùng thư mục, ví dụ như mong đợi tải tệp cấu hình trong cùng thư mục với tập lệnh, Path(__file__)sẽ đưa ra /path/to/script/currentscript.pykhi OP muốn lấy/path/to/script/
Davos

8
Ồ tôi hiểu lầm rồi, ý bạn là tránh mô-đun kiểm tra và chỉ sử dụng thứ gì đó như thế parent = Path(__file__).resolve().parent đẹp hơn nhiều.
Davos

3
@Dut A. Bạn nên sử dụng .joinpath()(hoặc /toán tử) cho việc này, không +.
Eugene Yarmash

13

Cách os.path...tiếp cận là "việc đã hoàn thành" trong Python 2.

Trong Python 3, bạn có thể tìm thấy thư mục của tập lệnh như sau:

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

11
Hoặc chỉ Path(__file__).parent. Nhưng cwdlà một cách viết sai, đó không phải là thư mục làm việc hiện tại , mà là thư mục của tệp . Chúng có thể giống nhau, nhưng đó không phải là trường hợp thường.
Nuno André

5

Chỉ cần sử dụng os.path.dirname(os.path.abspath(__file__))và kiểm tra rất cẩn thận xem có thực sự cần thiết cho trường hợp execđược sử dụng không. Nó có thể là một dấu hiệu của thiết kế gặp rắc rối nếu bạn không thể sử dụng tập lệnh của mình làm mô-đun.

Hãy ghi nhớ Zen của Python # 8 và nếu bạn tin rằng có một lý lẽ tốt cho trường hợp sử dụng mà nó phải hoạt động exec, thì vui lòng cho chúng tôi biết thêm một số chi tiết về nền tảng của vấn đề.


2
Nếu bạn không chạy với exec (), bạn sẽ mất bối cảnh trình gỡ lỗi. Ngoài ra exec () được cho là nhanh hơn đáng kể so với bắt đầu một quy trình mới.
sorin

@sorin Đây không phải là câu hỏi của người thực hiện so với việc bắt đầu một quy trình mới, vì vậy đó là một cuộc tranh cãi của người rơm. Đó là một câu hỏi của exec vs sử dụng một cuộc gọi nhập khẩu hoặc chức năng.
wim

4

Sẽ

import os
cwd = os.getcwd()

làm những gì bạn muốn? Tôi không chắc chính xác ý bạn là gì bởi "thư mục tập lệnh hiện tại". Sản lượng dự kiến ​​sẽ là gì cho các trường hợp sử dụng bạn đã đưa ra?


3
Nó sẽ không giúp đỡ. Tôi tin rằng @bogdan đang tìm thư mục cho tập lệnh nằm ở đầu ngăn xếp cuộc gọi. tức là trong tất cả các trường hợp của anh ấy / cô ấy, nó nên in thư mục chứa 'myfile.py'. Tuy nhiên, phương pháp của bạn sẽ chỉ in thư mục của tệp gọi exec('myfile.py'), giống như __file__sys.argv[0].
Zhang18

Vâng, điều đó có ý nghĩa. Tôi chỉ muốn chắc chắn rằng @bogdan không nhìn ra thứ gì đó đơn giản và tôi không thể nói chính xác những gì họ muốn.
Will McCutchen

3

Đầu tiên .. một vài trường hợp sử dụng bị thiếu ở đây nếu chúng ta đang nói về cách tiêm mã ẩn danh ..

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

Nhưng, câu hỏi thực sự là, mục tiêu của bạn là gì - bạn đang cố gắng thực thi một số loại bảo mật? Hoặc bạn chỉ quan tâm đến những gì đang được tải.

Nếu bạn quan tâm đến bảo mật , tên tệp đang được nhập qua exec / execfile là không quan trọng - bạn nên sử dụng rexec , cung cấp thông tin sau:

Mô-đun này chứa lớp RExec, hỗ trợ các phương thức r_eval (), r_execfile (), r_exec () và r_import (), là các phiên bản giới hạn của các hàm Python tiêu chuẩn eval (), execfile () và các câu lệnh exec và import. Mã được thực thi trong môi trường bị hạn chế này sẽ chỉ có quyền truy cập vào các mô-đun và chức năng được coi là an toàn; bạn có thể phân lớp RExec thêm hoặc loại bỏ các khả năng như mong muốn.

Tuy nhiên, nếu đây là một mục đích học thuật nhiều hơn .. đây là một vài cách tiếp cận ngớ ngẩn mà bạn có thể đào sâu hơn một chút vào ..

Kịch bản ví dụ:

./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)

Đầu ra

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

Tất nhiên, đây là một cách tốn nhiều tài nguyên để thực hiện, bạn sẽ truy tìm tất cả mã của mình .. Không hiệu quả lắm. Nhưng, tôi nghĩ rằng đó là một cách tiếp cận mới lạ vì nó tiếp tục hoạt động ngay cả khi bạn tiến sâu hơn vào tổ. Bạn không thể ghi đè 'eval'. Mặc dù bạn có thể ghi đè execfile ().

Lưu ý, cách tiếp cận này chỉ bao gồm exec / execfile, không phải 'nhập'. Để kết nối tải 'mô-đun' cấp cao hơn, bạn có thể sử dụng sys.path_hooks (Lịch sự viết lên của PyMOTW).

Đó là tất cả những gì tôi có trên đỉnh đầu.


2

Đây là một giải pháp một phần, vẫn tốt hơn tất cả những giải pháp được công bố cho đến nay.

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__))

Bây giờ công việc này sẽ tất cả các cuộc gọi nhưng nếu ai đó sử dụng chdir() để thay đổi thư mục hiện tại, điều này cũng sẽ thất bại.

Ghi chú:

  • sys.argv[0]sẽ không hoạt động, sẽ trở lại -cnếu bạn thực thi tập lệnh vớipython -c "execfile('path-tester.py')"
  • Tôi đã xuất bản một bài kiểm tra hoàn chỉnh tại https://gist.github.com/1385555 và bạn được chào đón để cải thiện nó.

1

Điều này sẽ làm việc trong hầu hết các trường hợp:

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

5
Giải pháp này sử dụng thư mục hiện tại và được nêu rõ ràng trong câu hỏi rằng giải pháp đó sẽ thất bại.
skyking

1

Hy vọng rằng điều này sẽ giúp: - Nếu bạn chạy tập lệnh / mô-đun từ bất kỳ đâu, bạn sẽ có thể truy cập vào __file__biến đó là biến mô-đun đại diện cho vị trí của tập lệnh.

Mặt khác, nếu bạn đang sử dụng trình thông dịch, bạn không có quyền truy cập vào biến đó, nơi bạn sẽ nhận được tên NameErroros.getcwd()sẽ cung cấp cho bạn thư mục không chính xác nếu bạn đang chạy tệp từ nơi khác.

Giải pháp này sẽ cung cấp cho bạn những gì bạn đang tìm kiếm trong mọi trường hợp:

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

Tôi đã không kiểm tra kỹ lưỡng nhưng nó đã giải quyết vấn đề của tôi.


Điều này sẽ cung cấp cho tập tin, không phải thư mục
Shital Shah
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.