Universal print syntax for Python 2 and 3

Often I see python programmers in python2 writing code like

print ('Hello World')

claiming “this will work in both python 2 and 3!” This is a noble pursuit, but the approach is misguided. In python3 the above behaves as a call to the print() function with a single argument, in python2 it is a print statement followed by a parenthesized expression. Once there are two arguments though, it fails

print ('Hello', 'World')

Under python3 this works as expected, but under python2 there is only one object following the print, and that object is a tuple. This will print the tuple as-is

('Hello', 'World')

So this piece of common misinformation is easily dismissed, the above is by no means universal since we can’t have multiple arguments. A real attempt at a universal print function means there can only be one object that is printed. The next step is to use join.

print (' '.join(('Hello', 'World')))

Great! call join with a tuple of ‘Hello’ and ‘World’, the separator space will do what printing normally does, then wrap that in parentheses to get back to step one (something that works with the print function). We also get the ability to use a custom separator when python2’s print only allows a space. It’s starting to seem like a lot of different things happening and that leaves room for human error. But there is a bigger problem. What happens when something other than a string is passed?

print (' '.join(('Hello', 'World', 2)))

join only works with strings, so now we have the error

TypeError: sequence item 2: expected string, int found

but that’s easily fixed by tossing a generator expression inside the join call to convert each item in the tuple to as string.

print (' '.join(str(e) for e in ('Hello', 'World', 2)))

Voila! a cross-python print function in just 5 easy steps

  1. put all of your arguments inside a tuple
  2. wrap that in a generator expression to convert them all to strings
  3. put that inside a call to ' '.join()
  4. wrap that with parentheses
  5. instert a print in front

Though there are two problems that remain: printing without a newline and printing to a file. Printing to a file can be achieved by changing stdout during the print call. Assuming we have a file object f

out, sys.stdout = sys.stdout, f
print (' '.join(str(e) for e in ('Hello', 'World', 2)))
sys.stdout = out

Change stdout to be f during the print call, then change it back after. I would strongly recommend putting that inside of a context manager (which exists in python3.4 stdlib) in case printing raises an exception.

If you can figure out a way to equate end='' with a trailing comma let me know.

Though at this level of complexity you might as well be using sys.stdout.write since getting equality means throwing out most of the convenience that print offers.

What you should take away from this is that there isn’t a simple way to write print syntax that works with both python2 and python3. If it’s available, in python2 I generally recommend

from __future__ import print_function

so you can just use the real python3 print syntax in python2. If it’s not, then write your own print function. If none of this is an option just use one or the other!. 2to3 can convert someday if you ever need to.

If you’re considering using what I’ve demonstrated here, please realize that this post is meant to show that trying to accomplish a universal print syntax is a mess. Don’t do this in real code.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s