6
Writing Pythonic Code

Powerful is a meaningless adjective for programming languages. Every programming language describes itself as powerful: the official Python Tutorial begins with the sentence “Python is an easy to learn, powerful programming language.” But there’s no algorithm that one language can do that another can’t, and no unit of measurement to quantify a programming language’s “power” (although you certainly can measure the volume at which programmers argue for their favorite language).

But every language does have its own design patterns and gotchas that make up its strengths and weaknesses. To write Python code like a true Pythonista, you’ll need to know more than just the syntax and standard library. The next step is to learn its idioms, or Python-specific coding practices. Certain Python language features lend themselves to writing code in ways that have become known as pythonic.

In this chapter, I’ll provide several common ways of writing idiomatic Python code along with their unpythonic counterparts. What counts as pythonic can vary from programmer to programmer, but it commonly includes the examples and practices I discuss here. Experienced Python programmers use these techniques, so becoming familiar with them allows you to recognize them in real-world code.

The Zen of Python

The Zen of Python by Tim Peters is a set of 20 guidelines for the design of the Python language and for Python programs. Your Python code doesn’t necessarily have to follow these guidelines, but they’re good to keep in mind. The Zen of Python is also an Easter egg, or hidden joke, that appears when you run import this:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
--snip--

In the end, these guidelines are opinions that programmers can argue for or against. Like all good sets of moral codes, they contradict themselves to provide the most flexibility. Here’s my interpretation of these aphorisms:

  1. Beautiful is better than ugly.  Beautiful code can be thought of as easy to read and understand. Programmers often write code quickly without concern for readability. The computer will run unreadable code, but unreadable code is difficult for human programmers to maintain and debug. Beauty is subjective, but code that is written without regard for how understandable it is often ugly to others. The reason for Python’s popularity is that its syntax isn’t cluttered with cryptic punctuation marks like other languages, making it easy to work with.
  2. Explicit is better than implicit.  If I’d written only “This is self-explanatory,” I would have provided a terrible explanation for this aphorism. Similarly, in code, it’s best to be verbose and explicit. You should avoid hiding code’s functionality behind obscure language features that require deep language familiarity to fully understand.
  3. Simple is better than complex. Complex is better than complicated.  These two aphorisms remind us that we can build anything with simple or complex techniques. If you have a simple problem that requires a shovel, it’s overkill to use a 50-ton hydraulic bulldozer. But for an enormous job, the complexity of operating a single bulldozer is preferable to the complications of coordinating a team of 100 shovelers. Prefer simplicity to complexity, but know the limits of simplicity.
  4. Flat is better than nested.  Programmers love to organize their code into categories, especially categories that contain subcategories that contain other sub-subcategories. These hierarchies often don’t add organization so much as they add bureaucracy. It’s okay to write code in just one top-level module or data structure. If your code looks like spam.eggs.bacon.ham() or spam['eggs']['bacon']['ham'], you’re making your code too complicated.
  5. Sparse is better than dense.  Programmers often like to cram as much functionality into as little code as possible, as in the following line: print('\n'.join("%i bytes = %i bits which has %i possiblevalues." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8)))). Although code like this might impress their friends, it’ll infuriate their co-workers who have to try to understand it. Don’t make your code try to do too much at once. Code that is spread out over multiple lines is often easier to read than dense one-liners. This aphorism is roughly the same as simple is better than complex.
  6. Readability counts.  Although strcmp() might obviously mean the “string compare” function to someone who has been programming in C since the 1970s, modern computers have enough memory to write out the full function name. Don’t drop letters from your names or write overly terse code. Take the time to come up with descriptive, specific names for your variables and functions. A blank line in between sections of your code can serve the same function as paragraph breaks in a book, letting the reader know which parts are meant to be read together. This aphorism is roughly the same as beautiful is better than ugly.
  7. Special cases aren’t special enough to break the rules. Although practicality beats purity.  These two aphorisms contradict each other. Programming is full of “best practices” that programmers should strive for in their code. Skirting these practices for a quick hack might be tempting but can lead to a rat’s nest of inconsistent, unreadable code. On the other hand, bending over backward to adhere to rules can result in highly abstract, unreadable code. For example, the Java programming language’s attempt to fit all code to its object-oriented paradigm often results in lots of boilerplate code for even the smallest program. Walking the line between these two aphorisms becomes easier with experience. In time, you’ll learn not only the rules but also when to break them.
  8. Errors should never pass silently. Unless explicitly silenced.  Just because programmers often ignore error messages doesn’t mean the program should stop emitting them. Silent errors can happen when functions return error codes or None instead of raising exceptions. These two aphorisms tell us that it’s better for a program to fail fast and crash than to silence the error and continue running. The bugs that inevitably happen later on will be harder to debug because they’re detected long after the original cause. Although you can always decide to explicitly ignore the errors your programs cause, be sure you’re making the conscious choice to do so.
  9. In the face of ambiguity, refuse the temptation to guess.  Computers have made humans superstitious: to exorcise the demons in our computers, we perform the sacred ritual of turning them off and then on. Supposedly this will fix any mysterious problem. But computers are not magic. If your code isn’t working, there’s a reason why, and only careful, critical thinking will solve the problem. Refuse the temptation to blindly try solutions until something seems to work; often, you’ve merely masked the problem rather than solved it.
  10. There should be one—and preferably only one—obvious way to do it.  This is a broadside against the Perl programming language’s motto, “There’s more than one way to do it!” It turns out that having three or four different ways to write code that does the same task is a double-edged sword: you have flexibility in how you write code, but now you have to learn every possible way it could have been written to read other people’s code. This flexibility isn’t worth the increased effort needed to learn a programming language.
  11. Although that way may not be obvious at first unless you’re Dutch.  This line is a joke. Guido van Rossum, the creator of Python, is Dutch.
  12. Now is better than never. Although never is often better than *right* now.  These two aphorisms tell us that code that runs slowly is obviously worse than code that runs quickly. But it’s better to have to wait for your program to finish than to finish it too early with incorrect results.
  13. If the implementation is hard to explain, it’s a bad idea. If the implementation is easy to explain, it may be a good idea.  Many things get more complicated over time: tax laws, romantic relationships, Python programming books. Software is no different. These two aphorisms remind us that if code is so complicated as to be impossible for programmers to understand and debug, it’s bad code. But just because it’s easy to explain a program’s code to someone else doesn’t mean it isn’t bad code. Unfortunately, figuring out how to make code as simple as possible, and not any simpler, is hard.
  14. Namespaces are one honking great idea—let’s do more of those!  Namespaces are separate containers for identifiers to prevent naming conflicts. For example, the open() built-in function and the webbrowser.open() function have the same name but refer to different functions. Importing webbrowser doesn’t overwrite the built-in open() function because both open() functions exist in different namespaces: the built-in namespace and the webbrowser module’s namespace, respectively. But keep in mind that flat is better than nested: as great as namespaces are, you should make them only to prevent naming conflicts, not to add needless categorization.

