web-dev-qa-db-ja.com

Pythonリアルタイム入力と複数のコンソールを備えたサブプロセス

主な問題

簡単に言うと、プログラムに2つのコンソールが必要です。アクティブなユーザー入力用。そして、純粋なログ出力用のもう1つ(受け入れられた回答を含む作業コードは、下の質問のテキストの「Edit-3」セクションにあります。 「Edit-1」および「Edit-2」セクションは機能する回避策です。)

このために、メインコマンドラインPythonスクリプトがあります。これは、ログ出力専用の追加コンソールを開くことになっています。このため、メインに出力されるログ出力をリダイレクトします。スクリプトのコンソール、2番目のコンソールの標準入力、サブプロセスとして開始します(2番目のコンソールを開く他の方法が見つからなかったため、サブプロセスを使用します)。

問題は、この2番目のコンソールの標準入力に送信できるように見えることですが、この2番目のコンソールには何も印刷されません。

以下は、実験に使用したコードです(Windows 10のPyDevでPython 3.4を使用)。関数writing(input, pipe, process)には、生成された文字列がasにコピーされる部分が含まれます。 pipeは、サブプロセスを介して開かれたコンソールのstdinを渡しました。関数writing(...)は、クラスwritetest(Thread)を介して実行されます(コードをいくつか残しましたが、コメントアウトしました)。

_import os
import sys
import io
import time
import threading
from cmd import Cmd
from queue import Queue
from subprocess import Popen, PIPE, CREATE_NEW_CONSOLE


REPETITIONS = 3


# Position of "The class" (Edit-2)


# Position of "The class" (Edit-1)


class generatetest(threading.Thread):

    def __init__(self, queue):
        self.output = queue
        threading.Thread.__init__(self)

    def run(self):
        print('run generatetest')
        generating(REPETITIONS, self.output)
        print('generatetest done')

    def getout(self):
        return self.output


class writetest(threading.Thread):

    def __init__(self, input=None, pipe=None, process=None):
        if (input == None):        # just in case
            self.input = Queue()
        else:
            self.input = input

        if (pipe == None):        # just in case
            self.pipe = PIPE
        else:
            self.pipe = pipe

        if (process == None):        # just in case
            self.process = subprocess.Popen('C:\Windows\System32\cmd.exe', universal_newlines=True, creationflags=CREATE_NEW_CONSOLE)
        else:
            self.process = proc

        threading.Thread.__init__(self)

    def run(self):
        print('run writetest')
        writing(self.input, self.pipe, self.process)
        print('writetest done')


# Position of "The function" (Edit-2)


# Position of "The function" (Edit-1)


def generating(maxint, outline):
    print('def generating')
    for i in range(maxint):
        time.sleep(1)
        outline.put_nowait(i)


def writing(input, pipe, process):
    print('def writing')
    while(True):
        try:
            print('try')
            string = str(input.get(True, REPETITIONS)) + "\n"
            pipe = io.StringIO(string)
            pipe.flush()
            time.sleep(1)
            # print(pipe.readline())
        except:
            print('except')
            break
        finally:
            print('finally')
            pass


data_queue = Queue()
data_pipe = sys.stdin
# printer = sys.stdout
# data_pipe = os.pipe()[1]


# The code of 'C:\\Users\\Public\\Documents\\test\\test-cmd.py'
# can be found in the question's text further below under "More code"


exe = 'C:\Python34\python.exe'
# exe = 'C:\Windows\System32\cmd.exe'
arg = 'C:\\Users\\Public\\Documents\\test\\test-cmd.py'
arguments = [exe, arg]
# proc = Popen(arguments, universal_newlines=True, creationflags=CREATE_NEW_CONSOLE)
proc = Popen(arguments, stdin=data_pipe, stdout=PIPE, stderr=PIPE,
             universal_newlines=True, creationflags=CREATE_NEW_CONSOLE)


# Position of "The call" (Edit-2 & Edit-1) - file init (proxyfile)


# Position of "The call" (Edit-2) - thread = sockettest()
# Position of "The call" (Edit-1) - thread0 = logtest()
thread1 = generatetest(data_queue)
thread2 = writetest(data_queue, data_pipe, proc)
# time.sleep(5)


