python stdin buffering

python - Establecer un tamaño de búfer más pequeño para sys.stdin?



buffering (5)

Estoy ejecutando memcached con el siguiente patrón de comando bash:

memcached -vv 2>&1 | tee memkeywatch2010098.log 2>&1 | ~/bin/memtracer.py | tee memkeywatchCounts20100908.log

para tratar de rastrear inigualable obtiene conjuntos para claves de toda la plataforma.

El script memtracer está debajo y funciona como se desee, con un pequeño problema. Viendo el tamaño del archivo de registro intermedio, memtracer.py no comienza a recibir entrada hasta que memkeywatchYMD.log tenga un tamaño de aproximadamente 15-18K. ¿Hay una mejor manera de leer en stdin o quizás una forma de reducir el tamaño del buffer a menos de 1k para tiempos de respuesta más rápidos?

#!/usr/bin/python import sys from collections import defaultdict if __name__ == "__main__": keys = defaultdict(int) GET = 1 SET = 2 CLIENT = 1 SERVER = 2 #if < for line in sys.stdin: key = None components = line.strip().split(" ") #newConn = components[0][1:3] direction = CLIENT if components[0].startswith("<") else SERVER #if lastConn != newConn: # lastConn = newConn if direction == CLIENT: command = SET if components[1] == "set" else GET key = components[2] if command == SET: keys[key] -= 1 elif direction == SERVER: command = components[1] if command == "sending": key = components[3] keys[key] += 1 if key != None: print "%s:%s" % ( key, keys[key], )


El sys.stdin.__iter__ sigue siendo un buffer de línea, uno puede tener un iterador que se comporta principalmente de forma idéntica (se detiene en EOF, mientras que stdin.__iter__ no) utilizando la forma de 2 argumentos de iter para hacer un iterador de sys.stdin.readline :

import sys for line in iter(sys.stdin.readline, ''''): sys.stdout.write(''> '' + line.upper())

O proporcione None como centinela (pero tenga en cuenta que debe manejar la condición EOF por su cuenta).


Esto funcionó para mí en Python 3.4.3:

import os import sys unbuffered_stdin = os.fdopen(sys.stdin.fileno(), ''rb'', buffering=0)

La documentación para fdopen() dice que es solo un alias para open() .

open() tiene un parámetro de buffering opcional:

el almacenamiento en búfer es un número entero opcional utilizado para establecer la política de almacenamiento en búfer. Pase 0 para desactivar el almacenamiento en búfer (solo permitido en modo binario), 1 para seleccionar almacenamiento en línea (solo utilizable en modo texto) y un número entero> 1 para indicar el tamaño en bytes de un almacenamiento en bloque de tamaño fijo.

En otras palabras:

  • El stdin completamente sin búfer requiere un modo binario y pasar cero como el tamaño del búfer.
  • El almacenamiento en línea requiere un modo de texto .
  • Cualquier otro tamaño de búfer parece funcionar en los modos binario y de texto (según la documentación).

La única forma en que podría hacerlo con Python 2.7 fue:

tty.setcbreak(sys.stdin.fileno())

desde la entrada de consola sin bloqueo de Python . Esto deshabilita por completo el almacenamiento en búfer y también suprime el eco.

EDITAR: Con respecto a la respuesta de Alex, la primera proposición (invocando python con -u ) no es posible en mi caso (ver limitación de shebang ).

La segunda proposición (duplicar fd con buffer más pequeño: os.fdopen(sys.stdin.fileno(), ''r'', 100) no funciona cuando uso un buffer de 0 o 1, como lo es para una entrada interactiva y Necesito que cada personaje presionado sea procesado inmediatamente.


Puede eliminar completamente el almacenamiento en búfer de stdin / stdout utilizando el indicador -u de python:

-u : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x) see man page for details on internal buffering relating to ''-u''

y la página man aclara:

-u Force stdin, stdout and stderr to be totally unbuffered. On systems where it matters, also put stdin, stdout and stderr in binary mode. Note that there is internal buffering in xread- lines(), readlines() and file-object iterators ("for line in sys.stdin") which is not influenced by this option. To work around this, you will want to use "sys.stdin.readline()" inside a "while 1:" loop.

Más allá de esto, no se admite alterar el almacenamiento en búfer de un archivo existente, pero puede crear un nuevo objeto de archivo con el mismo descriptor de archivo subyacente que uno existente, y posiblemente un búfer diferente, utilizando os.fdopen . Es decir,

import os import sys newin = os.fdopen(sys.stdin.fileno(), ''r'', 100)

debería asociar newin al nombre de un objeto de archivo que lea el mismo FD como entrada estándar, pero almacenado en búfer por solo unos 100 bytes a la vez (y puede continuar con sys.stdin = newin para usar el nuevo objeto de archivo como entrada estándar de allí en adelante). Digo "debería" porque esta área solía tener varios errores y problemas en algunas plataformas (es una funcionalidad bastante difícil proporcionar multiplataforma con total generalidad) - No estoy seguro de cuál es su estado ahora, pero yo '' Definitivamente, recomiendo pruebas exhaustivas en todas las plataformas de interés para garantizar que todo funcione sin problemas. ( -u , eliminar el almacenamiento en búfer por completo, debería funcionar con menos problemas en todas las plataformas, si eso puede cumplir sus requisitos).


Simplemente puede usar sys.stdin.readline() lugar de sys.stdin.__iter__() :

import sys while True: line = sys.stdin.readline() if not line: break # EOF sys.stdout.write(''> '' + line.upper())

Esto me da lecturas de buffer de línea usando Python 2.7.4 y Python 3.3.1 en Ubuntu 13.04.