As with all opinions about programming, you can argue against those I’ve listed here, or they might simply be irrelevant to your situation. Arguing over how you should write code or what counts as “pythonic” is rarely as productive as you think it is. (Unless you’re writing an entire book full of programming opinions.)

Learning to Love Significant Indentation

The most common concern I hear about Python from programmers coming from other languages is that Python’s significant indentation (often mistakenly called significant whitespace) is weird and unfamiliar. The amount of indentation at the start of a line of code has meaning in Python, because it determines which lines of code are in the same code block.

Grouping blocks of Python code using indentation can seem odd, because other languages begin and end their blocks with braces, { and }. But programmers in non-Python languages usually indent their blocks too, just like Python programmers, to make their code more readable. For example, the Java programming language doesn’t have significant indentation. Java programmers don’t need to indent blocks of code, but they often do anyway for readability. The following example has a Java function named main() that contains a single call to a println() function:

// Java Example
public static void main(String[] args) {
    System.out.println("Hello, world!");
}

This Java code would run just fine if the println() line weren’t indented, because the braces, rather than the indentation, are what mark the start and end of blocks in Java. Instead of allowing indentation to be optional, Python forces your code to be consistently readable. But note that Python doesn’t have significant whitespace, because Python doesn’t restrict how you can use nonindentation whitespace (both 2 + 2 and 2+2 are valid Python expressions).

Some programmers argue that the opening brace should be on the same line as the opening statement, while others argue it should be on the following line. Programmers will argue the merits of their preferred style until the end of time. Python neatly sidesteps this issue by not using braces at all, letting Pythonistas get back to more productive work. I’ve come to wish that all programming languages would adopt Python’s approach to grouping blocks of code.

