问题
I would like to see what is the best way to determine the current script directory in python?
I discovered that, due to the many ways of calling python code, it is hard to find a good solution.
Here are some problems:
__file__
is not defined if the script is executed withexec
,execfile
__module__
is defined only in modules
Use cases:
./myfile.py
python myfile.py
./somedir/myfile.py
python somedir/myfile.py
execfile(\'myfile.py\')
(from another script, that can be located in another directory and that can have another current directory.
I know that there is no perfect solution, but I\'m looking for the best approach that solves most of the cases.
The most used approach is os.path.dirname(os.path.abspath(__file__))
but this really doesn\'t work if you execute the script from another one with exec()
.
Warning
Any solution that uses current directory will fail, this can be different based on the way the script is called or it can be changed inside the running script.
回答1:
os.path.dirname(os.path.abspath(__file__))
is indeed the best you're going to get.
It's unusual to be executing a script with exec
/execfile
; normally you should be using the module infrastructure to load scripts. If you must use these methods, I suggest setting __file__
in the globals
you pass to the script so it can read that filename.
There's no other way to get the filename in execed code: as you note, the CWD may be in a completely different place.
回答2:
If you really want to cover the case that a script is called via execfile(...)
, you can use the inspect
module to deduce the filename (including the path). As far as I am aware, this will work for all cases you listed:
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
回答3:
#!/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())
It works on CPython, Jython, Pypy. It works if the script is executed using execfile()
(sys.argv[0]
and __file__
-based solutions would fail here). It works if the script is inside an executable zip file (/an egg). It works if the script is "imported" (PYTHONPATH=/path/to/library.zip python -mscript_to_run
) from a zip file; it returns the archive path in this case. It works if the script is compiled into a standalone executable (sys.frozen
). It works for symlinks (realpath
eliminates symbolic links). It works in an interactive interpreter; it returns the current working directory in this case.
回答4:
In Python 3.4+ you can use the simpler pathlib module:
from inspect import currentframe, getframeinfo
from pathlib import Path
filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
回答5:
The os.path...
approach was the 'done thing' in Python 2.
In Python 3, you can find directory of script as follows:
from pathlib import Path
cwd = Path(__file__).parents[0]
回答6:
Just use os.path.dirname(os.path.abspath(__file__))
and examine very carefully whether there is a real need for the case where exec
is used. It could be a sign of troubled design if you are not able to use your script as a module.
Keep in mind Zen of Python #8, and if you believe there is a good argument for a use-case where it must work for exec
, then please let us know some more details about the background of the problem.
回答7:
Would
import os
cwd = os.getcwd()
do what you want? I'm not sure what exactly you mean by the "current script directory". What would the expected output be for the use cases you gave?
回答8:
First.. a couple missing use-cases here if we're talking about ways to inject anonymous code..
code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)
But, the real question is, what is your goal - are you trying to enforce some sort of security? Or are you just interested in whats being loaded.
If you're interested in security, the filename that is being imported via exec/execfile is inconsequential - you should use rexec, which offers the following:
This module contains the RExec class, which supports r_eval(), r_execfile(), r_exec(), and r_import() methods, which are restricted versions of the standard Python functions eval(), execfile() and the exec and import statements. Code executed in this restricted environment will only have access to modules and functions that are deemed safe; you can subclass RExec add or remove capabilities as desired.
However, if this is more of an academic pursuit.. here are a couple goofy approaches that you might be able to dig a little deeper into..
Example scripts:
./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)
Output
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
Of course, this is a resource-intensive way to do it, you'd be tracing all your code.. Not very efficient. But, I think it's a novel approach since it continues to work even as you get deeper into the nest. You can't override 'eval'. Although you can override execfile().
Note, this approach only coveres exec/execfile, not 'import'. For higher level 'module' load hooking you might be able to use use sys.path_hooks (Write-up courtesy of PyMOTW).
Thats all I have off the top of my head.
回答9:
Here is a partial solution, still better than all published ones so far.
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__))
Now this works will all calls but if someone use chdir()
to change the current directory, this will also fail.
Notes:
sys.argv[0]
is not going to work, will return-c
if you execute the script withpython -c "execfile('path-tester.py')"
- I published a complete test at https://gist.github.com/1385555 and you are welcome to improve it.
回答10:
This should work in most cases:
import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
回答11:
Hopefully this helps:-
If you run a script/module from anywhere you'll be able to access the __file__
variable which is a module variable representing the location of the script.
On the other hand, if you're using the interpreter you don't have access to that variable, where you'll get a name NameError
and os.getcwd()
will give you the incorrect directory if you're running the file from somewhere else.
This solution should give you what you're looking for in all cases:
from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))
I haven't thoroughly tested it but it solved my problem.
来源:https://stackoverflow.com/questions/3718657/how-to-properly-determine-current-script-directory