LoginSignup
18
22

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-11-15

やったこと

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だとなんでもブロックしちゃって書きにくいなぁ。。。
18
22
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
22