# Position of "The call" (Edit-2) - thread.start()
# Position of "The call" (Edit-1) - thread0.start()
thread1.start()
thread2.start()


# Position of "The call" (Edit-2) - thread.join()
# Position of "The call" (Edit-1) - thread.join()
thread1.join(REPETITIONS * REPETITIONS)
thread2.join(REPETITIONS * REPETITIONS)

# data_queue.join()
# receiver = proc.communicate(stdin, 5)
# print('OUT:' + receiver[0])
# print('ERR:' + receiver[1])

print("1st part finished")
_

少し異なるアプローチ

次の追加のコードスニペットは、サブプロセスから標準出力を抽出することに関して機能します。ただし、以前に送信された標準入力はまだ2番目のコンソールに出力されません。また、2番目のコンソールはすぐに閉じられます。

_proc2 = Popen(['C:\Python34\python.exe', '-i'],
              stdin=PIPE,
              stdout=PIPE,
              stderr=PIPE,
              creationflags=CREATE_NEW_CONSOLE)
proc2.stdin.write(b'2+2\n')
proc2.stdin.flush()
print(proc2.stdout.readline())
proc2.stdin.write(b'len("foobar")\n')
proc2.stdin.flush()
print(proc2.stdout.readline())
time.sleep(1)
proc2.stdin.close()
proc2.terminate()
proc2.wait(timeout=0.2)

print("Exiting Main Thread")
_

より詳しい情報

サブプロセスを開始するためにパラメータ_stdin=data_pipe, stdout=PIPE, stderr=PIPE_の1つを使用するとすぐに、結果の2番目のコンソールはアクティブではなく、キーボード入力を受け入れません(これは望ましくありませんが、ここでは役立つ情報かもしれません)。

サブプロセスメソッドcommunicate()は、プロセスが終了するまで待機するため、これには使用できません。


より多くのコード

最後に、2番目のコンソール用のファイルのコード。

C:\ Users\Public\Documents\test\test-cmd.py

_from cmd import Cmd
from time import sleep
from datetime import datetime

INTRO = 'command line'
Prompt = '> '


class CommandLine(Cmd):
    """Custom console"""

    def __init__(self, intro=INTRO, Prompt=PROMPT):
        Cmd.__init__(self)
        self.intro = intro
        self.Prompt = Prompt
        self.doc_header = intro
        self.running = False

    def do_dummy(self, args):
        """Runs a dummy method."""
        print("Do the dummy.")
        self.running = True
        while(self.running == True):
            print(datetime.now())
            sleep(5)

    def do_stop(self, args):
        """Stops the dummy method."""
        print("Stop the dummy, if you can.")
        self.running = False

    def do_exit(self, args):
        """Exits this console."""
        print("Do console exit.")
        exit()

if __name__ == '__main__':
    cl = CommandLine()
    cl.Prompt = Prompt
    cl.cmdloop(INTRO)
_

考え

これまでのところ、Windowsコマンドラインインターフェイスが、キーボードからの入力以外の入力を受け入れる機能を提供しているかどうかさえわかりません(必要なstdinパイプなどの代わりに)。とはいえ、ある種のパッシブモードがあるので、私はそれを期待しています。

なぜこれが機能しないのですか?


編集-1:ファイルによる回避策(概念実証)

Pythonで複数のコンソールを操作する の答えで示唆されているように、ファイルを表示するための回避策としてファイルを使用すると、一般的に機能します。ただし、ログファイルは数GBに達するため、この場合の実用的な解決策ではありません。少なくともファイルの分割と適切な処理が必要です。

クラス:

_class logtest(threading.Thread):

    def __init__(self, file):
        self.file = file
        threading.Thread.__init__(self)

    def run(self):
        print('run logtest')
        logging(self.file)
        print('logtest done')
_

関数:

_def logging(file):
    pexe = 'C:\Python34\python.exe '
    script = 'C:\\Users\\Public\\Documents\\test\\test-004.py'
    filek = '--file'
    filev = file

    file = open(file, 'a')
    file.close()
    time.sleep(1)

    print('LOG START (outer): ' + script + ' ' + filek + ' ' + filev)
    proc = Popen([pexe, script, filek, filev], universal_newlines=True, creationflags=CREATE_NEW_CONSOLE)
    print('LOG FINISH (outer): ' + script + ' ' + filek + ' ' + filev)

    time.sleep(2)
