【问题标题】:How do you properly determine the current script directory?如何正确确定当前脚本目录?
【发布时间】:2011-04-12 17:32:39
【问题描述】:

我想看看在 Python 中确定当前脚本目录的最佳方法是什么。

我发现,由于调用Python代码的方式很多,很难找到一个好的解决方案。

这里有一些问题:

  • 如果使用execexecfile 执行脚本,则未定义__file__
  • __module__ 仅在模块中定义

用例:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py')(来自另一个脚本,可以位于另一个目录中,并且可以有另一个当前目录。

我知道没有完美的解决方案,但我正在寻找解决大多数情况的最佳方法。

最常用的方法是os.path.dirname(os.path.abspath(__file__)),但如果您从另一个使用exec() 的脚本执行脚本,这真的不起作用。

警告

任何使用当前目录的解决方案都会失败,这可能会根据调用脚本的方式而有所不同,也可以在运行脚本中进行更改。

【问题讨论】:

  • 您能否更具体地了解您需要知道文件的来源? - 在导入文件的代码中(包含感知主机)还是在导入的文件中? (有自我意识的奴隶)
  • 如果您使用的是 python 3.4 或更高版本,请参阅 Ron Kalian 的 pathlib 解决方案:stackoverflow.com/a/48931294/1011724
  • 所以解决方案不是在代码中使用任何当前目录,而是使用一些配置文件?
  • 有趣的发现,我刚刚做了:当从 shell 执行 python myfile.py 时,它可以工作,但在 vim 中的 :!python %:!python myfile.py 都失败了 系统找不到指定的路径。这很烦人。任何人都可以评论这背后的原因和潜在的解决方法吗?

标签: python pythonpath dirname


【解决方案1】:
os.path.dirname(os.path.abspath(__file__))

确实是你能得到的最好的。

使用exec/execfile 执行脚本是不寻常的;通常你应该使用模块基础设施来加载脚本。如果您必须使用这些方法,我建议在您传递给脚本的globals 中设置__file__,以便它可以读取该文件名。

没有其他方法可以在执行代码中获取文件名:正如您所注意到的,CWD 可能位于完全不同的位置。

【讨论】:

  • 永不言败?据此:stackoverflow.com/a/18489147 回答一个跨平台的解决方案是 abspath(getsourcefile(lambda:0))?还是我还缺少其他东西?
  • 用 pathlib 更新:pathlib.Path(file).resolve()/'..'
  • os.path.dirname(os.path.abspath(__file__)) 绝对不是你会得到的最好的。这甚至都没有尝试,真的。正如@JeffEllen 建议的那样,请参阅thisthis 以获得更安全、更强大和更便携的替代方案。
【解决方案2】:

如果你真的想覆盖通过execfile(...)调用脚本的情况,你可以使用inspect模块来推断文件名(包括路径)。据我所知,这适用于您列出的所有情况:

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

【讨论】:

  • 我认为这确实是最强大的方法,但我质疑 OP 对此的声明需求。我经常看到开发人员在相对于执行模块的位置使用数据文件时会这样做,但 IMO 数据文件应该放在已知位置。
  • @Ryan LOL,如果您能够定义一个“已知位置”,它是多平台的,并且该模块也带有该模块,那就太好了。我准备打赌唯一安全的位置是脚本位置。注意,这并不意味着脚本应该写入这个位置,但是对于读取数据来说它是安全的。
  • 还是不行,在函数前调用chdir(),结果会变。从另一个目录调用 python 脚本也会改变结果,所以这不是一个好的解决方案。
  • os.path.expanduser("~") 是一种跨平台获取用户目录的方式。不幸的是,这不是 Windows 将应用程序数据粘贴到何处的最佳做法。
  • @sorin:我在运行脚本之前尝试过chdir();它产生正确的结果。我试过从另一个目录调用脚本,它也可以工作。结果和inspect.getabsfile()-based solution一样。
【解决方案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())

它适用于 CPython、Jython、Pypy。如果使用execfile()sys.argv[0] 和基于__file__ 的解决方案将在此处失败)执行脚本,则它可以工作。如果脚本在an executable zip file (/an egg) 内,它就可以工作。如果脚本是从 zip 文件中“导入”(PYTHONPATH=/path/to/library.zip python -mscript_to_run) 的,则它可以工作;在这种情况下,它返回存档路径。如果脚本被编译成独立的可执行文件(sys.frozen),它就可以工作。它适用于符号链接(realpath 消除了符号链接)。它在交互式解释器中工作;在这种情况下,它返回当前工作目录。

