【问题标题】:Get name of current script in Python在 Python 中获取当前脚本的名称
【发布时间】:2011-05-08 08:48:39
【问题描述】:

我正在尝试获取当前正在运行的 Python 脚本的名称。

我有一个名为 foo.py 的脚本,我想做这样的事情来获取脚本名称:

print(Scriptname)

【问题讨论】:

  • Python 2 已过时。请改用 print(var)。
  • Elia Iliashenko,“2010 年 11 月 11 日 9:32 提问”
  • Path(__file__).name 自 Python3.4+ 起。

标签: python scripting basename


【解决方案1】:

为了完整起见,我认为值得总结各种可能的结果并为每个结果的确切行为提供参考。

答案由四部分组成:

  1. 返回当前执行脚本的完整路径的不同方法列表。

  2. 关于处理相对路径的警告。

  3. 关于处理符号链接的建议。

  4. 介绍一些可用于从完整文件路径中提取实际文件名(带或不带后缀)的方法。


提取完整文件路径

  • __file__是当前正在执行的文件,详见official documentation

    __file__ 是加载模块的文件的路径名,如果它是从文件加载的。某些类型的模块可能缺少__file__ 属性,例如静态链接到解释器的C 模块;对于从共享库动态加载的扩展模块,它是共享库文件的路径名。

    From Python3.4 以后,根据issue 18416__file__ 始终是绝对路径,除非当前执行的文件是直接执行的脚本(不是通过带有-m 命令行选项的解释器)使用相对路径。

  • __main__.__file__(需要导入__main__)只是访问main module 的上述__file__ 属性,例如从命令行调用的脚本。

    From Python3.9 以后,根据issue 20443__main__ 模块的__file__ 属性变为绝对路径,而不是相对路径。

  • sys.argv[0](需要导入sys)是从命令行调用的脚本名称,可能是绝对路径,详见official documentation

    argv[0] 是脚本名称(它是否为完整路径名取决于操作系统)。如果使用解释器的-c 命令行选项执行命令,则argv[0] 设置为字符串'-c'。如果没有将脚本名称传递给 Python 解释器,argv[0] 是空字符串。

    another answer to this question 中所述,通过py2exePyInstaller 等工具转换为独立可执行程序的Python 脚本在使用此方法时可能不会显示所需的结果(即sys.argv[0] 将保存可执行文件的名称,而不是该可执行文件中的主 Python 文件的名称)。

  • 如果上述选项似乎都不起作用,可能是由于非典型的执行过程或不规则的导入操作,inspect module 可能有用。特别是,调用 inspect.stack()[-1][1] 应该可以工作,尽管在没有 Python 堆栈框架的实现中运行时会引发异常。

  • Python3.6 开始,如another answer to this question 中所述,可以安装一个外部开源库lib_programname,该库专为提供此问题的完整解决方案而定制。

    该库遍历上面列出的所有方法,直到返回有效路径。如果它们都失败,则会引发异常。它还尝试解决各种陷阱,例如通过pytest 框架或pydoc 模块进行调用。

    import lib_programname
    # this returns the fully resolved path to the launched python program
    path_to_program = lib_programname.get_path_executed_script()  # type: pathlib.Path
    

处理相对路径

在处理碰巧返回相对路径的方法时,可能很容易调用各种路径操作函数,例如 os.path.abspath(...)os.path.realpath(...) 以提取完整或真实路径。

但是,这些方法依赖于当前路径来导出完整路径。因此,如果程序首先更改当前工作目录,例如通过os.chdir(...),然后才调用这些方法,它们将返回错误的路径。


处理符号链接

如果当前脚本是符号链接,那么以上所有内容都将返回符号链接的路径而不是真实文件的路径,并且应该调用 os.path.realpath(...) 以提取后者。


提取实际文件名的进一步操作

os.path.basename(...) 可以在上述任何一个上调用以提取实际文件名,os.path.splitext(...) 可以在实际文件名上调用以截断其后缀,如 os.path.splitext(os.path.basename(...))

Python 3.4 开始,根据PEP 428pathlib modulePurePath class 也可以用于上述任何一个。具体来说,pathlib.PurePath(...).name 提取实际文件名,pathlib.PurePath(...).stem 提取不带后缀的实际文件名。