_

呼び出し:

_# The file tempdata is filled with several strings of "0\n1\n2\n"
# Looking like this:
# 0
# 1
# 2
# 0
# 1
# 2

proxyfile = 'C:\\Users\\Public\\Documents\\test\\tempdata'
f = open(proxyfile, 'a')
f.close()
time.sleep(1)

thread0 = logtest(proxyfile)
thread0.start()
thread0.join(REPETITIONS * REPETITIONS)
_

テールスクリプト( "test-004.py"):

Windowsはtailコマンドを提供していないので、代わりに次のスクリプトを使用しました( tailに相当するPythonの実装方法-F? の答えに基づいています)。追加でありながら不要なclass CommandLine(Cmd)は、最初は2番目のコンソールを開いたままにする試みでした(スクリプトファイルの引数がないため)。ただし、コンソールが新しいログファイルの内容を流に印刷し続けるのに役立つことも証明されています。それ以外の場合、出力は決定的/予測不可能でした。

_import time
import sys
import os
import threading
from cmd import Cmd
from argparse import ArgumentParser


def main(args):
    parser = ArgumentParser(description="Parse arguments.")
    parser.add_argument("-f", "--file", type=str, default='', required=False)
    arguments = parser.parse_args(args)

    if not arguments.file:
        print('LOG PRE-START (inner): file argument not found. Creating new default entry.')
        arguments.file = 'C:\\Users\\Public\\Documents\\test\\tempdata'

    print('LOG START (inner): ' + os.path.abspath(os.path.dirname(__file__)) + ' ' + arguments.file)

    f = open(arguments.file, 'a')
    f.close()
    time.sleep(1)

    words = ['Word']
    console = CommandLine(arguments.file, words)
    console.Prompt = ''

    thread = threading.Thread(target=console.cmdloop, args=('', ))
    thread.start()
    print("\n")

    for hit_Word, hit_sentence in console.watch():
        print("Found %r in line: %r" % (hit_Word, hit_sentence))

    print('LOG FINISH (inner): ' + os.path.abspath(os.path.dirname(__file__)) + ' ' + arguments.file)


class CommandLine(Cmd):
    """Custom console"""

    def __init__(self, fn, words):
        Cmd.__init__(self)
        self.fn = fn
        self.words = words

    def watch(self):
        fp = open(self.fn, 'r')
        while True:
            time.sleep(0.05)
            new = fp.readline()
            print(new)
            # Once all lines are read this just returns ''
            # until the file changes and a new line appears

            if new:
                for Word in self.words:
                    if Word in new:
                        yield (Word, new)

            else:
                time.sleep(0.5)


if __name__ == '__main__':
    print('LOG START (inner - as main).')
    main(sys.argv[1:])
_

編集-1:より多くの考え

まだ試していないが動作する可能性のある3つの回避策は、ソケットです(この回答でも提案されています Pythonで複数のコンソールを動作させる )、プロセスIDを介してプロセスオブジェクトを取得して制御し、 WindowsコンソールAPIに直接アクセスするためのctypesライブラリ。スクリーンバッファーを設定できます。コンソールには複数のバッファーを設定できますが、アクティブバッファーは1つだけです( CreateConsoleScreenBuffer function のドキュメントの備考に記載されています) 。

ただし、ソケットを使用するのが最も簡単な方法かもしれません。そして、少なくともログのサイズはこのように関係ありません。ただし、ここでは接続の問題が問題になる場合があります。


編集-2:ソケットを介した回避策(概念実証)

Pythonで複数のコンソールを操作する の回答でも提案されているように、ソケットを回避策として使用して新しいログエンティティを表示することも一般的に機能しています。とはいえ、これは何かをするにはあまりにも多くの労力を費やしているようです。これは単に受信コンソールのプロセスに送られるべきです。

クラス:

