【问题标题】:Making Python run a few lines before my script让 Python 在我的脚本之前运行几行
【发布时间】:2015-11-18 00:04:12
【问题描述】:

我需要运行一个脚本foo.py,但我还需要在foo.py 中的代码之前插入一些调试行以运行。目前我只是将这些行放在foo.py 中,我小心不要将其提交给 Git,但我不喜欢这个解决方案。

我想要的是一个单独的文件bar.py,我不会提交给 Git。然后我想跑:

python /somewhere/bar.py /somewhere_else/foo.py

我想要做的是首先在bar.py 中运行几行代码,然后将foo.py 运行为__main__。应该和bar.py行在同一个进程中,否则调试行无济于事。

有没有办法让bar.py 这样做?

有人建议这样做:

import imp
import sys

# Debugging code here

fp, pathname, description = imp.find_module(sys.argv[1])
imp.load_module('__main__', fp, pathname, description)

问题在于,因为它使用导入机制,所以我需要与 foo.py 位于同一文件夹中才能运行它。我不想要那个。我想简单地输入foo.py 的完整路径。

另外:该解决方案还需要使用 .pyc 文件。

【问题讨论】:

  • 您可以创建一个别名并将其通过管道传输到您的实际通话中吗?例如。别名pytester=python -m bar.py,然后调用pytester | python foo.py。您需要获取 foo.py 才能接受 sys.stdin 上的数据。 编辑: 甚至完全跳过别名,直接调用python /path/to/bar.py | python /path/to/foo.py
  • 在导入之前不能简单地chdir到foo的路径吗?
  • @GerardvanHelden 不是一个干净的解决方案。我可以想到一些可能会导致的实际问题,因为脚本需要位于特定文件夹中。
  • 如果这只是为了调试,就用这个调试:wiki.python.org/moin/PythonDebuggingTools
  • @AleksanderLidtke 如果我发现自己只需要一张餐巾纸和一个线轴就可以越狱,我一定会联系你寻求建议 :) 不幸的是,你的新 hack 行不通,因为我有一些函数定义,不能放在一行中。

标签: python python-import


【解决方案1】:

Python 有一种在启动时运行代码的机制; site 模块。

"This module is automatically imported during initialization."

站点模块将在导入__main__ 之前尝试导入名为sitecustomize 的模块。 如果您的环境要求,它还会尝试导入名为 usercustomize 的模块。

例如,您可以将 sitecustomize.py 文件放在包含以下内容的站点包文件夹中:

import imp

import os

if 'MY_STARTUP_FILE' in os.environ:
    try:
        file_path = os.environ['MY_STARTUP_FILE']
        folder, file_name = os.path.split(file_path)
        module_name, _ = os.path.splitext(file_name)
        fp, pathname, description = imp.find_module(module_name, [folder])
    except Exception as e:
        # Broad exception handling since sitecustomize exceptions are ignored
        print "There was a problem finding startup file", file_path
        print repr(e)
        exit()

    try:
        imp.load_module(module_name, fp, pathname, description)
    except Exception as e:
        print "There was a problem loading startup file: ", file_path
        print repr(e)
        exit()
    finally:
        # "the caller is responsible for closing the file argument" from imp docs
        if fp:
            fp.close()

然后你可以像这样运行你的脚本:

MY_STARTUP_FILE=/somewhere/bar.py python /somewhere_else/foo.py
  • 您可以在 foo.py 之前运行任何脚本,而无需添加代码以重新导入 __main__
  • 运行export MY_STARTUP_FILE=/somewhere/bar.py,不需要每次都引用

