【问题标题】:Launch a python script from another script, with parameters in subprocess argument从另一个脚本启动 python 脚本,参数在子进程参数中
【发布时间】:2018-07-01 22:00:01
【问题描述】:

要从终端启动 python 脚本(运行 OLED 显示器需要它),我必须使用以下 bash 命令:python demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20.py 之后的那些参数很重要,否则,脚本将以默认设置运行,在我的情况下,脚本将不会以默认设置启动。因此,需要这些参数。

当我需要从另一个 python 脚本启动我的脚本(而不是在终端上使用 bash 命令)时,就会出现问题。从父脚本启动我的 python 脚本之一。我用过:

import subprocess # to use subprocess 

p = subprocess.Popen(['python', 'demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20'])

在我的父脚本中,但我收到一条错误消息:

python: can't open file 'demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20': [Errno 2] No such file or directory

我怀疑在.py 之后添加参数--display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20 可能会导致脚本启动困难。如前所述,这些参数对于我在终端上使用 bash 命令启动是必不可少的。如何使用带有所需参数的子进程来启动此脚本?

【问题讨论】:

  • 从 Python 运行 Python 是一种奇怪的反模式。看看demo_oled_v01.py 是否可以重构为可以加载到主 Python 脚本中的模块(或者可能已经有这个功能)。
  • @tripleeee:感谢您的建议。我使用 subprocess 而不是 import 的原因是我从经验中发现,通过 subprocess 方法从父脚本关闭辅助脚本更可靠。但是,我对所有这些都很陌生,所以我可能是错的。如果在这种情况下子流程不理想,您认为我应该如何实施?
  • 一个常见的设计是有一个简单的def main() 并调用它if __name__ == '__main__' 但如果你import 库,__name__ 是别的东西然后你可以使用你@的方法987654332@ 以您从调用脚本中认为合适的方式。 main() 的设计应该是它只调用其他方法并处理直接从命令行调用的简单情况,并且它调用的内部函数应该足够模块化,您可以以不同的顺序或以其他方式调用它们来自执行 import 的脚本的参数。
  • 这可能过于简洁,无法真正提供帮助,但谷歌 python __name__ __main__ 无法在实践中看到这一点。
  • @tripleee:如果我使用import 来启动这个脚本。我如何也包含这些参数?即--display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20我对启动典型python脚本(例如script1.py)的理解是使用:import script1而不包括.py

标签: python bash subprocess


【解决方案1】:

subprocess 库正在解释您的所有参数,包括 demo_oled_v01.py 作为 python 的单个参数。这就是为什么 python 抱怨它无法找到具有该名称的文件的原因。尝试运行它:

p = subprocess.Popen(['python', 'demo_oled_v01.py', '--display',
'ssd1351', '--width', '128', '--height', '128', '--interface', 'spi',
'--gpio-data-command', '20'])

查看更多关于 Popen here 的信息。

【讨论】:

  • 顺便说一句,您可能应该使用subprocess.run() 或其过时的表亲subprocess.check_call(),除非您想自己管理Popen 对象(例如,因为您希望它在后台运行在你的主脚本旁边)。
【解决方案2】:

这开始是一个评论线程,但太长太复杂了。

将 Python 称为 Python 的子进程是一种反模式。您通常可以通过重构您的 Python 代码来有效地避免这种情况,以便您的程序可以将其他程序作为一个简单的库(或模块,或包,或者您拥有的东西)调用 - 这里有一些 terminology,您将想要更正确地理解......最终)。

话虽如此, 有子进程需要成为子进程的场景(例如,它可能被设计为进行自己的信号处理)所以不要'不要盲目应用。

如果你有一个像 demo.py 这样的脚本,其中包含类似

def really_demo(something, other, message='No message'):
    # .... some functionality here ...

def main():
    import argparse
    parser = argparse.ArgumentParser(description='Basic boilerplate, ignore the details.')
    parser.add_argument('--something', dest='something')  # store argument in args.something
    parser.add_argument('--other', dest='other')  # ends up in args.other
    # ... etc etc etc more options
    args = parser.parse_args()
    # This is the beef: once the arguments are parsed, pass them on
    really_demo(args.something, args.other, message=args.message)

if __name__ == '__main__':
    main()

观察当你从命令行运行脚本时,__name__ 将是'__main__',因此它将进入main() 函数,该函数将命令行分开,然后调用其他一些函数——在这个案例,real_demo()。现在,如果您从已经运行的 Python 调用此代码,则无需将参数收集到列表中并将它们传递给新进程。只需让您的 Python 脚本加载您要从脚本调用的函数,并使用您的参数调用它。

