ホームページ  >  記事  >  バックエンド開発  >  Python は外部サブプロセスを呼び出し、パイプを介して非同期の標準入出力を実装します。

Python は外部サブプロセスを呼び出し、パイプを介して非同期の標準入出力を実装します。

大家讲道理
大家讲道理オリジナル
2016-11-07 17:06:381030ブラウズ

私たちは通常、このようなニーズに遭遇します。複雑な機能モジュールは C++ またはその他の下位言語を通じて実装され、データをクエリするために Web ベースのデモを構築する必要があります。 Python 言語のパワーとシンプルさにより、Flask フレームワークと jinja2 モジュール関数は Python に便利な Web 開発機能を提供します。同時に、Python は他の言語のコードと簡単に対話できます。したがって、デモを開発するためのツールとして Python を選択します。呼び出す必要があるモジュール (基礎となるサービスを提供する) が標準入力を介してループでデータを読み取り、処理後に結果をマークされた出力に書き込むと仮定します。このシナリオは Linux 環境では非常に一般的であり、Linux の強力なリダイレクト機能に依存しています。 。ただし、残念ながら、基礎となるモジュールには大量の初期化プロセスがあるため、クエリ要求ごとに基礎となるモジュールを呼び出す子プロセスを再生成することはできません。解決策は、子プロセスを 1 回だけ生成し、リクエストごとにパイプを介して子プロセスと対話することです。

Python のサブプロセス モジュールは、Linux システム コールの fork および exec と同様に、サブプロセスを簡単に生成できます。サブプロセス モジュールの Popen オブジェクトは、外部の実行可能プログラムをノンブロッキングで呼び出すことができるため、ニーズを達成するために Poen オブジェクトを使用します。サブプロセスの標準入力 stdin にデータを書き込む場合は、Popen オブジェクトの作成時にパラメータ stdin を subprocess.PIPE として指定する必要があります。同様に、サブプロセスの標準出力からデータを読み取る必要がある場合は、次のようにします。 Popen オブジェクトを作成するときは、パラメータ stdout を subprocess.PIPE として指定する必要があります。まず簡単な例を見てみましょう:

from subprocess import Popen, PIPE
p = Popen('less', stdin=PIPE, stdout=PIPE)
p.communicate('Line number %d.\n' % x)

communication 関数は、子プロセスの標準出力とエラーを示す出力データを含むタプル (stdoutdata、stderrdata) を返します。ただし、Popen オブジェクトの通信関数は親プロセスをブロックし、パイプも閉じるため、複数のリクエストがある場合、各 Popen オブジェクトは通信関数を 1 回しか呼び出すことができません。Popen オブジェクトを再生成する (子プロセスを再初期化する) 必要があります。それは私たちのニーズを満たすことができません。

したがって、Popen オブジェクトの stdin オブジェクトと stdout オブジェクトにデータを読み書きすることによってのみニーズを達成できます。ただし、残念ながら、サブプロセス モジュールはデフォルトでは、サブプロセスの終了時に 1 回だけ実行され、標準出力を読み取ります。サブプロセスと os.popen* は両方とも入出力を 1 回だけ許可し、出力はプロセス終了時にのみ読み取られます。

いくつか調べた結果、サブプロセスの標準出力は fcntl 関数を通じて出力できることがわかりました。 fcntl モジュール 目的を達成するためにノンブロッキングメソッドに変更します。長年私を悩ませてきたこの問題が、ついに完全に解決されました。コードは次のとおりです:

#!/usr/bin/python                                                                                                                                                      
# -*- coding: utf-8 -*-
# author: weisu.yxd@taobao.com
from subprocess import Popen, PIPE
import fcntl, os
import time
class Server(object):
  def __init__(self, args, server_env = None):
    if server_env:
      self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=server_env)
    else:
      self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    flags = fcntl.fcntl(self.process.stdout, fcntl.F_GETFL)
    fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
  def send(self, data, tail = '\n'):
    self.process.stdin.write(data + tail)
    self.process.stdin.flush()
  def recv(self, t=.1, e=1, tr=5, stderr=0):
    time.sleep(t)
    if tr < 1:
        tr = 1 
    x = time.time()+t
    r = &#39;&#39;
    pr = self.process.stdout
    if stderr:
      pr = self.process.stdout
    while time.time() < x or r:
        r = pr.read()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            return r.rstrip()
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return r.rstrip()
if __name__ == "__main__":
  ServerArgs = [&#39;/home/weisu.yxd/QP/trunk/bin/normalizer&#39;, &#39;/home/weisu.yxd/QP/trunk/conf/stopfile.txt&#39;]
  server = Server(ServerArgs)
  test_data = &#39;在云端&#39;, &#39;云梯&#39;, &#39;摩萨德&#39;, &#39;Alisa&#39;, &#39;iDB&#39;, &#39;阿里大数据&#39;
  for x in test_data:
    server.send(x)
    print x, server.recv()

さらに、一部の外部プログラムを呼び出すときは、次のように対応する環境変数を指定する必要がある場合があります:

  my_env = os.environ
  my_env["LD_LIBRARY_PATH"] = "/path/to/lib"
  server = server.Server(cmd, my_env)

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。