
Understanding Python print()
Understanding Python print() êŽë š


You know how to use print()
quite well at this point, but knowing what it is will allow you to use it even more effectively and consciously. After reading this section, youâll understand how printing in Python has improved over the years.
Print Is a Function in Python 3
Youâve seen that print()
is a function in Python 3. More specifically, itâs a built-in function, which means that you donât need to import it from anywhere:
print
#
# <built-in function print>
Itâs always available in the global namespace so that you can call it directly, but you can also access it through a module from the standard library:
import builtins
builtins.print
#
# <built-in function print>
This way, you can avoid name collisions with custom functions. Letâs say you wanted to redefine print()
so that it doesnât append a trailing newline. At the same time, you wanted to rename the original function to something like println()
:
import builtins
println = builtins.print
def print(*args, **kwargs):
builtins.print(*args, **kwargs, end='')
println('hello')
#
# hello
print('hello\n')
#
# hello
Now you have two separate printing functions just like in the Java programming language. Youâll define custom print()
functions in the mocking section later as well. Also, note that you wouldnât be able to overwrite print()
in the first place if it wasnât a function.
On the other hand, print()
isnât a function in the mathematical sense, because it doesnât return any meaningful value other than the implicit None
:
value = print('hello world')
#
# hello world
print(value)
#
# None
Such functions are, in fact, procedures or subroutines that you call to achieve some kind of side-effect, which ultimately is a change of a global state. In the case of print()
, that side-effect is showing a message on the standard output or writing to a file.
Because print()
is a function, it has a well-defined signature with known attributes. You can quickly find its documentation using the editor of your choice, without having to remember some weird syntax for performing a certain task.
Besides, functions are easier to extend. Adding a new feature to a function is as easy as adding another keyword argument, whereas changing the language to support that new feature is much more cumbersome. Think of stream redirection or buffer flushing, for example.
Another benefit of print()
being a function is composability. Functions are so-called first-class objects or first-class citizens in Python, which is a fancy way of saying theyâre values just like strings or numbers. This way, you can assign a function to a variable, pass it to another function, or even return one from another. print()
isnât different in this regard. For instance, you can take advantage of it for dependency injection:
def download(url, log=print):
log(f'Downloading {url}')
# ...
def custom_print(*args):
pass # Do not print anything
download('/js/app.js', log=custom_print)
Here, the log
parameter lets you inject a callback function, which defaults to print()
but can be any callable. In this example, printing is completely disabled by substituting print()
with a dummy function that does nothing.
John Munsch, 28 October 2009
A dependency is any piece of code required by another bit of code.
Dependency injection is a technique used in code design to make it more testable, reusable, and open for extension. You can achieve it by referring to dependencies indirectly through abstract interfaces and by providing them in a push rather than pull fashion.
Thereâs a funny explanation of dependency injection circulating on the Internet:
Dependency injection for five-year-olds
When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesnât want you to have. You might even be looking for something we donât even have or which has expired.
What you should be doing is stating a need, âI need something to drink with lunch,â and then we will make sure you have something when you sit down to eat.
Composition allows you to combine a few functions into a new one of the same kind. Letâs see this in action by specifying a custom error()
function that prints to the standard error stream and prefixes all messages with a given log level:
from functools import partial
import sys
redirect = lambda function, stream: partial(function, file=stream)
prefix = lambda function, prefix: partial(function, prefix)
error = prefix(redirect(print, sys.stderr), '[ERROR]')
error('Something went wrong')
#
# [ERROR] Something went wrong
This custom function uses partial functions to achieve the desired effect. Itâs an advanced concept borrowed from the functional programming paradigm, so you donât need to go too deep into that topic for now. However, if youâre interested in this topic, I recommend taking a look at the functools
module.
Unlike statements, functions are values. That means you can mix them with expressions, in particular, lambda expressions. Instead of defining a full-blown function to replace print()
with, you can make an anonymous lambda expression that calls it:
download('/js/app.js', lambda msg: print('[INFO]', msg))
#
# [INFO] Downloading /js/app.js
However, because a lambda expression is defined in place, thereâs no way of referring to it elsewhere in the code.
Note
In Python, you canât put statements, such as assignments, conditional statements, loops, and so on, in an anonymous lambda function. It has to be a single expression!
Another kind of expression is a ternary conditional expression:
user = 'jdoe'
print('Hi!') if user is None else print(f'Hi, {user}.')
#
# Hi, jdoe.
Python has both conditional statements and conditional expressions. The latter is evaluated to a single value that can be assigned to a variable or passed to a function. In the example above, youâre interested in the side-effect rather than the value, which evaluates to None
, so you simply ignore it.
As you can see, functions allow for an elegant and extensible solution, which is consistent with the rest of the language. In the next subsection, youâll discover how not having print()
as a function caused a lot of headaches.
print
Was a Statement in Python 2
A statement is an instruction that may evoke a side-effect when executed but never evaluates to a value. In other words, you wouldnât be able to print a statement or assign it to a variable like this:
result = print 'hello world'
Thatâs a syntax error in Python 2. Here are a few more examples of statements in Python:
- assignment:
=
- conditional:
if
- loop:
while
- assertion:
assert
Note
Python 3.8 brings a controversial walrus operator (:=
), which is an assignment expression. With it, you can evaluate an expression and assign the result to a variable at the same time, even within another expression!
Take a look at this example, which calls an expensive function once and then reuses the result for further computation:
values = [y := f(x), y**2, y**3]
This is useful for simplifying the code without losing its efficiency. Typically, performant code tends to be more verbose:
y = f(x)
values = [y, y**2, y**3]
The controversy behind this new piece of syntax caused a lot of argument. An abundance of negative comments and heated debates eventually led Guido van Rossum to step down from the Benevolent Dictator For Life or BDFL position.
Statements are usually comprised of reserved keywords such as if
, for
, or print
that have fixed meaning in the language. You canât use them to name your variables or other symbols. Thatâs why redefining or mocking the print
statement isnât possible in Python 2. Youâre stuck with what you get.
Furthermore, you canât print from anonymous functions, because statements arenât accepted in lambda expressions:
lambda: print 'hello world'
#
# File "<stdin>", line 1
# lambda: print 'hello world'
# ^
# SyntaxError: invalid syntax
The syntax of the print
statement is ambiguous. Sometimes you can add parentheses around the message, and theyâre completely optional:
print 'Please wait...'
#
# Please wait...
print('Please wait...')
#
# Please wait...
At other times they change how the message is printed:
print 'My name is', 'John'
#
# My name is John
print('My name is', 'John')
#
# ('My name is', 'John')
String concatenation can raise a TypeError
due to incompatible types, which you have to handle manually, for example:
values = ['jdoe', 'is', 42, 'years old']
print ' '.join(map(str, values))
#
# jdoe is 42 years old
Compare this with similar code in Python 3, which leverages sequence unpacking:
values = ['jdoe', 'is', 42, 'years old']
print(*values) # Python 3
#
# jdoe is 42 years old
There arenât any keyword arguments for common tasks such as flushing the buffer or stream redirection. You need to remember the quirky syntax instead. Even the built-in help()
function isnât that helpful with regards to the print
statement:
help(print)
#
# File "<stdin>", line 1
# help(print)
# ^
# SyntaxError: invalid syntax
Trailing newline removal doesnât work quite right, because it adds an unwanted space. You canât compose multiple print
statements together, and, on top of that, you have to be extra diligent about character encoding.
The list of problems goes on and on. If youâre curious, you can jump back to the previous section and look for more detailed explanations of the syntax in Python 2. However, you can mitigate some of those problems with a much simpler approach. It turns out the print()
function was backported to ease the migration to Python 3. You can import it from a special __future__
module, which exposes a selection of language features released in later Python versions.
Note
You may import future functions as well as baked-in language constructs such as the with
statement.
To find out exactly what features are available to you, inspect the module:
import __future__
__future__.all_feature_names
#
# ['nested_scopes',
# 'generators',
# 'division',
# 'absolute_import',
# 'with_statement',
# 'print_function',
# 'unicode_literals']
You could also call dir(__future__)
, but that would show a lot of uninteresting internal details of the module.
To enable the print()
function in Python 2, you need to add this import statement at the beginning of your source code:
from __future__ import print_function
From now on the print
statement is no longer available, but you have the print()
function at your disposal. Note that it isnât the same function like the one in Python 3, because itâs missing the flush
keyword argument, but the rest of the arguments are the same.
Other than that, it doesnât spare you from managing character encodings properly.
Hereâs an example of calling the print()
function in Python 2:
from __future__ import print_function
import sys
print('I am a function in Python', sys.version_info.major)
#
# I am a function in Python 2
You now have an idea of how printing in Python evolved and, most importantly, understand why these backward-incompatible changes were necessary. Knowing this will surely help you become a better Python programmer.