python3

python3で外部プログラムとstdin/stdoutで双方向通信を行う

やったこと

python3でsubprocessを利用して外部プログラムと双方向通信をしたかったのですが、その情報が見つけられなかったため、自分でモジュールを作成しました。

はじめに

python上でfasttextを利用して機械学習を試していますが、python用のfasttextのモジュールがバグがあるらしく、現状は複数の文をテキストファイル化してバッチ的に処理を行っていました。
それを、テキスト1文ずつ文字列で渡して、結果が返ってくればいいな。。。。

作ったもの

モジュール化したものを作成しました。

shell_communication.py
import subprocess

class Execute(object):
    """外部プログラムを実行。stdinとstdoutで対話する。"""

    def __init__(self, *args_1, **args_2):
        if 'encoding' in args_2:
            self.encoding = args_2.pop('encoding')
        else:
            self.encoding = 'utf-8'
        self.popen = subprocess.Popen(*args_1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding=self.encoding, **args_2)
    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def send(self, message, recieve=True, incr=False):
        message = message.rstrip('\n')
        if not incr and '\n' in message:
            raise ValueError("message in \\n!")
        self.popen.stdin.write(message + '\n')
        self.popen.stdin.flush()
        if recieve:
            return self.recieve()
        return None

    def recieve(self):
        self.popen.stdout.flush()
        return self.popen.stdout.readline()

    def close(self):
        self.popen.stdin.close()

※ ゾンビプロセスができる場合があるため、waitを追加。

使い方

ソースを見ていただけると分かるとは思いますが以下の通りです。

import shell_communication
e = shell_communication.Execute(引数)

として使います。
引数は、Popenと基本的に同様ですが、以下が異なります。

  • 常に、stdin=subprocess.PIPE, stdout=subprocess.PIPEを指定している。
  • encodingをデフォルトで'utf-8'指定している。

以下サンプルです。

sample.py
import shell_communication
e = shell_communication.Execute(["cat", "-n"])
for i in range(10):
    print(e.send(str(i)))

出力

     1  0

     2  1

     3  2

     4  3

     5  4

     6  5

     7  6

     8  7

     9  8

    10  9

分かったこと

  • stdin,stdoutはbinaryなので、encode,decodeが必要。
  • stdinに書いてもバッファリングされるため、flushが必要。

疑問点

  • 出力を、self.popen.stdout.readline()で取得しているため、出力がない場合にブロックしてしまう。どうすればいいんだろう??

感想

  • 今までこういうことはnode.jsを利用して非同期にやってきました。pythonだとなんでもブロックしちゃって書きにくいなぁ。。。