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 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:
spam.eggs.bacon.ham()
or spam['eggs']['bacon']['ham']
, you’re making your code too complicated.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.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.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.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.)
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.
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.
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.
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. .
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:
.
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.
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 \\
.
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.
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.
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.
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.
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.
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.
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 valueIfFalse
1 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.
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.
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.
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
.
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 with
statement 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.