_class sockettest(threading.Thread):

    def __init__(self, Host, port, file):
        self.Host = Host
        self.port = port
        self.file = file
        threading.Thread.__init__(self)

    def run(self):
        print('run sockettest')
        socketing(self.Host, self.port, self.file)
        print('sockettest done')
_

関数:

_def socketing(Host, port, file):
    pexe = 'C:\Python34\python.exe '
    script = 'C:\\Users\\Public\\Documents\\test\test-005.py'
    hostk = '--address'
    hostv = str(Host)
    portk = '--port'
    portv = str(port)
    filek = '--file'
    filev = file

    file = open(file, 'a')
    file.close()
    time.sleep(1)

    print('Host START (outer): ' + pexe + script + ' ' + hostk + ' ' + hostv + ' ' + portk + ' ' + portv + ' ' + filek + ' ' + filev)
    proc = Popen([pexe, script, hostk, hostv, portk, portv, filek, filev], universal_newlines=True, creationflags=CREATE_NEW_CONSOLE)

    print('Host FINISH (outer): ' + pexe + script + ' ' + hostk + ' ' + hostv + ' ' + portk + ' ' + portv + ' ' + filek + ' ' + filev)

    time.sleep(2)
_

呼び出し:

_# The file tempdata is filled with several strings of "0\n1\n2\n"
# Looking like this:
# 0
# 1
# 2
# 0
# 1
# 2

proxyfile = 'C:\\Users\\Public\\Documents\\test\\tempdata'
f = open(proxyfile, 'a')
f.close()
time.sleep(1)

thread = sockettest('127.0.0.1', 8888, proxyfile)
thread.start()
thread.join(REPETITIONS * REPETITIONS)
_

ソケットスクリプト( "test-005.py"):

次のスクリプトは、 Python:スレッドを使用したサーバー/クライアントアプリケーションのプログラミング に基づいています。ここでは、ログエントリジェネレータとしてclass CommandLine(Cmd)を保持しています。この時点で、2番目のコンソールを呼び出すメインスクリプトにクライアントを配置し、(新しい)ファイル行の代わりに実際のログエンティティをキューに供給することは問題になりません。 (サーバーはプリンターです。)

_import socket
import sys
import threading
import time
from cmd import Cmd
from argparse import ArgumentParser
from queue import Queue

BUFFER_SIZE = 5120

class CommandLine(Cmd):
    """Custom console"""

    def __init__(self, fn, words, queue):
        Cmd.__init__(self)
        self.fn = fn
        self.words = words
        self.queue = queue

    def watch(self):
        fp = open(self.fn, 'r')
        while True:
            time.sleep(0.05)
            new = fp.readline()

            # Once all lines are read this just returns ''
            # until the file changes and a new line appears
            self.queue.put_nowait(new)


def main(args):
    parser = ArgumentParser(description="Parse arguments.")
    parser.add_argument("-a", "--address", type=str, default='127.0.0.1', required=False)
    parser.add_argument("-p", "--port", type=str, default='8888', required=False)
    parser.add_argument("-f", "--file", type=str, default='', required=False)
    arguments = parser.parse_args(args)

    if not arguments.address:
        print('Host PRE-START (inner): Host argument not found. Creating new default entry.')
        arguments.Host = '127.0.0.1'
    if not arguments.port:
        print('Host PRE-START (inner): port argument not found. Creating new default entry.')
        arguments.port = '8888'
    if not arguments.file:
        print('Host PRE-START (inner): file argument not found. Creating new default entry.')
        arguments.file = 'C:\\Users\\Public\\Documents\\test\\tempdata'

    file_queue = Queue()

    print('Host START (inner): ' + ' ' + arguments.address + ':' + arguments.port + ' --file ' + arguments.file)

    # Start server
    thread = threading.Thread(target=start_server, args=(arguments.address, arguments.port, ))
    thread.start()
    time.sleep(1)

    # Start client
    thread = threading.Thread(target=start_client, args=(arguments.address, arguments.port, file_queue, ))
    thread.start()

    # Start file reader
    f = open(arguments.file, 'a')
    f.close()
    time.sleep(1)

    words = ['Word']
    console = CommandLine(arguments.file, words, file_queue)
    console.Prompt = ''

    thread = threading.Thread(target=console.cmdloop, args=('', ))
    thread.start()
    print("\n")

    for hit_Word, hit_sentence in console.watch():
        print("Found %r in line: %r" % (hit_Word, hit_sentence))

    print('Host FINISH (inner): ' + ' ' + arguments.address + ':' + arguments.port)


