barplot - Subproceso Python con entrada en tiempo real y mĂșltiples consolas
pandas plot (1)
El problema al que te enfrentas es la arquitectura del subsistema de consola en Windows, la ventana de consola que normalmente ves no está alojada en cmd.exe, sino que conhost.exe, un proceso secundario de una ventana de conhost solo puede conectarse a una Conhost instancia única significa que está limitado a una sola ventana por proceso.
Esto lleva a tener un proceso adicional para cada ventana de consola que desea tener, y para ver cómo se muestra en esa ventana, debe ver cómo se manejan normalmente la entrada estándar y la salida estándar, ya que se escriben y leen En la instancia de Conhost, excepto que si convierte la entrada estándar en una canalización (para poder escribir en el proceso), ya no proviene de Conhost sino de su proceso principal y, por lo tanto, Conhost no tiene visibilidad. Esto significa que cualquier cosa escrita en stdin solo es leída por el proceso hijo, por lo que no es mostrada por conhost.
Que yo sepa, no hay una manera de compartir la tubería de esa manera.
Como efecto secundario, si convierte la entrada estándar en una canalización, todas las entradas del teclado enviadas a la nueva ventana de la consola no van a ninguna parte, ya que la entrada estándar no está conectada a esa ventana.
Para una función de salida solamente, esto significa que puede generar un nuevo proceso que se comunique con el padre a través de una canalización a la entrada estándar y que todo se transmita a la salida estándar.
Heres un intento:
#!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)
Así que una canalización simple y agradable entre dos procesos y haciendo eco de la entrada a la salida si es el subproceso, utilicé a -r para indicar si la instancia es un proceso, pero hay otras formas en función de cómo se implementa.
Varias cosas a tener en cuenta:
- la descarga después de escribir en stdin es necesaria, ya que Python normalmente usa buffering.
- La forma en que se escribe este enfoque está orientada a estar en su propio módulo, por lo tanto, se usa
__file__
- debido al uso de
__file__
este enfoque puede necesitar modificaciones si se congela usando cx_Freeze o similar.
EDITAR 1
para una versión que se puede congelar con 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,
)
]
)
Editar 2
Nueva versión que debería funcionar bajo herramientas dev como 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,
)
]
)
Esto podría hacerse más robusto, es decir, siempre buscando una consola existente y separándola si se encuentra antes de crear una nueva consola, y posiblemente un mejor manejo de errores.
El problema principal
En pocas palabras: quiero dos consolas para mi programa. Uno para la entrada de usuario activo. Y la otra para salida de log pura. (El código de trabajo que incluye la respuesta aceptada se encuentra en el texto de la pregunta a continuación, en la sección "Edición-3". Y en la sección "Edición-1" y la sección "Edición-2" funcionan como soluciones provisionales).
Para esto tengo un script de Python de línea de comando principal, que se supone que abre una consola adicional solo para la salida del registro. Para esto, tengo la intención de redirigir la salida del registro, que se imprimiría en la consola del script principal, al stdin de la segunda consola, que comienzo como un subproceso. (Uso subproceso, porque no encontré ninguna otra forma de abrir una segunda consola).
El problema es que parece que puedo enviar al stdin de esta segunda consola, sin embargo, nada se imprime en esta segunda consola.
A continuación se muestra el código que utilicé para experimentar (con Python 3.4 en PyDev en Windows 10). La función de writing(input, pipe, process)
contiene la parte, donde la cadena generada se copia a la pipe
estándar pasada, de la consola abierta a través de subproceso. La función escritura (...) se ejecuta a través de la clase writetest(Thread)
. (Dejé un código, que comenté).
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")
Un enfoque ligeramente diferente
El siguiente fragmento de código adicional funciona en lo que respecta a extraer la salida estándar del subproceso. Sin embargo, el stdin enviado anteriormente todavía no se imprime en la segunda consola. Además, la segunda consola se cierra de inmediato.
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")
Más información
Tan pronto como uso uno de los parámetros stdin=data_pipe, stdout=PIPE, stderr=PIPE
para iniciar el subproceso, la segunda consola resultante no está activa y no acepta entrada de teclado (lo que no es deseable, aunque podría ser información útil aquí).
El método de subproceso communicate()
no se puede usar para esto ya que espera a que finalice el proceso.
Más código
Finalmente el código para el archivo, que es para la segunda consola.
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)
Pensamientos
Hasta ahora ni siquiera estoy seguro de si la interfaz de línea de comandos de Windows ofrece la capacidad de aceptar otra entrada que no sea la del teclado (en lugar de la tubería estándar deseada o similar). Aunque, teniendo algún tipo de modo pasivo, lo espero.
¿Por qué esto no funciona?
Edit-1: Solución a través del archivo (prueba de concepto)
Usar un archivo como solución temporal para mostrar su contenido nuevo, como se sugiere en la respuesta de Trabajar varias consolas en Python , funciona en general. Sin embargo, dado que el archivo de registro crecerá a muchos GB, no es una solución práctica en este caso. Al menos requeriría la división de archivos y su manejo adecuado.
La clase:
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'')
La función:
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)
La llamada:
# 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)
El script de cola ("test-004.py"):
Como Windows no ofrece el comando tail, utilicé el siguiente script ( basado en la respuesta de ¿Cómo implementar un equivalente pythonic de tail -F? ), Que funcionó para esto. La clase de línea de comandos adicional, aunque innecesaria class CommandLine(Cmd)
fue inicialmente un intento de mantener abierta la segunda consola (porque faltaba el argumento del archivo de script). Sin embargo, también demostró ser útil para mantener la consola imprimiendo con fluidez el nuevo contenido del archivo de registro. De lo contrario, la salida no fue determinista / predecible.
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:])
Edit-1: Más pensamientos
Tres soluciones alternativas, que no probé todavía y que podrían funcionar son los sockets (también sugeridos en esta respuesta Trabajando múltiples consolas en python ), obteniendo un objeto de proceso a través del ID de proceso para un mayor control, y usando la biblioteca ctypes para acceder directamente a Windows API de la consola, que permite configurar el búfer de pantalla, ya que la consola puede tener varios búferes, pero solo un búfer activo (como se indica en los comentarios de la documentación para la función CreateConsoleScreenBuffer ).
Sin embargo, el uso de sockets puede ser el más fácil. Y al menos el tamaño del registro no importa de esta manera. Sin embargo, los problemas de conexión pueden ser un problema aquí.
Edit-2: Solución a través de sockets (prueba de concepto)
El uso de sockets como solución temporal para mostrar nuevas entidades de registro, como también se sugirió en la respuesta de Trabajar múltiples consolas en python , también funciona en general. Sin embargo, esto parece ser demasiado esfuerzo para algo, que simplemente debe enviarse al proceso de la consola receptora.
La clase:
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'')
La función:
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)
La llamada:
# 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)
El script de socket ("test-005.py"):
La siguiente secuencia de comandos se basa en Python: aplicación de cliente-servidor de programación de sockets que utiliza subprocesos . Aquí acabo de mantener la class CommandLine(Cmd)
como generador de entrada de registro. En este punto, no debería ser un problema, poner al cliente en el script principal, que llama a la segunda consola y luego alimenta la cola con registros reales en lugar de (nuevas) líneas de archivos. (El servidor es la impresora.)
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:])
Edit-2: Además pensamientos
Tener el control directo de la entrada / consola de la consola del subproceso sería la solución preferible a este problema. Porque esta es la recompensa de 500 Reputación.
Lamentablemente me estoy quedando sin tiempo. Por lo tanto, podría usar una de esas soluciones por ahora y reemplazarla con la solución adecuada más adelante. O tal vez tenga que usar la opción nuclear, solo una consola, donde la salida del registro en curso se detiene durante cualquier entrada del teclado del usuario y luego se imprime. Por supuesto, esto podría llevar a problemas en el búfer, cuando el usuario decide escribir algo a la mitad.
Edit-3: Código que incluye la respuesta aceptada (un archivo)
Con la respuesta de James Kent obtengo el comportamiento deseado, cuando comienzo un script con el código a través de la línea de comandos de Windows (cmd) o PowerShell. Sin embargo, cuando comienzo este mismo script a través de Eclipse / PyDev con "Python run", la salida siempre se imprime en la consola principal de Eclipse / PyDev, mientras que la segunda consola del subproceso permanece vacía y permanece inactiva. Sin embargo, creo que esta es otra especialidad de sistema / entorno y un tema diferente.
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).'')
Edit-3: Menciones honoríficas
En el texto de las preguntas anteriores, mencioné el uso de la biblioteca ctypes para acceder directamente a la API de la consola de Windows como otro entorno de trabajo (en "Editar-1: Más ideas"). O usando solo una consola de forma que el mensaje de entrada siempre se mantenga en la parte inferior como opción nuclear para todo este problema. (en "Editar-2: Además pensamientos")
Para usar la biblioteca ctypes me habría orientado en la siguiente respuesta a Cambiar la fuente de la consola en Windows . Y para usar una sola consola, habría intentado la siguiente respuesta a Mantener la línea de entrada de la consola debajo de la salida . Creo que estas dos respuestas pueden ofrecer un posible error con respecto a este problema y tal vez sean útiles para los demás. Además, si encuentro el tiempo, lo intentaré si funcionan de alguna manera.