【讨论】:

    【解决方案2】:

    如果文件是.py,你可以使用execfile();如果文件是.pyc,你可以使用uncompyle2

    假设您的文件结构如下:

    test|-- foo.py
        |-- bar
             |--bar.py   
    

    foo.py

    import sys
    
    a = 1
    print ('debugging...')
    
    # run the other file
    if sys.argv[1].endswith('.py'): # if .py run right away
        execfile(sys.argv[1], globals(), locals())
    elif sys.argv[1].endswith('.pyc'): # if .pyc, first uncompyle, then run
        import uncompyle2
        from StringIO import StringIO
        f = StringIO()
        uncompyle2.uncompyle_file(sys.argv[1], f)
        f.seek(0)
        exec(f.read(), globals(), locals())
    

    bar.py

    print a
    print 'real job'
    

    test/,如果你这样做:

    $ python foo.py bar/bar.py
    $ python foo.py bar/bar.pyc
    

    两者,输出相同:

    debugging...
    1
    real job
    

    另请参阅answer

    【讨论】:

    • 这本来是我理想的解决方案,但不幸的是execfile 不适用于.pyc 文件,我需要它。
    • @RamRachum,我更新了.pyc 文件的答案。如果您关心的不是时间,但实际上没有.py 文件,它可以解决问题。
    【解决方案3】:

    你可能有一些类似的东西:

    if __name__ == '__main__':
        # some code
    

    相反,在foo 中的函数main() 中编写代码,然后执行以下操作:

    if __name__ == '__main__':
        main()
    

    然后,在 bar 中,您可以导入 foo 并调用 foo.main()

    另外,如果你需要change the working directory,你可以使用os.chdir(path)方法,例如os.chdir('path/of/bar').

    【讨论】:

    • 抱歉,这对foo.py 来说变化太大了。这是一个很大的代码文件,我公司有几十个人在使用,我无法重构它。此外,在启动这样的文件时,我们可能没有考虑到其他差异,我不想冒险。
    【解决方案4】:

    bar.py 必须像 Python 解释器本身一样运行 foo.py(或 foo.pyc,正如您所要求的那样),就好像它是主脚本一样。这出乎意料地难。我 90% 的解决方案是这样的:

    def run_script_as_main(cmdline):
        # It is crucial to import locally what we need as we
        # later remove everything from __main__
        import sys, imp, traceback, os
        # Patch sys.argv
        sys.argv = cmdline
        # Clear the __main__ namespace and set it up to run
        # the secondary program
        maindict = sys.modules["__main__"].__dict__
        builtins = maindict['__builtins__']
        maindict.clear()
        maindict['__file__'] = cmdline[0]
        maindict['__builtins__'] = builtins
        maindict['__name__'] = "__main__"
        maindict['__doc__'] = None
        # Python prepends a script's location to sys.path
        sys.path[0] = os.path.dirname(os.path.abspath(cmdline[0]))
        # Treat everything as a Python source file, except it
        # ends in '.pyc'
        loader = imp.load_source
        if maindict["__file__"].endswith(".pyc"):
            loader = imp.load_compiled
        with open(cmdline[0], 'rb') as f:
            try:
                loader('__main__', maindict["__file__"], f)
            except Exception:
                # In case of an exception, remove this script from the
                # stack trace; if you don't care seeing some bar.py in
                # traceback, you can leave that out.
                ex_type, ex_value, ex_traceback = sys.exc_info()
                tb = traceback.extract_tb(ex_traceback, None)[1:]
                sys.stderr.write("Traceback (most recent call last):\n")
                for line in traceback.format_list(tb):
                    sys.stderr.write(line)
                for line in traceback.format_exception_only(ex_type, ex_value):
                    sys.stderr.write(line)
    

    这模仿了 Python 解释器的默认行为 比较密切。它设置系统全局变量__name____file__sys.argv,将脚本位置插入sys.path,清除全局命名空间等。

    您可以将该代码复制到 bar.py 或从某处导入并像这样使用它:

    if __name__ == "__main__":
        # You debugging setup goes here
        ...
        # Run the Python program given as argv[1]
        run_script_as_main(sys.argv[1:])
    

    那么,鉴于此:

    $ find so-foo-bar
    so-foo-bar
    so-foo-bar/debugaid
    so-foo-bar/debugaid/bar.py
    so-foo-bar/foo.py
    

    foo.py 看起来像这样:

    import sys
    
    if __name__ == "__main__":
        print "My name is", __name__
        print "My file is", __file__
        print "My command line is", sys.argv
        print "First 2 path items", sys.path[:2]
        print "Globals", globals().keys()
        raise Exception("foo")
    

    ...通过bar.py 运行foo.py 会为您提供:

    $ python so-foo-bar/debugaid/bar.py so-foo-bar/foo.py 我的名字是__main__ 我的文件是 so-foo-bar/foo.py 我的命令行是 ['so-foo-bar/foo.py'] 前 2 个路径项 ['~/so-foo-bar', '/usr/local/...'] 全局变量 ['__builtins__', '__name__', '__file__', 'sys', '__package__', '__doc__'] 回溯(最近一次通话最后): 文件“so-foo-bar/foo.py”,第 9 行,在 引发异常(“foo”) 例外:foo

    虽然我猜run_script_as_main 缺少一些有趣的细节,但它与 Python 运行 foo.py 的方式非常接近。

    【讨论】:

      【解决方案5】:

      foo.py 不必与主文件夹位于同一文件夹中。使用

      export PYTHONPATH=$HOME/dirWithFoo/:$PYTHONPATH
      

      foo所在的路径添加到Python路径。现在你可以

      from foo import myfunc
      
      myfunc()
      

      让 myfunc 为所欲为

      【讨论】:

        【解决方案6】:

        你试过了吗?

        bar.py

        imp.load_source('__main__', '../../foo.py')
        

        对我来说,它运行 foo.py 中的任何内容(在 ma​​in 之前和内部)

        之后,也许你在 bar.py 中所做的事情可能比我的基本测试更棘手。

        load_source 的好处是它会导入 .pyc(如果它已经存在)或启动 .py 的“编译”然后导入 .pyc。

        【讨论】:

          【解决方案7】:

          这个解决方案对我来说效果很好:

          #!/usr/bin/env python
          
          import imp
          import sys
          import os.path
          
          file_path = sys.argv.pop(1)
          assert os.path.exists(file_path)
          
          folder, file_name = os.path.split(file_path)
          
          ###############################################################################
          #                                                                             #
          
          # Debugging cruft here
          
          #                                                                             #
          ###############################################################################
          
          
          module_name, _ = os.path.splitext(file_name)
          sys.path.append(folder)
          imp.load_module('__main__', *imp.find_module(module_name))
          

          【讨论】:

          • 您会在我的回答中找到该方法的更通用版本,该版本在一定程度上可以避免对运行的脚本产生不必要的副作用。
          猜你喜欢
          • 1970-01-01
          • 2023-01-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-31
          • 1970-01-01
          相关资源
          最近更新 更多