How to Flush the Output of the Python Print FunctionIn this tutorial, we will learn how to flush the output data buffer explicitly using the flush parameter of the print() function. We will also determine when we need to flush the data buffer and when we don't need it. We will also discuss changing data buffering for a single function and the script. The behavior of the print() function with default arguments changes depending on whether we are running Python in interactive mode or non-interactive mode. When running Python interactively (e.g., in a Jupyter notebook), the output of print() is line-buffered, meaning that each line of output is written to the screen as soon as it is generated. However, when running Python non-interactively (e.g., running a Python script from the command line), the output is block-buffered, meaning that it is stored in a buffer until a certain amount of data has accumulated, and then it is written to the screen all at once. Line-buffered means that the output is flushed (i.e., written to the screen) after each newline character \n is printed. Block-buffered means that the output is stored in a buffer until a certain amount of data has accumulated, and then it is flushed all at once. How Python Buffers OutputWhen we make a write call to a file-like object, Python buffers call by default-and that's a good idea! Disk writes and read operations are slow in comparison to random-access memory (RAM) access. When the script makes fewer system calls for write operations by batching characters in a RAM data buffer and writing them all at once to disk with a single system call, then we can save a lot of time. Let's take a real-life example to understand buffering - A traffic light can be an example of buffering; if every car were allowed to cross an intersection as soon as it arrived, it would lead to gridlock, with cars from different directions getting stuck at the intersection. In contrast, traffic lights buffer the flow of traffic from one direction while allowing traffic from the other direction to move, preventing gridlock and ensuring a more efficient flow of traffic overall. Similarly, buffering data in memory before writing it to disk allows for more efficient use of system resources, reducing the number of system calls required for writing data and improving the overall performance of the system. Sometimes, we want to fill up before the data buffer flushes. For example - An ambulance needs to be passed as soon as possible, and we don't want it to wait at the traffic light until a certain number of cars are queued up. When developing a program, it is often necessary to get real-time feedback on the execution of the code. In such cases, flushing the data buffer immediately is crucial. Here are a few examples of scenarios where immediate flushing is beneficial:
In either of these cases, it is necessary to read the output as soon as it is generated rather than waiting for enough output to accumulate in the buffer and flush it. Failing to do so can result in missing or outdated data, which can cause errors or other issues in your program. In many situations, buffering is helpful, and at some point, too much buffering leads to a disadvantage. We can implement different types of data buffering where they are suitable.
In Python, block buffering is used when we write to file-like objects. However, if we are writing in interactive environment, it executes line-buffered. Let's see the following script for a better understanding. Example - Output: 3 2 1 Go! The sleep() function from the time module is used to pause the program execution for a specified number of seconds. In this case, it pauses the program for 1 second between each count. The range() function is used to create a sequence of numbers from 3 to 1 (not including 0), with a step of -1 (i.e., counting down). If the program were to buffer the entire countdown and only print it once it has finished, it could cause confusion and uncertainty for the athletes waiting at the start line. Therefore, it is necessary to print each count as it happens with a one-second delay between them, so the athletes can have a clear and accurate sense of when the countdown is nearing its end. Add a Newline for Python to Flush Print OutputWe won't get any issues if we run the code snippet in a Python REPL or execute it as a script directly with the Python interpreter. When running a program in an interactive environment, such as a terminal, the standard output stream (STDOUT) will be line-buffered. It means that when you use the print() function to write something to STDOUT, the output will be held in a buffer until either a newline character (\n) is encountered, the buffer becomes full, or the program ends. When encountering a newline character, the buffer is automatically flushed, which means that all of the contents of the buffer are written to STDOUT immediately. It is why, when using print() to output multiple lines of text, each line appears as soon as it is printed instead of waiting for the buffer to fill up or the program to finish. If we write the above script with the print() with its argument, the end of each call of print() writes a newline character implicitly. Example - Output: 3 2 1 Go When we use the print() function to output a number, the number is sent to the output buffer along with a newline character (\n). Since we are working with an interactive environment, such as a terminal, the print() function operates in a line-buffered mode, which means that the buffer is automatically flushed and the output is displayed on the terminal after each print() call. Therefore, each number is sent to the terminal separately, with its own system call, resulting in immediate display of the number on the terminal. To get the newlines in the output, it is good to avoid pass any argument to end. In that case, the print() statement will use the default value of "/n" for end argument. In the following section, we will discuss about an unexpected data buffering issue that can occur if we modify our code for performance reasons. Additionally, we will discover how to address this issue to ensure that our program functions as intended. Set the Flush ParameterWe know the print() has an end argument and want to print the countdown number in a single line. We can change the end value from the default newline character to a space (""). Let's understand the following example. Example - Output: 3 2 1 Go! Although we intended to print the numbers on a single line, refactoring the code for performance has resulted in a new issue. Instead of printing the numbers one after another as they count down, they all appear simultaneously once the program has finished executing. Block buffering is the reason for the unexpected behavior. Although the output stream that print() writes to is still terminal, an interactive environment, the behavior of the line buffering has changed due to modifying the end parameter. The output stream is still technically line-buffered, as before, but because the end parameter has been changed, there are no longer any newline characters (\n) being written. As a result, the line buffering is never triggered. Example - Output: 3 2 1 Go We change the default flush's value to True which flushes the output stream, independent of what default data buffering the file stream that we are writing to has. If we decide to remove any newline characters from print() statements, it is important to note that the default behavior of Python is to buffer the output rather than display it in real time. The output will only be displayed once the buffer is full or the program finishes executing. To ensure your output is displayed in real-time, we must set the flush parameter to True. By doing so, Python will immediately flush the output buffer after each print() statement, allowing the output to be displayed in real-time on the terminal. Change the PYTHONBUFFERED Environment VariableTo change the PYTHONBUFFERED environment without changing the existing code, we need to execute the script without the data buffer and using the command. Example - Depending on the operating system, we will use the cat or echo command for the monitoring script. To run countdown.py unbuffered without applying any changes to the source code, we can execute Python with the -u command option before piping its output to cat or echo. The -u command option is used to disable the data buffer for output streams, standard output (stdout) and standard error (stderr). Even when piping the output of our program to another program, we can achieve unbuffered output from print() without making any changes to our code. In this approach, print() writes directly to standard output without buffering the data, allowing the output to be immediately piped to the next program in the pipeline. Instead of relying on the -u command option to achieve unbuffered output, you can explicitly set the PYTHONUNBUFFERED environment variable to a non-empty string. By default, this variable is set to an empty string, but setting it to any other value will cause Python to execute in unbuffered mode for all script runs in the current environment. This provides an alternative method for ensuring that your program's output is immediately displayed, regardless of whether or not it is being piped to another program. We need to change the value of PYTHONUNBUFFERED in your environment before running your script for this change to have an effect. For Windows - For Linux - Use functools.partial to Change the Signature of PrintIn this section, we will explore another real-life situation. Suppose a team has introduced a large database and asked the setting up automated monitoring on an existing log file. We have tasked the script with monitoring using print() to send logging information to an output stream. Real-time monitoring of the script's output is impossible due to output buffering. The script contains numerous print() statements, making it challenging to modify without impacting the original codebase. To minimize changes to the original script, finding a solution that does not require extensive modifications is important. Changing its function signature is one way to quickly modify the default behavior of print() in the script we want to monitor. It can be accomplished without modifying the original script by creating a modified version of the print() function that includes the desired default behavior. Example - When we add the above two lines of code at the top of the script, we changed the built-in signature of the print() to set flush to True. By creating a modified version of the print() function in the script, all subsequent calls to print() will automatically use the updated function signature. The data buffer will be flushed after each print() call, even if the output stream is not a terminal. This modification can help ensure that real-time monitoring of the script's output is possible, regardless of the destination of the output stream. Let's understand the following example. Example - Output: 3 2 1 Go! This approach allows you to use buffered and unbuffered print() calls within the script. By defining a function specifying unbuffered writes to standard output up front, you can easily incorporate it into the existing codebase without modifying each print() call. Additionally, giving the function a descriptive name can help other developers understand the purpose of the modification more quickly than if you had added the flush=True parameter to each print() call. ConclusionThis tutorial included the flush of the output data buffer explicitly using the flush parameter of print(). We have explored change data buffering for a single function, the whole script, and even the whole Python environment. By running a short code snippet that you modified slightly, you were able to observe that print() executes line-buffered when run in interactive mode, but block-buffered otherwise. You also gained an understanding of situations where you might want to change this default behavior, as well as the methods available for doing so. |