【讨论】:

  • 与 PyInstaller 完美配合。
  • the documentation for inspect中没有提到getabsfile(..)有什么原因吗?它出现在与该页面链接的源中。
  • @EvgeniSergeev 这可能是一个错误。它是对 getsourcefile()getfile() 的简单包装,已记录在案。
【解决方案4】:

在 Python 3.4+ 中,您可以使用更简单的 pathlib 模块:

from inspect import currentframe, getframeinfo
from pathlib import Path

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

您还可以使用__file__(如果可用)来完全避免使用inspect 模块:

from pathlib import Path
parent = Path(__file__).resolve().parent

【讨论】:

  • 在 Windows (10) 上,当我尝试使用 + 运算符将另一个字符串附加到文件路径时,出现此错误:TypeError: unsupported operand type(s) for +: 'WindowsPath' and 'str'。一种有效的解决方法是将*parent* 包装在str() 函数中。
  • @Dut A. 您应该为此使用 .joinpath()(或 / 运算符),而不是 +
  • 注意:如果你想要一个没有符号链接的绝对路径,你必须使用 resolve()。文档here
  • __file__ 不可用的任何情况下,为简单起见,您可以在使用 pathlib.Path 时将 __file__ 替换为点 ('.')。所以,当前工作目录是directory = Path('.').resolve()parent = Path('..')resolve()
【解决方案5】:

os.path... 方法是 Python 2 中“完成的事情”。

在 Python 3 中,您可以找到脚本目录如下:

from pathlib import Path
script_path = Path(__file__).parent

【讨论】:

  • 或者只是Path(__file__).parent。但是cwd 用词不当,它不是当前工作目录,而是文件所在目录。它们可能相同,但通常情况并非如此。
  • 注意:这只会返回一个相对路径。对于绝对路径,您必须使用 resolve()。文档here
【解决方案6】:

import os
cwd = os.getcwd()

做你想做的事?我不确定“当前脚本目录”到底是什么意思。您给出的用例的预期输出是什么?

【讨论】:

  • 这无济于事。我相信@bogdan 正在寻找位于调用堆栈顶部的脚本目录。即在他/她的所有情况下,它应该打印“myfile.py”所在的目录。然而您的方法只会打印调用exec('myfile.py') 的文件的目录,与__file__sys.argv[0] 相同。
  • 是的,这是有道理的。我只是想确保@bogdan 没有忽略一些简单的事情,而且我无法准确说出他们想要什么。
【解决方案7】:

只需使用os.path.dirname(os.path.abspath(__file__)) 并仔细检查是否真的需要使用exec 的情况。如果您无法将脚本用作模块,这可能是设计问题的标志。

请记住 Python #8 之禅,如果您认为有一个很好的论据可以证明它必须适用于 exec,那么请告诉我们更多详细信息关于问题的背景。

【讨论】:

  • 如果您不使用 exec() 运行,您将失去调试器上下文。此外 exec() 应该比启动一个新进程要快得多。
  • @sorin 这不是 exec 与开始新进程的问题,所以这是一个稻草人的论点。这是 exec 与使用导入或函数调用的问题。
【解决方案8】:

注意:这个答案现在是一个包

https://github.com/heetbeet/locate

$ pip install locate

$ python
>>> from locate import this_dir
>>> print(this_dir())
C:/Users/simon

对于.py 脚本以及交互使用:

我经常使用我的脚本目录(用于访问与它们一起存储的文件),但我也经常在交互式 shell 中运行这些脚本以进行调试。我将__dirpath__ 定义为:

  • 运行或导入.py 文件时,文件的基目录。这始终是正确的路径。
  • 运行.ipyn 笔记本时,当前工作目录。这始终是正确的路径,因为 Jupyter 将工作目录设置为 .ipynb 基本目录。
  • 在 REPL 中运行时,当前工作目录。嗯,当代码从文件中分离出来时,实际的“正确路径”是什么?相反,让您有责任在调用 REPL 之前更改为“正确路径”。

Python 3.4(及以上):

from pathlib import Path
__dirpath__ = Path(globals().get("__file__", "./_")).absolute().parent

Python 2(及以上):

import os
__dirpath__ = os.path.dirname(os.path.abspath(globals().get("__file__", "./_")))

解释:

  • globals() 将所有全局变量作为字典返回。
  • 如果globals() 中存在.get("__file__", "./_"),则返回键"__file__" 中的值,否则返回提供的默认值"./_"
  • 其余代码只是将__file__(或"./_")扩展为绝对文件路径,然后返回文件路径的基目录。

