Tags

, , , ,

Simple Tricks started late last year with a “project” post that may have been a misfire, though it’s possible I’ll do some other simple projects in the future (though my question is whether they’re really “simple tricks” — I’m not sure the first post in the series qualified).

Regardless, since then we’ve looked at Python comprehensions (see here and here), file handling techniques (see here and here), and function parameters (see here and here). This time we look at printing output with an emphasis on formatted output.

The obvious starting place is Python’s built-in print function:

001| from sys import stdout
002| 
003| print(“Hello, beautiful World!”)
004| print(“Hello,”, “beautiful”, “World!”)
005| print(“Hello, beautiful World!”, file=stdout)
006| 
007| print(“Hello, “, “beautiful “, “World!”, sep=)
008| 
009| print(“Hello”, end=)
010| print(“, “, end=)
011| print(“beautiful”, end=)
012| print(” “, end=)
013| print(“World”, end=)
014| print(“!”, end=“\n\n”, flush=)
015| 
016| print(“Sum:”, end=‘ ‘, file=stdout)
017| print(1,2,3,4,5, sep=“+”, end=‘ ‘, file=stdout)
018| print(“=”, 15, file=stdout)
019| 

When run, this prints:

Hello, beautiful World!
Hello, beautiful World!
Hello, beautiful World!
Hello, beautiful World!
Hello, beautiful World!

Sum: 1+2+3+4+5 = 15

The code is divided into four sections, each illustrating an aspect of the print function.