def start_client(Host, port, queue):
    Host = Host
    port = int(port)         # arbitrary non-privileged port
    queue = queue

    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        soc.connect((Host, port))
    except:
        print("Client connection error" + str(sys.exc_info()))
        sys.exit()

    print("Enter 'quit' to exit")
    message = ""

    while message != 'quit':
        time.sleep(0.05)
        if(message != ""):
            soc.sendall(message.encode("utf8"))
            if soc.recv(BUFFER_SIZE).decode("utf8") == "-":
                pass        # null operation

        string = ""
        if (not queue.empty()):
            string = str(queue.get_nowait()) + "\n"

        if(string == None or string == ""):
            message = ""
        else:
            message = string

    soc.send(b'--quit--')


def start_server(Host, port):
    Host = Host
    port = int(port)         # arbitrary non-privileged port

    soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # SO_REUSEADDR flag tells the kernel to reuse a local socket in TIME_WAIT state, without waiting for its natural timeout to expire
    soc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    print("Socket created")

    try:
        soc.bind((Host, port))
    except:
        print("Bind failed. Error : " + str(sys.exc_info()))
        sys.exit()

    soc.listen(5)       # queue up to 5 requests
    print("Socket now listening")

    # infinite loop- do not reset for every requests
    while True:
        connection, address = soc.accept()
        ip, port = str(address[0]), str(address[1])
        print("Connected with " + ip + ":" + port)

        try:
            threading.Thread(target=client_thread, args=(connection, ip, port)).start()
        except:
            print("Thread did not start.")
            traceback.print_exc()

    soc.close()


def client_thread(connection, ip, port, max_buffer_size=BUFFER_SIZE):
    is_active = True

    while is_active:
        client_input = receive_input(connection, max_buffer_size)

        if "--QUIT--" in client_input:
            print("Client is requesting to quit")
            connection.close()
            print("Connection " + ip + ":" + port + " closed")
            is_active = False
        Elif not client_input == "":
            print("{}".format(client_input))
            connection.sendall("-".encode("utf8"))
        else:
            connection.sendall("-".encode("utf8"))


def receive_input(connection, max_buffer_size):
    client_input = connection.recv(max_buffer_size)
    client_input_size = sys.getsizeof(client_input)

    if client_input_size > max_buffer_size:
        print("The input size is greater than expected {}".format(client_input_size))

    decoded_input = client_input.decode("utf8").rstrip()  # decode and strip end of line
    result = process_input(decoded_input)

    return result


def process_input(input_str):
    return str(input_str).upper()


if __name__ == '__main__':
    print('Host START (inner - as main).')
    main(sys.argv[1:])
_

編集-2:さらに考え

サブプロセスのコンソール入力パイプ/バッファを直接制御することは、この問題の望ましい解決策です。これは500評判の報奨金です。

残念ながら、私は時間を使い果たしています。したがって、これらの回避策のいずれかを今のところ使用し、後で適切なソリューションに置き換える可能性があります。または、核のオプションを使用する必要があります。コンソールは1つだけで、ユーザーのキーボード入力中は継続的なログ出力が一時停止され、その後印刷されます。もちろん、ユーザーが途中で何かを入力することを決めた場合、これはバッファの問題につながる可能性があります。


編集-3:受け入れられた回答を含むコード(1ファイル)

James Kentからの回答により、Windowsコマンドライン(cmd)またはPowerShellを介してコードを使用してスクリプトを開始したときに、目的の動作が得られます。ただし、「Python run」でEclipse/PyDevを介してこの同じスクリプトを起動すると、出力は常にメインのEclipse/PyDevコンソールに出力されますが、サブプロセスの2番目のコンソールは空のままで非アクティブのままです。ただし、これは別のシステム/環境の専門分野であり、別の問題だと思います。

_from sys import argv, stdin, stdout
from threading import Thread
from cmd import Cmd
from time import sleep
from datetime import datetime
from subprocess import Popen, PIPE, CREATE_NEW_CONSOLE