But some people still long for braces and want to add them to a future version of Python—despite how unpythonic they are. Python’s __future__module backports features to earlier Python versions, and you’ll find a hidden Easter egg if you try to import a braces feature into Python:

>>> from __future__ import braces
SyntaxError: not a chance

I wouldn’t count on braces being added to Python any time soon.

Commonly Misused Syntax

If Python isn’t your first programming language, you might write your Python code with the same strategies you use to write code in other programming languages. Or perhaps you learned an unusual way of writing your Python code because you were unaware that there are more established best practices. This awkward code works, but you could save some time and effort by learning more standard approaches to writing pythonic code. This section explains common missteps programmers make and how you should write the code instead.

Use enumerate() Instead of range()

When looping over a list or other sequence, some programmers use the range() and len() functions to generate the index integers from 0 up to, but not including, the length of the sequence. It’s common to use the variable name i (for index) in these for loops. For example, enter the following unpythonic example into the interactive shell:

>>> animals = ['cat', 'dog', 'moose']
>>> for i in range(len(animals)):
...     print(i, animals[i])
...
0 cat
1 dog
2 moose

The range(len()) convention is straightforward but less than ideal because it can be difficult to read. Instead, pass the list or sequence to the built-in enumerate() function, which will return an integer for the index and the item at that index. For example, you can write the following pythonic code:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for i, animal in enumerate(animals):
...     print(i, animal)
...
0 cat
1 dog
2 moose

The code you write will be slightly cleaner using enumerate() instead of range(len()). If you need only the items but not the indexes, you can still directly iterate over the list in a pythonic way:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for animal in animals:
...     print(animal)
...
cat
dog
moose

Calling enumerate() and iterating over a sequence directly are preferable to using the old-fashioned range(len()) convention.

Use the with Statement Instead of open() and close()

The open() function will return a file object that contains methods for reading or writing a file. When you’re done, the file object’s close() method makes the file available to other programs for reading and writing. You can use these functions individually. But doing so is unpythonic. For example, enter the following into the interactive shell to write the text “Hello, world!” to a file named spam.txt:

>>> # Unpythonic Example
>>> fileObj = open('spam.txt', 'w')
>>> fileObj.write('Hello, world!')
13
>>> fileObj.close()

Writing code this way can lead to unclosed files if, say, an error occurs in a try block and the program skips the call to close(). For example:

>>> # Unpythonic Example
>>> try:
...     fileObj = open('spam.txt', 'w')
...     eggs = 42 / 0    # A zero divide error happens here.
...     fileObj.close()  # This line never runs.
... except:
...     print('Some error occurred.')
...
Some error occurred.

Upon reaching the zero divide error, the execution moves to the except block, skipping the close() call and leaving the file open. This can lead to file corruption bugs later that are hard to trace back to the try block.

Instead, you can use the with statement to automatically call close() when the execution leaves the with statement’s block. The following pythonic example does the same task as the first example in this section:

>>> # Pythonic Example
>>> with open('spam.txt', 'w') as fileObj:
...     fileObj.write('Hello, world!')
...

Even though there’s no explicit call to close(), the with statement will know to call it when the execution leaves the block. .

Use is to Compare with None Instead of ==

The == equality operator compares two object’s values, whereas the is identity operator compares two object’s identities. Chapter 7 covers value and identity. Two objects can store equivalent values, but being two separate objects means they have separate identities. However, whenever you compare a value to None, you should almost always use the is operator rather than the == operator.

In some cases, the expression spam == None could evaluate to True even when spam merely contains None. This can happen due to overloading the == operator, which Chapter 17 covers in more detail. But spam is None will check whether the value in the spam variable is literally None. Because None is the only value of the NoneType data type, there is only one None object in any Python program. If a variable is set to None, the is None comparison will always evaluate to True. Chapter 17 describes the specifics of overloading the == operator, but the following is an example of this behavior:

>>> class SomeClass:
...     def __eq__(self, other):
...         if other is None:
...             return True
...
>>> spam = SomeClass()
>>> spam == None
True
>>> spam is None
False

The possibility that a class overloads the == operator this way is rare, but it’s become idiomatic Python to always use is None instead of == None just in case.