The first section prints “Hello, beautiful World!” three times using print in the more common ways. The first (line #3) shows the simplest, printing a single fully formed string. The second (line #4) shows how print can take multiple arguments. When it does, it prints them separated by spaces (by default). The third (line #5) uses the file keyword argument to redirect the output to a given file. In this case, that file is the system’s sys.stdout stream, which is what print uses by default (so here nothing changes). Normally, the file keyword is used to direct output to a disk file (see below for an example).

The second section has just one print statement (line #7). It uses multiple arguments like the one on line #4, but here the sep keyword changes the default separator to an empty string. This differs from the line #4 case in then having to supply the needed spaces at the ends of the words. The separator can be set to any string. Note that sep=None defaults to the space separator, so use sep='' to remove separation between multiple print arguments.

The third section (lines #9 through #13) demonstrates the other two keyword parameters, end, which sets the print end character; and flush, which flushes any output buffer to the output stream. A case like this might occur when generating output token by token. Similar to the sep keyword, setting end='' in lines #9 through #12 disables the default newline ('\n') character (while using end=None enables the default). And as with the sep keyword, end can be set to any string. Line #13 sets it to two newline characters to get a double-spaced line. The flush keyword is handy when to ensure output is written to file before proceeding. It’s also useful to synchronize using buffered and nonbuffered methods for an output stream (something you should try to avoid).

The fourth section changes things up, using a variety of print keywords. The example might seem a bit contrived, so here’s a somewhat more realistic use case:

001| def print_sum (lines):
002|     print(‘Sum:’, end=‘ ‘)
003|     print(*lines, sep=‘ + ‘, end=‘ ‘)
004|     print(‘=’, sum(lines), end=‘\n\n’)
005| 
006| print_sum([2,2])
007| print_sum([1,2,3,4,5,6,7,8,9])
008| print_sum([21,42,63,84])
009| 

A function that prints the sum of any list of numbers. When run it prints:

Sum: 2 + 2 = 4

Sum: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = 45

Sum: 21 + 42 + 63 + 84 = 210

The technique can be tailored as required, but there are better options for generating structured output.


We’ll get to those, but before we continue, here’s an example of using the file keyword to print to a file:

001| from sys import stdout
002| 
003| def print_to_file (strings, file=stdout):
004|     ”’Print list of strings to a file.”’
005|     for line in strings:
006|         print(line, file=file)
007| 
008| def print_to_filename (strings, filename, encoding=‘utf8’):
009|     fp = open(filename, mode=‘w’, encoding=encoding)
010|     try:
011|         print_to_file(strings, file=fp)
012|     except:
013|         raise
014|     finally:
015|         fp.close()
016|         print(f’wrote: {filename}’)
017| 
018| stuff = [
019|     “Hello, beautiful World!”,
020|     “”,
021|     “This is a short list of strings.”,
022|     “They don’t say anything interesting.”,
023|     “They’re just here as an example.”,
024|     “A real case would be more interesting!”,
025|     “”,
026|     “Bye for now.”,
027|     “”
028| ]
029| print_to_file(stuff)
030| print_to_filename(stuff, ‘stuff.text’)
031| 

When run, this saves the text to a file and prints:

Hello, beautiful World!

This is a short list of strings.
They don't say anything interesting.
They're just here as an example.
A real case would be more interesting!

Bye for now.

wrote: stuff.text

The two functions allow users to print a list of strings to the screen (as in line #29) or to a specified file (as in line #30). The output shows the result of both. First, a listing of the strings, then the information about writing to the file.

(Remember to always wrap file use in a try block! This is especially important for output files to ensure they are properly closed. You can also use the with construction.)


As some of the examples above illustrate, one common need when printing is joining strings together. Another is formatting text for more readable output, especially when printing anything table-like. Python offers several approaches to both of these.

One case I encounter frequently is having a list of strings I need to join together, either just end-to-end or with a delimiter string of some sort between each list member. A very common example is a list of strings meant as lines of text (as in lines #18-#28 in the previous example). To display them on separate lines, we can print them one-by-one to (as in lines #5-#6 the same example).

We can also use the str.join method to concatenate the strings in the list to a single string for a single print statement. We can join the strings with the between-string delimiter of our choice:

001| stuff = [
002|     “Hello, beautiful World!”,
003|     “”,
004|     “This is a short list of strings.”,
005|     “They don’t say anything interesting.”,
006|     “They’re just here as an example.”,
007|     “A real case would be more interesting!”,
008|     “”,
009|     “Bye for now.”,
010|     “”
011| ]
012| lines = ‘\n’.join(stuff)
013| print(lines)
014| 

In this case the delimiter we wanted was the newline character, but it can be any string desired. The output is similar to the print_to_file example above (line #29).

For no delimiter at all, use an empty string:

001| stuff = [‘Hello’, ‘,’, ‘ ‘, ‘beautiful’, ‘ ‘, ‘World’, ‘!’]
002| 
003| words = .join(stuff)
004| print(words)
005| 

This prints “Hello, beautiful World!” (similar to the first example above).

Sometimes the list doesn’t contain strings but numbers or other objects. In that case we can use a list comprehension to get a list of strings to join:

001| stuff = [1, 2, 3, 4, 5, 6, 7, 8]
002| 
003| numbers = ‘+’.join(str(n) for n in stuff)
004| print(numbers)
005| 

Note that, without the square brackets around it, the comprehension in line #3 is in generator form (see Simple Tricks #3 for details).

Another common way to join a small number of strings, especially if there is also surrounding text, uses Python’s original string formatting (which is based on the C/C++ printf family of functions). One advantage is the ability to handle diverse data types:

001| from math import pi, e
002| 
003| print(‘pi = %.6f’ % pi)
004| print(‘e = %.6f’ % (e,))
005| 
006| name = ‘foobar’
007| ext = ‘dat’
008| print(‘file: %s.%s’ % (name, ext))
009| 
010| args = (‘div’, 21, 42, 0.5)
011| print(‘%s %d %d = %.2f’ % args)
012| 

When run, this prints:

pi = 3.141593
e  = 2.718282
file: foobar.dat
div 21 42 = 0.50

The format is:

str % tuple

Where the str contains placeholder codes and tuple contains the arguments for those placeholders (see printf-style String Formatting in the Python docs for syntax and semantics). The exception is that a single argument doesn’t require the tuple (as in line #3 but does allow single member tuples as in line #4).


The printf system for formatted text exists in many programming languages, but Python has a formatting trick of its own. This trick is behind Python’s built-in format function and the str.format method. The syntax for the function is:

format(object, format_spec)

Where value is the object to be formatted, and format_spec is a string with formatting codes for value.

Here’s a simple example of the format function in action:

001| print(‘|’, format(42)       , ‘|’, sep=)
002| print(‘|’, format(42, )   , ‘|’, sep=)
003| print(‘|’, format(42, ‘4d’) , ‘|’, sep=)
004| print(‘|’, format(42, ’04x’), ‘|’, sep=)
005| print(‘|’, format(42, ‘.1f’), ‘|’, sep=)
006| print()
007| 

When run, this prints:

|42|
|42|
|  42|
|002a|
|42.0|

That example used multiple print arguments and set the separator to an empty string. Here’s another (easier) way to accomplish the same output:

001| print(‘|%s|’ % format(42))
002| print(‘|%s|’ % format(42, ))
003| print(‘|%s|’ % format(42, ‘4d’))
004| print(‘|%s|’ % format(42, ’04x’))
005| print(‘|%s|’ % format(42, ‘.1f’))
006| print()
007| 

These examples illustrate how the format function works. It takes the object to format as the (required) first argument. The (optional) second argument is a string with codes from the Format Specification Mini-Language. If the second argument is an empty string or not provided (it cannot be None), format uses the default for the object (explained below).

Note that you can also accomplish the same output with printf formatting:

001| print(‘|%d|’ % 42)
002| print(‘|%s|’ % 42)
003| print(‘|%4d|’ % 42)
004| print(‘|%04x|’ % 42)
005| print(‘|%.1f|’ % 42)
006| print()
007| 

Which may be easier for simple things (and see below about f-strings). As we’ll see, though, the format function (and str.format method) provide some extra functionality.


Before we get there, let’s take a look at how the str.format method works. The syntax is:

str.format(*args, **kwargs)

Where str contains a multiple-argument format specification (using the String Format Syntax) and the arguments, positional or keyword, provide the values to fulfill the format. Here’s an example using positional arguments:

001| fmt = ‘{0}: {1} = {2}’
002| args = [42, ‘red’, 3.14159]
003| 
004| print(‘{0}: {1} = {2}’.format(42, ‘red’, 3.14159))
005| print(‘{1}: {2} = {3}’.format(0, 42, ‘red’, 3.14159))
006| print(fmt.format(*args))
007| print()
008| 

The basic idea is to indicate print fields with curly braces. Inside the braces, for positional arguments, use the index of the argument.

Notice how — if you really hate indexing from zero (line #4) — you can index from one (line #5) if you provide a dummy first argument.

When run, this prints:

42: red = 3.14159
42: red = 3.14159
42: red = 3.14159

Here’s an example that uses keyword arguments:

001| fmt = ‘{ix}: {name} = {value}’
002| kwargs = dict(ix=63, name=‘red’, value=2.718)
003| 
004| print(fmt.format(ix=63, name=‘red’, value=2.718))
005| print(fmt.format(**kwargs))
006| print()
007| 

Note how we reuse the format string (lines #1 and #4-#5).

When run, this prints:

63: red = 2.718
63: red = 2.718

The method allows using both positional and keyword arguments:

001| fmt = ‘<{1},{0}> {name} = {value} ({code}:{2})’
002| args = (2.1, 4.2, 68)
003| kwargs = {‘name’:‘red’, ‘value’:2.718, ‘code’:19}
004| 
005| print(fmt.format(*args, **kwargs))
006| print()
007| 

Note that positional arguments can be used out of order (and can be repeated). Failing to provide an indexed or named argument causes an error but extra arguments are ignored.

When run, this prints:

<4.2,2.1> red = 2.718 (19:68)

To be honest, I’ve never found much use for the str.format method (or the format function). In the past, I used the printf style of formatting and never needed these. Now, I use the f-string style and still don’t have much use for these, but understanding what lies behind them is useful.


The format function and the str.format method both depend on the object.__format__ method (“dunder format”) — one of the many special names Python reserves for object methods. Both the format function and the str.format method call an object’s dunder format to get a formatted string representing that object’s value(s).

The built-in classes (int, float, list, and so on) all define a dunder format method to handle formatting for objects of their type. The formatting codes accepted depend on the type. This is noticeable mainly between integer and float types. See the Format String Syntax specification for details.

This means that, if your classes implement the dunder format method, your objects can use the same formatting system. What formatting codes your class uses is entirely up to you. No codes are necessary if you have a fixed way of formatting and just want to present a “nice” string version to the format function or str.format method. The syntax for the method is:

object.__format__(self, format_spec)

Where format_spec is the string passed to the format function.

Here’s an example of implementing dunder format and then using the format function to display the object differently than from the str or repr functions (and their associated dunder methods in the class):

001| class xypoint:
002|     def __init__ (self, x=0.0, y=0.0):
003|         self.x = x
004|         self.y = y
005| 
006|     def __call__ (self):
007|         return (self.x, self.y)
008| 
009|     def __format__ (self, fmt):
010|         s = ‘[%s, %s]’
011|         t = (format(self.x,fmt), format(self.y,fmt))
012|         return s % t
013| 
014|     def __str__ (self):
015|         s = ‘[%.2f, %.2f]’
016|         return s % self()
017| 
018|     def __repr__ (self):
019|         s = ‘{“%s”:{“x”:%.2f, “y”:%.2f}}’
020|         t = (type(self).__name__, *self())
021|         return s % t
022| 
023| pt = xypoint()
024| print(repr(pt))
025| print(str(pt))
026| print(format(pt, ‘.6f’))
027| print(format(pt, ‘.3e’))
028| print()
029| 

The intention is controlling of the format of the X and Y elements. The allowed format specification is the same as allowed for float objects. In fact, formatting is delegated to those objects. The format string is passed to them.

When run, this prints:

{"xypoint":{"x":0.00, "y":0.00}}
[0.00, 0.00]
[0.000000, 0.000000]
[0.000e+00, 0.000e+00]

As an aside, you should always define either dunder str or dunder repr. Preferably both. (Always implement toString.) I like to have dunder repr emit a JSON or other persistence-type string and dunder str emit a “pretty print” version of the object.

Note that implementing dunder str is itself a technique for formatted printing.


To jump directly to the final topic, format strings (“f-strings”), here’s how accomplish the same output as just above with f-strings:

001| from examples import xypoint
002| 
003| pt = xypoint()
004| print(f'{pt!r}’)
005| print(f'{pt!s}’)
006| print(f'{pt:.6f}’)
007| print(f'{pt:.3e}’)
008| print()
009| 

Format strings have a leading f (just as raw strings have a leading r). See Formatted string literals for the documentation on f-strings. Format strings essentially follow the str.format convention with curly braces defining print fields. Importantly, as the example shows, f-strings depend on the object’s dunder format method, so f-strings can include user objects (as above).

Here are some examples showing off ways to print numbers:

001| d = 11
002| n = 640_000_000_000
003| f = 1234.5678
004| 
005| print(f’|{d}|’)
006| print(f’|{d:6d}|’)
007| print(f’|{d:<6d}|’)
008| print(f’|{d:06d}|’)
009| print(f’|{d:06x}|’)
010| print()
011| 
012| print(f’|{n}|’)
013| print(f’|{n:_}|’)
014| print(f’|{n:,}|’)
015| print(f’|{n:.2e}|’)
016| print()
017| 
018| print(f’|{f}|’)
019| print(f’|{f:.3f}|’)
020| print(f’|{f:.2e}|’)
021| print(f’|{f:+012.3f}|’)
022| print(f’|{f:,.2f}|’)
023| print()
024| 

The syntax is very similar to that used in the printf functions, but with some extras from Python (like the ‘<‘ that left justifies the field).

When run, this prints:

|11|
|    11|
|11    |
|000011|
|00000b|

|640000000000|
|640_000_000_000|
|640,000,000,000|
|6.40e+11|

|1234.5678|
|1234.568|
|1.23e+03|
|+0001234.568|
|1,234.57|

Here are some examples showing off strings:

001| s = ‘Hello!’
002| emoj = ‘\U0001f60e’
003| 
004| print(f’|{s:.<20}|’)
005| print(f’|{s:=^20}|’)
006| print(f’|{s:_>20}|’)
007| print()
008| print(f'{s!s} {emoj!s}’)
009| print(f'{s!r} {emoj!r}’)
010| print(f'{s!a} {emoj!a}’)
011| print()
012| 

When run, this prints:

|Hello!..............|
|=======Hello!=======|
|______________Hello!|

Hello! 😎
'Hello!' '😎'
'Hello!' '\U0001f60e'

You can combine raw strings and format strings:

001| currdir = r’PIL’
002| basedir = rf’C:\users\wyrd\python\blog\{currdir}’
003| 
004| print(basedir)
005| print()
006| 
007| # Better way…
008| from os import path
009| 
010| prefix = r’C:\users\wyrd\python\blog’
011| basedir = path.join(prefix, currdir)
012| 
013| print(basedir)
014| print()
015| 

The r and f can be in any order. In this case, it’s better to use the os.path function or Pythons’s pathlib module, but the example illustrates that you can combine raw and format strings.

Here’s an example showing datetime formatting:

001| from datetime import datetime
002| 
003| d = datetime.now()
004| fmt = ‘%Y-%m-%d %H:%M:%S’
005| 
006| print(f'{d!s}’)
007| print(f'{d!r}’)
008| print(f'{d!a}’)
009| print()
010| print(f'{d:%c}’)
011| print(f'{d:%A %B %d (%Y)}’)
012| print(f'{d:%Y-%m-%d %H:%M:%S}’)
013| print(f'{d:{fmt}}’)
014| print()
015| 

When run, this prints:

2024-07-31 18:38:03.823797
datetime.datetime(2024, 7, 31, 18, 38, 3, 823797)
datetime.datetime(2024, 7, 31, 18, 38, 3, 823797)

Wed Jul 31 18:38:03 2024
Wednesday July 31 (2024)
2024-07-31 18:38:03
2024-07-31 18:38:03

The last example illustrates an f-string feature handy for debugging:

001| x = 2.71828
002| y = 3.14159
003| 
004| print(f'{x=}, {y=}’)
005| print()
006| print(f'{x = }, {y = }’)
007| print()
008| 

When run, this prints:

x=2.71828, y=3.14159

x = 2.71828, y = 3.14159

Note how the spaces around the equals sign are preserved.

That’s it for this time. There will be more in the future.


Link: Zip file containing all code fragments used in this post.