Why do I get extra output from code using 'print' (or output at all, using 'return') at the REPL, but not in a script?

Question:

Suppose I make a simple script hello.py that contains just:

print('hello world')

When I run it, I just see hello world in the output. And if I try the same code at the interpreter prompt (REPL), I see the same result:

>>> print('hello world')
hello world

But if I try something more complex, like:

print('hello'), print('world')

When I run the script, I get each word on a separate line, as I expect. But at the REPL:

>>> print('hello'), print('world')
hello
world
(None, None)

What is that extra (None, None) output? Why wasn’t there any extra output in the first example?


Similarly, if I try calling a function:

def example():
    return 1

example()

I don’t see any output, as I expect, because return is not print. But at the REPL, I do see the result:

>>> def example():
...     return 1
... 
>>> example()
1

This seems useful, and perhaps intentional, but I’m not sure I understand why or how it happens. I know that print is a function in 3.x. Since I’m using a function either way, could this be somehow related?

Asked By: Karl Knechtel

||

Answers:

Summary

print produces the hello world output in the first case, whether from the REPL or the script; and the separate hello and world lines in the second case, again whether from the REPL or the script. The REPL itself produces the (None, None) output, because that is the result of evaluating print('hello'), print('world'). The result of evaluating just print('hello world') is None, but the REPL hides None results as a special case.

Text is displayed when it’s written to the program’s standard output stream. Evaluating expressions, returning from functions etc. doesn’t do this automatically. print does display output (that’s its purpose), and the REPL (which is outside your program) also does (except when the result is None).

That’s all you really need to know; but here’s some detail on how the overall system works.

How print works

In 3.x, print is a function. When you call it, it returns the special value None.

"What’s None?" See What is a 'NoneType' object?.

"Why does it return that?" Because in Python, a call to a function is an expression; it has to return something1. If a function reaches the end without returning explicitly, None is what you get implicitly.

"So it doesn’t return the formatted text?" No, why would it? Python style is for functions to do something as a side effect or return something other than None, not both.

"Wait, but then how can it display anything?" It doesn’t need to return anything in order to display the text, and in fact return has nothing to do with displaying anything. Programs that run at the command line display their output by writing it to the standard output stream, which is something like a special file provided by the operating system.2

The REPL, and how it treats code

When you start Python without giving it a script or module to run, you get something that looks like:

Python 3.8.10 (default, Jun 22 2022, 20:18:18) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

(The details will depend on your operating system, Python version and how it was compiled, of course.)

The tutorial in the official documentation refers to the Python executable itself as "the interpreter", and says that here we have started it "in interactive mode". In more colloquial language, the Python community typically refers to this mode, and the >>> prompt, as "the REPL".3

Blocks of code in Python (such as you might start off with if or def, and follow with some indented lines) don’t evaluate to anything – not even None. They’re statements, not expressions.4 But calling a function is an expression, and so it evaluates to a result which can be displayed. When you input an expression, the interpreter displays a textual representation of the result, which comes from repr.5

Except when that result is None. The repr of None is, as you might have guessed, None (that literal text, without quotes). It would be really distracting to see that every time you call print, or use .extend on a list, etc. etc. So the interpreter special-cases this, and hides the None results.

print('hello'), print('world') is an expression that makes a tuple out of the two None values from the print calls. So the result is exactly that: (None, None). That’s not the special case None, so it gets displayed.6

Similarly, if we call our own function at the REPL, the result is displayed even without printing it:

>>> def example():
...     return 1
... 
>>> example()
1

Unless the result is None:

>>> def example():
...     return None
... 
>>> example()
>>> 

How code behaves in a script

When you run Python code from a .py file, there is no REPL any more. So although the code is constantly evaluating expressions, there is nothing to display results – unless you do it explicitly with print. For example, you can make a test .py file that just contains 1 + 1 and run it; that’s perfectly valid Python and it will compute the sum of 2 – it just won’t display anything. Similarly, the example code print('hello'), print('world') computes a tuple of two None values, but doesn’t display it.7

The same applies when we call our own functions: the result isn’t displayed by default, unless we explicitly use something to display it (such as print). After all, it would be really annoying if we couldn’t prevent the result from displaying. Real-world programs do a lot of function calling!


1Or it could raise an exception, of course.

2In Python, the standard output stream can be accessed from the sys standard library module as stdout (so, from sys import stdout and then use stdout, or import sys and then use sys.stdout). Python represents it to you as a file that’s open for writing text. There is also, similarly, a standard error stream, accessible as sys.stderr, used for writing error messages. There are separate streams so that command-line programs can keep those two streams of information separate when they call each other and interpret each others’ output. This design is decades old. Anyway, actually changing the pixel colours in the terminal window is done by the terminal program, not by Python. Python just says what text to display.

3That stands for "Read-Eval-Print Loop", and is an established concept seen in many programming languages. It means exactly what it sounds like: when you provide code at the REPL, the interpreter reads that code, evaluates it, and potentially prints (displays) a result. It keeps doing this, in a loop, until you exit back to the command line.

4The same is true of assignments, which is why you can’t do x = (y = 1) or (x = y) = 1, even though you can do x = y = 1.

5It can’t actually display an integer; it displays text that represents the integer in base ten. Integers don’t have decimal (or binary, or hexadecimal…) digits. Only those representations do. It’s very important for programmers to make these kinds of distinctions, in order to think clearly about problems.

6In 2.x, print was a statement (except in the latest revisions, if you enabled the forwards-compatibility options). So there was no None for the REPL to suppress, but also, code like print('hello'), print('world') wasn’t possible anyway.

7I often see code – especially in Pandas examples – where someone appears to have copied and pasted from an interactive session (after figuring out what works) into a source file, leaving behind lines that just have a variable name on them (because while testing it at the REPL, the author decided to check the variable’s value at that point). In a script, this is benign, but it is also useless – better to take out lines like this.

I also often see code where a list comprehension has been used to replace a for loop that is only used for its side effects. Please don’t do that. It’s not harmful to create the list using a list comprehension, but it’s useless (you end up with a list of None values that you don’t use for anything), unintuitive (list comprehensions are for creating a list; loops are for repeating a process) and could be slightly less efficient.

Answered By: Karl Knechtel
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.