【讨论】:

    【解决方案2】:
    import sys
    print(sys.argv[0])
    

    这将为python foo.py 打印foo.py,为python dir/foo.py 打印dir/foo.py,等等。这是python 的第一个参数。 (请注意,在 py2exe 之后它将是 foo.exe。)

    【讨论】:

    • @DenisMalinovsky:定义“行不通”。如果您调用python linkfile.py,其中linkfile.pyrealfile.py 的符号链接,sys.argv[0] 将是'linkfile.py',这可能是也可能不是您想要的;这当然是我期望的__file__ 是一样的:它将是 linkfile.py。如果您想从'linkfile.py' 中找到'realfile.py',请尝试os.path.realpath('linkfile.py')
    • +1 因为它 (a) 更简洁一些并且 (b) 仍然可以在模块中工作(其中 file 变量将是模块文件,而不是执行的文件) .
    • 这个答案很好,因为它也适用于 IDLE。注意,要获取文件名,您可以编写 os.path.basename(sys.argv[0])
    • 什么!!!你试过这个吗?恰恰相反。提问者询问正在运行的 python 脚本的名称 - 而不是当前正在执行的 python 文件。假设您有一个脚本,当发生错误时,它会打印脚本名称以及允许的参数。你把它放在一个函数中,使用这两种技术之一。在某个时候,您决定将函数移至外部库。您要打印正在运行的主脚本的名称,还是正在执行的库文件的名称?
    • 当您想打印使用信息时,这是正确的解决方案,因为这正是程序的调用方式,而 file 则不是。
    【解决方案3】:

    您可以在不导入操作系统或其他库的情况下执行此操作。

    如果要获取当前python脚本的路径,使用:__file__

    如果您只想获取不带 .py 扩展名的文件名,请使用:

    __file__.rsplit("/", 1)[1].split('.')[0]
    

    【讨论】:

      【解决方案4】:

      所有答案都很棒,但有些问题您可能第一眼看不到。

      让我们定义我们想要的 - 我们想要执行的脚本的名称,而不是当前模块的名称 - 所以 __file__ 仅在它用于执行的脚本时才有效,而不是在导入的模块中。 sys.argv 也是有问题的——如果你的程序被 pytest 调用了怎么办?还是 pydoc 跑步者?或者如果它是由 uwsgi 调用的?

      并且 - 还有第三种获取脚本名称的方法,我在答案中没有看到 - 你可以检查堆栈。

      另一个问题是,您(或其他程序)可以篡改sys.argv__main__.__file__ - 它可能存在,也可能不存在。它可能有效,也可能无效。至少您可以检查脚本(所需的结果)是否存在!

      lib_programname 库正是这样做的:

      • 检查__main__是否存在
      • 检查__main__.__file__是否存在
      • 确实给__main__.__file__ 一个有效的结果(该脚本存在吗?)
      • 如果不是:检查 sys.argv:
      • sys.argv 中是否有 pytest、docrunner 等? --> 如果是,忽略它
      • 我们可以在这里得到一个有效的结果吗?
      • 如果不是:检查堆栈并可能从那里获取结果
      • 如果堆栈也没有给出有效结果,则抛出异常。

      通过这种方式,到目前为止,我的解决方案正在使用 setup.py testuwsgipytestpycharm pytestpycharm docrunner (doctest)dreampieeclipse

      还有一篇来自 Dough Hellman 的关于该问题的精彩博客文章,“Determining the Name of a Process from Python”

      顺便说一句,它会在 python 3.9 中再次改变:ma​​in 模块的 file 属性变成了绝对路径,而不是相对路径。在当前目录被 os.chdir() 更改后,这些路径现在仍然有效

      所以我宁愿处理一个小模块,而不是浏览我的代码库,如果它应该更改的话......


      免责声明:我是 lib_programname 库的作者。

      【讨论】:

      • 在您的允许下,我已对您的答案投了赞成票,并在my answer 中引用了它和图书馆,这有望增加他们的曝光率。
      • @Yoel:谢谢你。如果该库有任何问题 - 请告诉我,然后我会快速更新
      【解决方案5】:

      从 Python 3.5 开始,您可以简单地执行以下操作:

      from pathlib import Path
      Path(__file__).stem
      

      在此处查看更多信息:https://docs.python.org/3.5/library/pathlib.html#pathlib.PurePath.stem

      例如,我的用户目录下有一个名为 test.py 的文件,其中包含:

      from pathlib import Path
      
      print(Path(__file__).stem)
      print(__file__)
      

      运行此输出:

      >>> python3.6 test.py
      test
      test.py
      

      【讨论】:

        【解决方案6】:

        对于现代 Python 版本 (3.4+),Path(__file__).name 应该更惯用。此外,Path(__file__).stem 为您提供不带 .py 扩展名的脚本名称。

        【讨论】:

        • NameError: name 'Path' is not defined
        • 你应该先from pathlib import Path
        • “现代”是指 Python 3.x ?
        • @einpoklum pathlib 是在 Python 3.4 中引入的,因此它应该从 Python 3.4 开始工作。
        【解决方案7】:

        您可以使用__file__ 来获取当前文件的名称。在主模块中使用时,这是最初调用的脚本的名称。

        如果您想省略目录部分(可能存在),您可以使用os.path.basename(__file__)

        【讨论】:

        • Python 3.2: "Exception NameError: NameError("global name '__file__' is not defined",)"
        • @sdaau: __file__ 没有在交互式解释器中定义,因为它在那里毫无意义。它由导入实现设置,因此如果您使用非标准导入机制,它也可能未设置。
        • 至少对于 Python 2.7,我相信需要 import os 才能工作。我会将其添加到答案中。
        • @cdunn2001: import osimport os.path 完全等价。
        • @sven-marnach:哦,you're right。多年来我一直在做错事!
        【解决方案8】:

        我们可以试试这个来获取当前脚本名称,不带扩展名。

        import os
        
        script_name = os.path.splitext(os.path.basename(__file__))[0]
        

        【讨论】:

          【解决方案9】:

          由于 OP 要求我提供当前脚本文件的名称

          import os
          os.path.split(sys.argv[0])[1]
          

          【讨论】:

            【解决方案10】:

            如果您正在进行不寻常的导入(例如,它是一个选项文件),请尝试:

            import inspect
            print (inspect.getfile(inspect.currentframe()))
            

            请注意,这将返回文件的绝对路径。

            【讨论】:

            • 这是在 platform.io 构建系统中从 env.SConscript 调用当前文件时最终起作用的方法。
            【解决方案11】:

            以上答案很好。但我发现使用上述结果这种方法更有效。
            这导致实际的脚本文件名不是路径。

            import sys    
            import os    
            file_name =  os.path.basename(sys.argv[0])
            

            【讨论】:

            • 我也喜欢拆分扩展,所以我使用:os.path.splitext(os.path.basename(sys.argv[0]))[0]
            • 对我来说几年后使用 Python 3.8 这会打印 ipykernel_launcher.py 而不是文件名。
            • 你在用jupyter notebook吗?
            【解决方案12】:

            请注意,__file__ 将给出此代码所在的文件,该文件可以导入,并且与正在解释的主文件不同。要获取主文件,可以使用特殊的__main__ 模块:

            import __main__ as main
            print(main.__file__)
            

            请注意,__main__.__file__ 在 Python 2.7 中有效,但在 3.2 中无效,因此请使用上述 import-as 语法以使其可移植。

            【讨论】:

            • 这在很多情况下都有效,但在我使用来自R 语言的rPython 包时无效。这一定是一个太难处理的例外情况。
            • 确实,rPython 包嵌入了 python 解释器,这意味着没有像 python 单独运行时那样的“主”文件(无论何时 python 运行,你都会发现相同的行为)嵌入)。它确实在内部导入__main__,用于在Rpython 之间传递变量,因此在调用其他任何内容之前将其设置为__main__.__file__ 相对容易,但我什至不确定会是什么在这种情况下是一个合适的值。
            【解决方案13】:

            试试这个:

            print __file__
            

            【讨论】:

              猜你喜欢
              • 2017-09-09
              • 2022-06-16
              • 1970-01-01
              • 2018-01-14
              • 2011-10-15
              • 1970-01-01
              • 2011-05-12
              • 2021-08-24
              • 2020-01-11
              相关资源
              最近更新 更多