¿Por qué este código imprime un resultado diferente entre Windows y Linux?

Este código imprime una cadena diferente entre Windows y Linux.

test.py:

print(";".join([str(i) for i in range(10000)])) 

Plataforma: x86_64 Linux 4.4 .0-17763 – Microsoft
Versión de Python: 3.7.2
Terminales: bash, pescado

Salida abreviada:

 $ python --version Python 3.7.2 $ python test.py 0;1;2;3;4;5;6....9997;9998;9999 $ python -u test.py 0;1;2;3;4;5;6....9997;9998;9999 

Plataforma: Windows 10 1809
Versión de Python: 3.6.8, 3.7.0, 3.7.2
Terminales: cmd, powershell

Salida abreviada:

 ./python --version Python 3.6.8 ./python test.py 0;1;2;3;4;5;6....9997;9998;9999 ./python -u test.py 0;1;2;3;4;5;6....2663;2664;2665;26 
 ./python --version Python 3.7.0 ./python test.py 0;1;2;3;4;5;6....9997;9998;9999 ./python -u test.py 0;1;2;3;4;5;6....2663;2664;2665;26 
 ./python --version Python 3.7.2 ./python test.py 0;1;2;3;4;5;6....9997;9998;9999 ./python -u test.py 0;1;2;3;4;5;6....2663;2664;2665;26 

Entonces, ¿por qué, en Windows, el -u arg causa que la salida se trunque (solo de 0 a 2666 )?
(Cuando se usa python -u test.py > a.txt para redirigir la salida a un archivo, funciona correctamente).

Tal vez algo sobre el almacenamiento en búfer?

Se documenta que el tamaño de una escritura de consola a través de WINAPI WriteFile y WriteConsoleW tiene un límite vagamente definido, de la siguiente manera:

nNumberOfCharsToWrite [en]
El número de caracteres a escribir. Si el tamaño total del número especificado de caracteres supera el montón disponible, la función falla con ERROR_NOT_ENOUGH_MEMORY .

No está documentado a qué “montón” se refiere. Un proceso puede tener múltiples montones de varios tamaños (fijos o dynamics). La implementación del montón nativo en la biblioteca de tiempo de ejecución de NT (por ejemplo, RtlCreateHeap ) puede crear un montón en una dirección específica, lo que permite un acceso conveniente a la memoria que se comparte con otros procesos. El uso de un montón compartido a menudo se combina con puertos de comunicación entre procesos locales (LPC) o LPC asíncrono en NT 6.0+. Los puertos LPC se utilizan para pasar mensajes entre aplicaciones y servicios del sistema, como el administrador de sesión (smss.exe), el administrador de control de servicios (services.exe), la autoridad de seguridad local (lsass.exe), el servidor de sesión de escritorio (csrss.exe) , e instancias del host de la consola (conhost.exe). Los mensajes en cola directamente a un puerto LPC están limitados a 256 bytes. Los mensajes más grandes se pasan al poner en cola un mensaje al puerto que hace referencia a la memoria compartida.

Resulta que la implementación anterior de la consola (antes de NT 6.3) utiliza LPC como un canal de E / S, y el montón mencionado anteriormente es solo de 64 KiB . Esta fue una elección peculiar de diseño. Creo que alguien estaba bebiendo demasiado del subsistema en modo usuario, Kool-Aid que pasa mensajes. La I / O de NT adecuada utiliza un dispositivo con servicios de sistema de E / S, incluidos NtCreateFile , NtReadFile , NtWriteFile y NtDeviceIoControlFile .

Una aplicación de consola no sabe cuánto de este montón está disponible para una escritura. Python podría comenzar a 64 KiB y seguir su camino hacia abajo, pero su E / S de archivo sin formato exige una llamada al sistema por llamada. En su lugar, encabeza las escrituras en 32 KiB, que deberían tener éxito. Este límite permite escribir cadenas de caracteres anchos con hasta 16K de puntos de código UTF-16. Una complicación es que la stack de E / S de la consola utiliza UTF-8 en 3.6+, que debe descodificarse a través de MultiByteToWideChar . Actualmente, solo divide repetidamente el búfer UTF-8 a la mitad hasta que la longitud resultante es menor que 16K. Así, en el ejemplo de la pregunta, la escritura de 48,889 caracteres se reduce a la mitad a 24,444 caracteres y se reduce a la mitad nuevamente a 12,222 caracteres. (OMI, sería mejor tratar de escribir hasta 16K puntos de código; obtener el número realmente escrito y llamar a WideCharToMultiByte en la subcadena para determinar el número de bytes UTF-8 escritos. El diseño actual en realidad tiene un error si un UTF 8 2-4 secuencia de bytes se superpone a un punto de corte.)

En NT 6.3+ (Windows 8.1+), la E / S de la consola no tiene este límite de tamaño porque utiliza el dispositivo ConDrv y las llamadas del sistema de E / S en lugar de LPC. Sin embargo, no vale la pena incluir el código en la encoding especial solo para admitir una stack de E / S de texto sin almacenamiento de información, como lo configura la opción de línea de comandos -u . Esperamos que la E / S de la consola interactiva esté en búfer. La E / S de texto no almacenado no está permitida con una llamada open normal. Por ejemplo:

 >>> open('conout$', 'w', buffering=0) Traceback (most recent call last): File "", line 1, in  ValueError: can't have unbuffered text I/O 

El soporte extendido para Windows 7 finaliza el 14 de enero de 2020, por lo que Python 3.8 será la última versión que lo admita. El límite de escritura de la consola debe eliminarse en Python 3.9.