【讨论】:

    【解决方案9】:

    首先.. 如果我们正在讨论注入匿名代码的方法,这里缺少几个用例..

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

    但是,真正的问题是,您的目标是什么 - 您是否要强制执行某种安全措施?或者您只是对正在加载的内容感兴趣。

    如果您对security 感兴趣,通过 exec/execfile 导入的文件名无关紧要 - 您应该使用 rexec,它提供以下内容:

    此模块包含 RExec 类, 支持 r_eval(), r_execfile(), r_exec() 和 r_import() 方法,其中 是标准的受限版本 Python 函数 eval()、execfile() 和 exec 和 import 语句。代码 在这个受限的环境中执行 只能访问模块和 被认为是安全的功能;你可以 子类 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
    

    当然,这是一种资源密集型的方式,您需要跟踪 你所有的代码..不是很有效。但是,我认为这是一种新颖的方法 因为即使您深入巢穴,它也会继续工作。 您不能覆盖“评估”。虽然您可以覆盖 execfile()。

    请注意,这种方法仅涵盖 exec/execfile,而不是“导入”。 对于更高级别的“模块”负载挂钩,您可以使用 sys.path_hooks(由 PyMOTW 提供)。

    这就是我的全部想法。

    【讨论】:

      【解决方案10】:

      这是一个部分解决方案,仍然比迄今为止所有已发布的解决方案都要好。

      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] 不起作用,如果您使用 python -c "execfile('path-tester.py')" 执行脚本,将返回 -c
      • 我在https://gist.github.com/1385555 发布了一个完整的测试,欢迎您改进它。

      【讨论】:

        【解决方案11】:

        这应该适用于大多数情况:

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

        【讨论】:

        • 此解决方案使用当前目录,并且在问题中明确指出此类解决方案将失败。
        【解决方案12】:

        希望这会有所帮助:- 如果您从任何地方运行脚本/模块,您将能够访问__file__ 变量,该变量是表示脚本位置的模块变量。

        另一方面,如果您使用的是解释器,则您无权访问该变量,如果您正在运行,您将获得一个名称 NameErroros.getcwd() 会给您错误的目录其他地方的文件。

        This 解决方案应该在所有情况下都能为您提供所需的内容:

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

        我还没有彻底测试它,但它解决了我的问题。

        【讨论】:

        • 这将给出文件,而不是目录
        【解决方案13】:

        如果__file__ 可用:

        # -- script1.py --
        import os
        file_path = os.path.abspath(__file__)
        print(os.path.dirname(file_path))
        

        对于我们希望能够从解释器运行命令或获取运行脚本的位置的路径的用户:

        # -- script2.py --
        import os
        print(os.path.abspath(''))
        

        这适用于解释器。 但是当在脚本中运行(或导入)时,它会给出所在位置的路径 您从运行脚本,而不是包含目录的路径 带有打印的脚本。

        示例:

        如果你的目录结构是

        test_dir (in the home dir)
        ├── main.py
        └── test_subdir
            ├── script1.py
            └── script2.py
        

        # -- main.py --
        import script1.py
        import script2.py
        

        输出是:

        ~/test_dir/test_subdir
        ~/test_dir
        

        【讨论】:

          【解决方案14】:

          要获取包含当前脚本的目录的绝对路径,您可以使用:

          from pathlib import Path
          absDir = Path(__file__).parent.resolve()
          

          请注意.resolve() 调用是必需的,因为那是绝对路径。没有resolve(),你会得到类似'.'的东西。

          此解决方案使用pathlib,它是自 v3.4 (2014) 以来 Python 标准库的一部分。与使用os 的其他解决方案相比,这更可取。

          pathlib 官方文档有一个有用的表格,将旧的 os 函数映射到新的函数:https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module

          【讨论】:

            【解决方案15】:

            由于以前的答案要求您导入一些模块,我想我会写一个不需要的答案。如果您不想导入任何内容,请使用下面的代码。

            dir = '/'.join(__file__.split('/')[:-1])
            print(dir)
            

            如果脚本在/path/to/script.py 上,那么这将打印/path/to。请注意,这将在终端上引发错误,因为没有执行任何文件。这基本上从__file__ 解析目录,删除它的最后一部分。在这种情况下,/script.py 被删除以生成输出 /path/to

            【讨论】:

              【解决方案16】:
              print(__import__("pathlib").Path(__file__).parent)
              

              【讨论】:

              • 添加更多 cmets,解释为什么这可以回答问题以及您的方法与所有其他答案有何不同
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-08-13
              • 1970-01-01
              • 2012-04-11
              • 1970-01-01
              相关资源
              最近更新 更多