【问题标题】:How to return a dictionary as a function's return value running as a subprocess to its parent process?如何将字典作为作为子进程运行的函数的返回值返回到其父进程?
【发布时间】:2021-02-02 02:06:21
【问题描述】:

我有两个脚本 parent.py 和 child.py parent.py 调用 child.py 作为子进程。 Child.py 有一个在字典中收集某些结果的函数,我希望将该字典返回给父进程。我尝试通过将 child.py 中的字典打印到其 STDOUT 上,以便父进程可以读取它,但这对我没有帮助,因为字典的内容正在被父进程作为单独的行中的字符串读取。

此外,正如 cmets 中所建议的那样,我尝试使用 JSON 序列化字典,同时在 stdout 上打印它,并使用 JSON 从父级读取它,效果很好,但我也打印了很多来自孩子的其他信息到它的标准输出,它最终也被父级读取并且正在混淆。

提出的另一个建议是将子级的结果写入目录中的一个文件,并让父级从该文件中读取。这也可以,但我会在 Celery 中运行 100 多个此代码的实例,因此它会导致其他子实例覆盖同一文件。

我的问题是,因为我们有一个连接两个进程的管道,我如何才能将我的字典直接从 child.py 写入管道并从 parent.py 读取它

# parent.py

import subprocess

proc = subprocess.Popen(['python3', 'child.py'],
                        stdin=subprocess.PIPE,
                        stdout = subprocess.PIPE
                        )
proc.comunicate()
result = proc.stdout
#child.py

def child_function():
    result = {}
    result[1] = "one"
    result[2] = "two"
    print(result)
    #return result
    
if __name__ == "__main__":
    child_function()

【问题讨论】:

  • 为了通过管道传输字典,您需要将其序列化,例如作为 JSON。但是,为什么不直接从 parent.py 导入并调用 child_function()
  • 您可以使用 JSON 序列化数据:child.py 中的 print(json.dumps(result))parent.py 中的 result = json.loads(proc.stdout)
  • @mkrieger1 感谢您的回复。是的,我希望我可以从父级导入 child_function() 但我处于这样一种情况,因为我必须将 child.py 作为子进程调用,因为我以 root 身份运行父级,但 child.py 中存在的某些功能没有以 root 用户身份工作,因此我通过子进程调用 child.py 与单独的非 root 用户。
  • @IonutTicus 谢谢你的建议,我会试试的。
  • @IonutTicus 没有任何方法可以通过 PIPE 直接将 child.py 中的字典传递给父级,而不是“首先将其打印到子级的 STDOUT”,然后让父级从子级的标准输出中读取它.我的意思是,当我们已经通过 PIPE 连接时,为什么要在 STDOUT 上打印它,为什么我们不能直接通过 PIPE 将该值传递给它的父级?寻找它的原因是我还打印了很多其他的东西,从孩子到它的 STDOUT(调试信息),如果我打印它,那些其他的东西会弄乱字典。

标签: python python-3.x subprocess popen popen3


【解决方案1】:

让父级为子级创建一个 FIFO(命名管道):

with os.mkfifo(mypipe) as pipe:
    proc = subprocess.Popen(['python3', 'child.py', 'mypipe'],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    print(pipe.read())

现在孩子可以这样做了:

pipe_path = # get from argv
with open(pipe_path, 'w') as pipe:
    pipe.write(str(result))

这使您的通信与 stdin/stdout/stderr 分开。

【讨论】:

    【解决方案2】:

    运行 Python 的子进程与运行其他东西的子进程没有任何不同。 Python 不知道也不关心另一个程序也是 Python 程序;他们无法访问彼此的变量、内存、运行状态或其他内部信息。简单地想象子进程是一个整体的二进制文件。您可以与之通信的唯一方法是发送和接收字节(可以是字符串,如果您同意字符编码)和信号(这样您就可以杀死您的子进程,或引发其他可以捕获和处理的信号 - - 就像一个计时器;当计时器到期时,你会得到一个确切的信息,而你对那个位的处理取决于信号的接收者)。

    “序列化”信息意味着以允许接收者反序列化的方式对其进行编码。 JSON 就是一个很好的例子;您可以将由字典或列表(可能是嵌套结构)组成的结构作为文本传输,接收者将知道如何将该字节流映射到相同的结构中。

    当发送者和接收者都运行相同的 Python 版本时,你也可以使用 pickle; pickle 是一种原生 Python 格式,它允许您传输更丰富的结构。但如果您的需求不大,我会选择 JSON。

    parent.py:

    import subprocess
    import json
    
    # Prefer subprocess.run() over bare-bones Popen()
    proc = subprocess.run(['python3', 'child.py'],
        check=True, capture_output=True, text=True)
    result = json.loads(proc.stdout)
    

    child.py:

    import json
    import logging
    
    def child_function():
        result = {}
        result[1] = "one"
        result[2] = "two"
        loggging.info('Some unrelated output which should not go into the JSON')
        print(json.dumps(result))
        #return result
        
    if __name__ == "__main__":
        logging.basicConfig(level=logging.WARNING)
        child_function()
    

    为避免将 JSON 与其他输出混合,请将其他输出打印到标准错误而不是标准输出(或者想办法将其嵌入到 JSON 中)。 logging 模块是一种方便的方法,您可以轻松、部分或完全关闭它(上面的示例演示了通过 logging.basicConfig 关闭的日志记录,因为它只选择打印优先级WARNING 或更高,不包括INFO)。家长会在proc.stderr 中收到这些消息。

    【讨论】:

      【解决方案3】:

      您可以通过文件获取结果。

      父.py:

      import tempfile
      import os
      import subprocess
      import json
      
      
      fd, temp_file_name = tempfile.mkstemp() # create temporary file
      os.close(fd) # close the file
      proc = subprocess.Popen(['python3', 'child.py', temp_file_name]) # pass file_name
      proc.communicate()
      with open(temp_file_name) as fp:
          result = json.load(fp) # get dictionary from here
      os.unlink(temp_file_name) # no longer need this file
      

      child.py:

      import sys
      import json
      
      
      def child_function(temp_file_name):
          result = {}
          result[1] = "one"
          result[2] = "two"
          with open(temp_file_name, 'w') as fp:
              json.dump(result, fp)
      
          
      if __name__ == "__main__":
          child_function(sys.argv[1]) # pass the file name argument
      

      【讨论】:

      • 感谢您的回复,即使我想到了这个解决方案,但问题是整个事情将被发送到 celery(任务队列),并且该代码的近 100 个实例将并行运行我担心这肯定会导致覆盖该文件,因为同样,100 个实例将运行相同的代码。
      • 我将不得不查看更多代码,但仅从您的描述中并不清楚为什么每个实例都没有自己唯一的临时文件。换句话说,我认为这与使用 subprocess.run) 并从标准输出中检索 JSON 的答案没有太大不同,至少就覆盖任何内容而言。该解决方案可能更直接,我提供的只是另一种方式,不需要您修改使用标准输出的方式或代码的任何其他方面。
      • 感谢您的回复,我还在研究这个和其他方面。您在评论后面部分所说的内容是正确的。
      猜你喜欢
      • 2019-10-17
      • 1970-01-01
      • 2019-03-14
      • 2018-09-09
      • 2021-10-02
      • 1970-01-01
      • 2018-04-05
      • 2011-02-03
      • 2014-08-29
      相关资源
      最近更新 更多