换句话说,如果你目前正在做

 subprocess.call(['demo.py', '--something', 'foo', '--other', value, '--message', 'whatever'])

您可以将子流程调用替换为

 from demo import real_demo
 real_demo('foo', value, message='whatever')

注意您是如何绕过main() 函数和所有丑陋的命令行解析,而只是调用另一个Python 函数。 (注意参数的顺序和名称;它们可能与命令行解析器接受的完全不同。)它在不同的文件中定义的事实是import 为您处理的一个小细节,并且文件包含其他函数的事实是您可以忽略的(或者可能更充分地利用,例如,如果您想以方便的方式访问未通过命令行界面公开的内部函数)。

作为一种优化,Python 不会import 某事两次,所以你真的需要确保你需要的功能在你import 它时没有运行。通常,您在脚本的开头import 一次(尽管从技术上讲,您可以在需要它的def 中执行此操作,例如,如果您的代码中只有一个地方依赖于import)然后根据需要调用从import 获得的函数,次数不限。

这是一个非常常见的问题的快速回顾。如果这不能让您朝着正确的方向开始,您应该能够在 Stack Overflow 上找到许多关于此重构任务各个方面的现有问题。

【讨论】:

  • 感谢您的详细解释。我现在更好地理解了导入功能,虽然我还在学习,但我仍然需要阅读更多关于这个的内容。我最近尝试在我的脚本中实现这一点并取得了很大的成功。但是,我似乎遇到了一个限制。那是我需要反导入模块的时候。
  • 在我的一个脚本中,我有一个带有递增和递减函数的 while 循环,每次递增 1 时,它都会导入一个 python 脚本,该脚本会改变我在 LED 屏幕上的显示。但是,当我触发递减时,似乎什么都没有发生(好像脚本已经按照我之前所做的递增导入,并且无法重新导入/重新加载)。
  • 是的,import 部分只能发生一次,但是您运行的函数应该在您调用它时运行,而不是在您 import 时运行。您可能需要将脚本更改为 import 以正确支持此功能。这正是我的意思,确保您要调用的内容位于 def 中,这样您就可以在需要时调用该函数,除非您特别调用它,否则它不会运行。
  • 感谢您的澄清。我导入脚本而不是特定函数的原因是我发现该脚本的一个函数依赖于主函数。如果我仅从该脚本导入特定函数,终端会告诉我未定义某些内容。
  • 在这个脚本中,在写下导入之后,它开始于:def primitives(device, draw): # First define some constants to allow easy resizing of shapes. padding = 2 shape_width = 20 top = padding bottom = device.height - padding - 1 # Move left to right keeping track of the current x position for drawing shapes. x = padding # Write text. size = draw.textsize('World!') x = device.width - padding - size[0] draw.text((device.width - padding - size[0], top + 4), 'Hello', fill="cyan") fill="purple")
【解决方案3】:

为python脚本添加完整路径并分隔所有参数

前:

import subprocess
p = subprocess.Popen(['python', 'FULL_PATH_TO_FILE/demo_oled_v01.py',  '--display',  'ssd1351', '--width', '128', '--height', '128', '--interface', 'spi', '--gpio-data-command', '20'])

【讨论】:

    【解决方案4】:

    对于 WindowsPython 3.x,您可以:

    1. 使用 Windows shell(cmd.exe 很可能默认在 Windows 上)
    result = subprocess.Popen('cd C:\\Users\\PathToMyPythonScript
                               && python myPythonScript.py value1ofParam1 value2ofParam2', 
                               shell=True, universal_newlines=True, 
                               stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    output, error = result.communicate()
    
    print(output)
    
    1. 留在你的 Python 环境中(去掉 shell=True)你可以这样写:
    result = subprocess.Popen(["C:\Windows\System32\cmd.exe", "/k",
                               "cd", "C:\\Users\\PathToMyPythonScript",
                               "&&", "dir", "&&", "python", "myPythonScript.py",
                               "value1ofParam1", "value2ofParam2"], universal_newlines=True,
                               stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    output, error = result.communicate()
    
    print(output)
    

    • 您可以调用的脚本文件示例(“MyPythonScript.py”):
    # =============================================================================
    # This script just outputs the arguments you've passed to it
    import sys
    print('Number of arguments:', len(sys.argv), 'arguments.')
    print('Argument List:', str(sys.argv))
    # =============================================================================
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-10
      • 1970-01-01
      • 2022-08-22
      • 1970-01-01
      • 1970-01-01
      • 2022-01-18
      相关资源
      最近更新 更多