Finally, you shouldn’t use the is operator with the values True and False. You can use the == equality operator to compare a value with True or False, such as spam == True or spam == False. Even more common is to leave out the operator and Boolean value altogether, writing code like if spam: or if not spam: instead of if spam == True: or if spam == False:.

Formatting Strings

Strings appear in almost every computer program, no matter the language. This data type is common, so it’s no surprise there are many approaches to manipulating and formatting strings. This section highlights a couple of best practices.

Use Raw Strings If Your String Has Many Backslashes

Escape characters allow you to insert text into string literals that would otherwise be impossible to include. For example, you need the \ in 'Zophie\'s chair' so Python interprets the second quote as part of the string, not the symbol marking the end of the string. Because the backslash has this special escape meaning, if you want to put an actual backslash character in your string, you must enter it as \\.

Raw strings are string literals that have an r prefix, and they don’t treat the backslash characters as escape characters. Instead, they just put the backslashes into the string. For example, this string of a Windows file path requires several escaped backslashes, which isn’t very pythonic:

>>> # Unpythonic Example
>>> print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

This raw string (notice the r prefix) produces the same string value while being more readable:

>>> # Pythonic Example
>>> print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

Raw strings aren’t a different kind of string data type; they’re just a convenient way to type string literals that contain several backslash characters. We often use raw strings to type the strings for regular expressions or Windows file paths, which often have several backslash characters in them that would be a pain to escape individually with \\.

Format Strings with F-Strings

String formatting, or string interpolation, is the process of creating strings that include other strings and has had a long history in Python. Originally, the + operator could concatenate strings together, but this resulted in code with many quotes and pluses: 'Hello, ' + name + '. Today is ' + day + ' and it is ' + weather + '.'. The %s conversion specifier made the syntax a bit easier: 'Hello, %s. Today is %s and it is %s.' % (name, day, weather). Both techniques will insert the strings in the name, day, and weather variables into the string literals to evaluate to a new string value, like this: 'Hello, Al. Today is Sunday and it is sunny.'.