INTRO = 'command line'
Prompt = '> '


class CommandLine(Cmd):
    """Custom console"""

    def __init__(self, subprocess, intro=INTRO, Prompt=PROMPT):
        Cmd.__init__(self)
        self.subprocess = subprocess
        self.intro = intro
        self.Prompt = Prompt
        self.doc_header = intro
        self.running = False

    def do_date(self, args):
        """Prints the current date and time."""
        print(datetime.now())
        sleep(1)

    def do_exit(self, args):
        """Exits this command line application."""
        print("Exit by user command.")
        if self.subprocess is not None:
            try:
                self.subprocess.terminate()
            except:
                self.subprocess.kill()
        exit()


class Console():

    def __init__(self):
        if '-r' not in argv:
            self.p = Popen(
                ['python.exe', __file__, '-r'],
                stdin=PIPE,
                creationflags=CREATE_NEW_CONSOLE
            )
        else:
            while True:
                data = stdin.read(1)
                if not data:
                    #                     break
                    sleep(1)
                    continue
                stdout.write(data)

    def write(self, data):
        self.p.stdin.write(data.encode('utf8'))
        self.p.stdin.flush()

    def getSubprocess(self):
        if self.p:
            return self.p
        else:
            return None


class Feeder (Thread):

    def __init__(self, console):
        self.console = console
        Thread.__init__(self)

    def run(self):
        feeding(self.console)


def feeding(console):
    for i in range(0, 100):
        console.write('test %i\n' % i)
        sleep(1)


if __name__ == '__main__':
    p = Console()
    if '-r' not in argv:
        thread = Feeder(p)
        thread.setDaemon(True)
        thread.start()

        cl = CommandLine(subprocess=p.getSubprocess())
        cl.use_rawinput = False
        cl.Prompt = Prompt
        cl.cmdloop('\nCommand line is waiting for user input (e.g. help).')
_

編集-3:名誉ある言及

上記の質問のテキストでは、別の回避策としてWindowsコンソールAPIに直接アクセスするためにctypesライブラリを使用することに言及しています(「Edit-1:More Thoughts」の下)。または、1つのコンソールだけを使用して、入力プロンプトが常にこの問題全体の核となる選択肢として一番下にとどまるようにします。 (「Edit-2:さらに考え」の下)

Ctypesライブラリを使用するために、私は Windowsでコンソールフォントを変更する に対する次の答えに焦点を当てていました。そして、1つのコンソールだけを使用する場合、 コンソールの入力行を出力の下に置く に対して次の回答を試してみました。これらの答えは両方とも、この問題に関して潜在的なメリットを提供する可能性があり、おそらくこの投稿に出くわした他の人に役立つと思います。また、私は時間を見つけたら、彼らが何らかの形で動作するかどうかを試してみます。

20
Jonathan Root

あなたが直面している問題は、Windows上のコンソールサブシステムのアーキテクチャであり、通常表示されるコンソールウィンドウはcmd.exeによってホストされず、代わりにconhost.exeによってホストされ、conhostウィンドウの子プロセスは単一のコンホストインスタンスは、プロセスごとに1つのウィンドウに制限されることを意味します。

これにより、各コンソールウィンドウに追加のプロセスが必要になります。そのウィンドウに何かを表示するには、stdinとstdoutが通常どのように処理され、読み書きされるかを調べる必要があります(ホストにプロセスを書き込むことができるように)stdinをパイプに変更する場合を除き、conhostインスタンスによって、conhostからではなく、親プロセスから来るようになります。これは、stdinに書き込まれたものはすべて子プロセスによってのみ読み取られるため、conhostによって表示されないことを意味します。

私の知る限り、そのようなパイプを共有する方法はありません。

副作用として、stdinをパイプにすると、新しいコンソールウィンドウに送信されるすべてのキーボード入力はどこにも行きません。stdinはそのウィンドウに接続されていないからです。

これは、出力のみの関数の場合、パイプを介して標準入力に親と通信し、すべてを標準出力にエコーする新しいプロセスを生成できることを意味します。

試みはここにあります:

#!python3

import sys, subprocess, time