The format() string method adds the Format Specification Mini-Language (https://docs.python.org/3/library/string.html#formatspec), which involves using {} brace pairs in a way similar to the %s conversion specifier. However, the method is somewhat convoluted and can produce unreadable code, so I discourage its use.

But as of Python 3.6, f-strings (short for format strings) offer a more convenient way to create strings that include other strings. Just like how raw strings are prefixed with an r before the first quote, f-strings are prefixed with an f. You can include variable names in between braces in the f-string to insert the strings stored in those variables:

>>> name, day, weather = 'Al', 'Sunday', 'sunny'
>>> f'Hello, {name}. Today is {day} and it is {weather}.'
'Hello, Al. Today is Sunday and it is sunny.'

The braces can contain entire expressions as well:

>>> width, length = 10, 12
>>> f'A {width} by {length} room has an area of {width * length}.'
'A 10 by 12 room has an area of 120.'

If you need to use a literal brace inside an f-string, you can escape it with an additional brace:

>>> spam = 42
>>> f'This prints the value in spam: {spam}'
'This prints the value in spam: 42'
>>> f'This prints literal curly braces: {{spam}}'
'This prints literal curly braces: {spam}'

Because you can put variable names and expressions inline inside the string, your code becomes more readable than using the old ways of string formatting.

All of these different ways to format strings go against the Zen of Python aphorism that there should be one—and preferably only one—obvious way to do something. But f-strings are an improvement to the language (in my opinion), and as the other guideline states, practicality beats purity. If you’re writing code for Python 3.6 or later only, use f-strings. If you’re writing code that might be run by earlier Python versions, stick to the format() string method or %s conversion specifiers.

Making Shallow Copies of Lists

The slice syntax can easily create new strings or lists from existing ones. Enter the following into the interactive shell to see how it works:

>>> 'Hello, world!'[7:12] # Create a string from a larger string.
'world'
>>> 'Hello, world!'[:5] # Create a string from a larger string.
'Hello'
>>> ['cat', 'dog', 'rat', 'eel'][2:] # Create a list from a larger list.
['rat', 'eel']

The colon (:) separates the starting and ending indexes of the items to put in the new list you’re creating. If you omit the starting index before the colon, as in 'Hello, world!'[:5], the starting index defaults to 0. If you omit the ending index after the colon, as in ['cat', 'dog', 'rat', 'eel'][2:], the ending index defaults to the end of the list.

If you omit both indexes, the starting index is 0 (the start of the list) and the ending index is the end of the list. This effectively creates a copy of the list:

>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = spam[:]
>>> eggs
['cat', 'dog', 'rat', 'eel']
>>> id(spam) == id(eggs)
False

Notice that the identities of the lists in spam and eggs are different. The eggs = spam[:] line creates a shallow copy of the list in spam, whereas eggs = spam would copy only the reference to the list. But the [:] does look a bit odd, and using the copy module’s copy() function to produce a shallow copy of the list is more readable:

>>> # Pythonic Example
>>> import copy
>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = copy.copy(spam)
>>> id(spam) == id(eggs)
False

You should know about this odd syntax in case you come across Python code that uses it, but I don’t recommend writing it in your own code. Keep in mind that both [:] and copy.copy() create shallow copies.

Pythonic Ways to Use Dictionaries

Dictionaries are at the core of many Python programs because of the flexibility that key-value pairs (discussed further in Chapter 7) provide by mapping one piece of data to another. Therefore, it’s useful to learn about some of the dictionary idioms Python code commonly uses.

For further information about dictionaries, consult Python programmer Brandon Rhodes’s incredible talks about dictionaries and how they work: “The Mighty Dictionary” at PyCon 2010, viewable at https://invpy.com/mightydictionary, and “The Dictionary Even Mightier” at PyCon 2017, viewable at https://invpy.com/dictionaryevenmightier.

Use get() and setdefault() with Dictionaries

Trying to access a dictionary key that doesn’t exist will result in a KeyError error, so programmers will often write unpythonic code to avoid the situation, like this:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
...     print('I have', numberOfPets['cats'], 'cats.')
... else:
...     print('I have 0 cats.')
...
I have 0 cats.

This code checks whether the string 'cats' exists as a key in the numberOfPets dictionary. If it does, a print() call accesses numberOfPets['cats'] as part of a message for the user. If it doesn’t, another print() call prints a string without accessing numberOfPets['cats'] so it doesn’t raise a KeyError.

This pattern happens so often that dictionaries have a get() method that allows you to specify a default value to return when a key doesn’t exist in the dictionary. The following pythonic code is equivalent to the previous example:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> print('I have', numberOfPets.get('cats', 0), 'cats.')
I have 0 cats.

The numberOfPets.get('cats', 0) call checks whether the key 'cats' exists in the numberOfPets dictionary. If it does, the method call returns the value for the 'cats' key. If it doesn’t, it returns the second argument, 0, instead. Using the get() method to specify a default value to use for nonexistent keys is shorter and more readable than using if-else statements.

Conversely, you might want to set a default value if a key doesn’t exist. For example, if the dictionary in numberOfPets doesn’t have a 'cats' key, the instruction numberOfPets['cats'] += 10 would result in a KeyError error. You might want to add code that checks for the key’s absence and sets a default value:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' not in numberOfPets:
...     numberOfPets['cats'] = 0
...
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

But because this pattern is also common, dictionaries have a more pythonic setdefault() method. The following code is equivalent to the previous example:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

If you’re writing if statements that check whether a key exists in a dictionary and sets a default value if the key is absent, use the setdefault() method instead.

Use collections.defaultdict for Default Values

You can use the collections.defaultdict class to eliminate KeyError errors entirely. This class lets you create a default dictionary by importing the collections module and calling collections.defaultdict(), passing it a data type to use for a default value. For example, by passing int to collections.defaultdict(), you can make a dictionary-like object that uses 0 for a default value of nonexistent keys. Enter the following into the interactive shell:

>>> import collections
>>> scores = collections.defaultdict(int)
>>> scores
defaultdict(<class 'int'>, {})
>>> scores['Al'] += 1 # No need to set a value for the 'Al' key first.
>>> scores
defaultdict(<class 'int'>, {'Al': 1})
>>> scores['Zophie'] # No need to set a value for the 'Zophie' key first.
0
>>> scores['Zophie'] += 40
>>> scores
defaultdict(<class 'int'>, {'Al': 1, 'Zophie': 40})

Note that you’re passing the int() function, not calling it, so you omit the parentheses after int in collections.defaultdict(int). You can also pass list to use an empty list as the default value. Enter the following into the interactive shell:

>>> import collections
>>> booksReadBy = collections.defaultdict(list)
>>> booksReadBy['Al'].append('Oryx and Crake')
>>> booksReadBy['Al'].append('American Gods')
>>> len(booksReadBy['Al'])
2
>>> len(booksReadBy['Zophie']) # The default value is an empty list.
0

If you need a default value for every possible key, it’s much easier to use collections.defaultdict() than use a regular dictionary and constantly call the setdefault() method.

Use Dictionaries Instead of a switch Statement

Languages such as Java have a switch statement, which is a kind of if-elif-else statement that runs code based on which one of many values a specific variable contains. Python doesn’t have a switch statement, so Python programmers sometimes write code like the following example, which runs a different assignment statement based on which one of many values the season variable contains:

# All of the following if and elif conditions have "season ==":
if season == 'Winter':
    holiday = 'New Year\'s Day'
elif season == 'Spring':
    holiday = 'May Day'
elif season == 'Summer':
    holiday = 'Juneteenth'
elif season == 'Fall':
    holiday = 'Halloween'
else:
    holiday = 'Personal day off'

This code isn’t necessarily unpythonic, but it’s a bit verbose. By default, Java switch statements have “fall-through” that requires each block to end with a break statement. Otherwise, the execution continues on to the next block. Forgetting to add this break statement is a common source of bugs. But all the if-elif statements in our Python example can be repetitive. Some Python programmers prefer to set up a dictionary value instead of using if-elif statements. The following concise and pythonic code is equivalent to the previous example:

holiday = {'Winter': 'New Year\'s Day',
           'Spring': 'May Day',
           'Summer': 'Juneteenth',
           'Fall':   'Halloween'}.get(season, 'Personal day off')

This code is just a single assignment statement. The value stored in holiday is the return value of the get() method call, which returns the value for the key that season is set to. If the season key doesn’t exist, get() returns 'Personal day off'. Using a dictionary will result in more concise code, but it can also make your code harder to read. It’s up to you whether or not to use this convention.

Conditional Expressions: Python’s “Ugly” Ternary Operator

Ternary operators (officially called conditional expressions, or sometimes ternary selection expressions, in Python) evaluate an expression to one of two values based on a condition. Normally, you would do this with a pythonic if-else statement:

>>> # Pythonic Example
>>> condition = True
>>> if condition:
...     message = 'Access granted'
... else:
...     message = 'Access denied'
...
>>> message
'Access granted'

Ternary simply means an operator with three inputs, but in programming it’s synonymous with conditional expression. Conditional expressions also offer a more concise one-liner for code that fits this pattern. In Python, they’re implemented with an odd arrangement of the if and else keywords:

>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
1 >>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access granted'
2 >>> print(valueIfTrue if condition else valueIfFalse)
'Access granted'
>>> condition = False
>>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access denied'

The expression valueIfTrue if condition else valueIfFalse1 evaluates to valueIfTrue if the condition variable is True. When the condition variable is False, the expression evaluates to valueIfFalse. Guido van Rossum jokingly described his syntax design as “intentionally ugly.” Most languages with a ternary operator list the condition first, followed by the true value and then the false value. You can use a conditional expression anywhere you can use an expression or value, including as the argument to a function call 2.

Why would Python introduce this syntax in Python 2.5 even though it breaks the first guideline that beautiful is better than ugly? Unfortunately, despite being somewhat unreadable, many programmers use ternary operators and wanted Python to support this syntax. It’s possible to abuse Boolean operator short-circuiting to create a sort of ternary operator. The expression condition and valueIfTrue or valueIfFalse will evaluate to valueIfTrue if condition is True, and valueIfFalse if condition is False (except in one important case). Enter the following into the interactive shell:

>>> # Unpythonic Example
>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
>>> condition and valueIfTrue or valueIfFalse
'Access granted'

This condition and valueIfTrue or valueIfFalse style of pseudo-ternary operator has a subtle bug: if valueIfTrue is a falsey value (such as 0, False, None, or the blank string), the expression unexpectedly evaluates to valueIfFalse if condition is True.

But programmers continued to use this fake ternary operator anyway, and “Why doesn’t Python have a ternary operator?” became a perennial question to the Python core developers. Conditional expressions were created so programmers would stop asking for a ternary operator and wouldn’t use the bug-prone pseudo-ternary operator. But conditional expressions are also ugly enough to discourage programmers from using them. Although beautiful may be better than ugly, Python’s “ugly” ternary operator is an example of a case when practicality beats purity.

Conditional expressions aren’t exactly pythonic, but they’re not unpythonic, either. If you do use them, avoid nesting conditional expressions inside other conditional expressions:

>>> # Unpythonic Example
>>> age = 30
>>> ageRange = 'child' if age < 13 else 'teenager' if age >= 13 and age < 18 else 'adult'
>>> ageRange
'adult'

Nested conditional expressions are a good example of how a dense one-liner can be technically correct but frustrating to make sense of when reading.

Working with Variable Values

You’ll often need to check and modify the values that variables store. Python has several ways of doing this. Let’s look at a couple of examples.

Chaining Assignment and Comparison Operators

When you have to check whether a number is within a certain range, you might use the Boolean and operator like this:

# Unpythonic Example
if 42 < spam and spam < 99:

But Python lets you chain comparison operators so you don’t need to use the and operator. The following code is equivalent to the previous example:

# Pythonic Example
if 42 < spam < 99:

The same applies to chaining the = assignment operator. You can set multiple variables to the same value in a single line of code:

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> print(spam, eggs, bacon)
string string string

To check whether all three of these variables are the same, you can use the and operator, or more simply, chain the == comparison operator for equality.

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> spam == eggs == bacon == 'string'
True

Chaining operators is a small but useful shortcut in Python. However, if you use them incorrectly, they can cause problems. Chapter 8 demonstrates some instances where using them can introduce unexpected bugs in your code.

Checking Whether a Variable Is One of Many Values

You might sometimes encounter the inverse of the situation described in the preceding section: checking whether a single variable is one of multiple possible values. You could do this using the or operator, such as in the expression spam == 'cat' or spam == 'dog' or spam == 'moose'. All of those redundant “spam ==” parts make this expression a bit unwieldy.

Instead, you can put the multiple values into a tuple and check for whether a variable’s value exists in that tuple using the in operator, as in the following example:

>>> # Pythonic Example
>>> spam = 'cat'
>>> spam in ('cat', 'dog', 'moose')
True

Not only is this idiom easier to understand, it’s also slightly faster, according to timeit.

Summary

All programming languages have their own idioms and best practices. This chapter focuses on the particular ways that Python programmers have come to write “pythonic” code to make the best use of Python’s syntax.

At the core of pythonic code are the 20 aphorisms from the Zen of Python, which are rough guidelines for writing Python. These aphorisms are opinions and not strictly necessary for writing Python code, but they are good to keep in mind.

Python’s significant indentation (not to be confused with significant whitespace) provokes the most protest from new Python programmers. Although almost all programming languages commonly use indentation to make code readable, Python requires it in place of the more typical braces that other languages use.

Although many Python programmers use the range(len()) convention for for loops, the enumerate() function offers a cleaner approach to getting the index and value while iterating over a sequence. Similarly, the withstatement is a cleaner and less bug-prone way to handle files compared to calling open() and close() manually. The with statement ensures that close() gets called whenever the execution moves outside the with statement’s block.

Python has had several ways to interpolate strings. The original way was to use the %s conversion specifier to mark where strings should be included in the original string. The modern way as of Python 3.6 is to use f-strings. F-strings prefix the string literal with the letter f and use braces to mark where you can place strings (or entire expressions) inside the string.

The [:] syntax for making shallow copies of lists is a bit odd-looking and not necessarily pythonic, but it’s become a common way to quickly create a shallow list.

Dictionaries have a get() and setdefault() method for dealing with nonexistent keys. Alternatively, a collections.defaultdict dictionary will use a default value for nonexistent keys. Also, although there is no switch statement in Python, using a dictionary is a terse way to implement its equivalent without using several if-elif-else statements, and you can use ternary operators when evaluating between two values.

A chain of == operators can check whether multiple variables are equal to each other, whereas the in operator can check whether a variable is one of many possible values.

This chapter covered several Python language idioms, providing you with hints for how to write more pythonic code. In the next chapter, you’ll learn about some of the Python gotchas and pitfalls that beginners fall into.