class Console():
    def __init__(self):
        if '-r' not in sys.argv:
            self.p = subprocess.Popen(
                ['python.exe', __file__, '-r'],
                stdin=subprocess.PIPE,
                creationflags=subprocess.CREATE_NEW_CONSOLE
                )
        else:
            while True:
                data = sys.stdin.read(1)
                if not data:
                    break
                sys.stdout.write(data)

    def write(self, data):
        self.p.stdin.write(data.encode('utf8'))
        self.p.stdin.flush()

if (__name__ == '__main__'):
    p = Console()
    if '-r' not in sys.argv:
        for i in range(0, 100):
            p.write('test %i\n' % i)
            time.sleep(1)

したがって、2つのプロセス間のニースの単純なパイプで、サブプロセスの場合は入力を出力にエコーバックします。インスタンスがプロセスかどうかを示すために-rを使用しましたが、実装方法に応じて他の方法があります。

注意すべき点がいくつかあります。

  • python通常バッファリングを使用するため、stdinへの書き込み後のフラッシュが必要です。
  • このアプローチが書かれている方法は、それ自身のモジュールにあることを目的としているため、__file__
  • __file__を使用しているため、cx_Freezeなどを使用して凍結する場合、このアプローチを変更する必要があります。

編集1

cx_Freezeでフリーズできるバージョンの場合:

Console.py

import sys, subprocess

class Console():
    def __init__(self, ischild=True):
        if not ischild:
            if hasattr(sys, 'frozen'):
                args = ['Console.exe']
            else:
                args = [sys.executable, __file__]
            self.p = subprocess.Popen(
                args,
                stdin=subprocess.PIPE,
                creationflags=subprocess.CREATE_NEW_CONSOLE
                )
        else:
            while True:
                data = sys.stdin.read(1)
                if not data:
                    break
                sys.stdout.write(data)

    def write(self, data):
        self.p.stdin.write(data.encode('utf8'))
        self.p.stdin.flush()

if (__name__ == '__main__'):
    p = Console()

test.py

from Console import Console
import sys, time

if (__name__ == '__main__'):
    p = Console(False)
    for i in range(0, 100):
        p.write('test %i\n' % i)
        time.sleep(1)

setup.py

from cx_Freeze import setup, Executable

setup(
    name = 'Console-test',
    executables = [
        Executable(
            'Console.py',
            base=None,
            ),
        Executable(
            'test.py',
            base=None,
            )
        ]
)

編集2

IDLEなどの開発ツールで動作する新しいバージョン

Console.py

#!python3

import ctypes, sys, subprocess

Kernel32 = ctypes.windll.Kernel32

class Console():
    def __init__(self, ischild=True):
        if ischild:
            # try allocate new console
            result = Kernel32.AllocConsole()
            if result > 0:
                # if we succeed open handle to the console output
                sys.stdout = open('CONOUT$', mode='w')
        else:
            # if frozen we assume its names Console.exe
            # note that when frozen 'Win32GUI' must be used as a base
            if hasattr(sys, 'frozen'):
                args = ['Console.exe']
            else:
                # otherwise we use the console free version of python
                args = ['pythonw.exe', __file__]
            self.p = subprocess.Popen(
                args,
                stdin=subprocess.PIPE
                )
            return
        while True:
            data = sys.stdin.read(1)
            if not data:
                break
            sys.stdout.write(data)

    def write(self, data):
        self.p.stdin.write(data.encode('utf8'))
        self.p.stdin.flush()

if (__name__ == '__main__'):
    p = Console()

test.py

from Console import Console
import sys, time

if (__name__ == '__main__'):
    p = Console(False)
    for i in range(0, 100):
        p.write('test %i\n' % i)
        time.sleep(1)

setup.py

from cx_Freeze import setup, Executable

setup(
    name = 'Console-test',
    executables = [
        Executable(
            'Console.py',
            base='Win32GUI',
            ),
        Executable(
            'test.py',
            base=None,
            )
        ]
)

これはより堅牢にすることができます。つまり、新しいコンソールを作成する前に、既存のコンソールを常に確認し、見つかった場合はそれをデタッチし、エラー処理を改善します。

14
James Kent