Statements and Syntax
Python syntax is designed to be simple. There are a few rules; we'll look at some of the interesting statements in the language as a way to understand those rules. Concrete examples can help clarify the language's syntax.
We'll cover some basics of creating script files first. Then we'll move on to looking at some of the more commonly-used statements. Python only has about 20 or so different kinds of imperative statements in the language. We've already looked at two kinds of statements in Chapter 1, Numbers, Strings, and Tuples, the assignment statement and the expression statement.
When we write something like this:
>>> print("hello world") hello world
We're actually executing a statement that contains only the evaluation of a function,
print(). This kind of statement—where we evaluate a function or a method of an object—is common.
The other kind of statement we've...
Writing Python script and module files – syntax basics
We'll need to write Python script files in order to do anything that's fully automated. We can experiment with the language at the interactive
>>> prompt. We can also use JupyterLab interactively. For automated work, however, we'll need to create and run script files.
How can we make sure our code matches what's in common use? We need to look at some common aspects of style: how we organize our programming to make it readable.
We'll also look at a number of more technical considerations. For example, we need to be sure to save our files in UTF-8 encoding. While ASCII encoding is still supported by Python, it's a poor choice for modern programming. We'll also need to be sure to use spaces instead of tabs. If we use Unix newlines as much as possible, we'll also find it slightly simpler to create software that runs on a variety of operating systems.
Writing long lines of code
There are many times when we need to write lines of code that are so long that they're very hard to read. Many people like to limit the length of a line of code to 80 characters or fewer. It's a well-known principle of graphic design that a narrower line is easier to read. See http://webtypography.net/2.1.2 for a deeper discussion of line width and readability.
While shorter lines are easier on the eyes, our code can refuse to cooperate with this principle. Long statements are a common problem. How can we break long Python statements into more manageable pieces?
Often, we'll have a statement that's awkwardly long and hard to work with. Let's say we've got something like this:
>>> import math >>> example_value = (63/25) * (17+15*math.sqrt(5)) / (7+15*math.sqrt(5)) >>> mantissa_fraction, exponent = math.frexp(example_value) >>> mantissa_whole = int...
Including descriptions and documentation
When we have a useful script, we often need to leave notes for ourselves—and others—on what it does, how it solves some particular problem, and when it should be used.
Because clarity is important, there are some formatting recipes that can help make the documentation very clear. This recipe also contains a suggested outline so that the documentation will be reasonably complete.
If we've used the Writing Python script and module files – syntax basics recipe to build a script file, we'll have to put a small documentation string in our script file. We'll expand on this documentation string in this recipe.
There are other places where documentation strings should be used. We'll look at these additional locations in Chapter 3, Function Definitions, and Chapter 7, Basics of Classes and Objects.
We have two general kinds of modules for which we'll be writing...
Writing better RST markup in docstrings
When we have a useful script, we often need to leave notes on what it does, how it works, and when it should be used. Many tools for producing documentation, including docutils, work with RST markup. What RST features can we use to make documentation more readable?
In the Including descriptions and documentation recipe, we looked at putting a basic set of documentation into a module. This is the starting point for writing our documentation. There are a large number of RST formatting rules. We'll look at a few that are important for creating readable documentation.
How to do it...
- Be sure to write an outline of the key points. This may lead to creating RST section titles to organize the material. A section title is a two-line paragraph with the title followed by an underline using
~, or one of the other docutils characters for underlining.
A heading will look like this:
Designing complex if...elif chains
In most cases, our scripts will involve a number of choices. Sometimes the choices are simple, and we can judge the quality of the design with a glance at the code. In other cases, the choices are more complex, and it's not easy to determine whether or not our
if statements are designed properly to handle all of the conditions.
In the simplest case, we have one condition, C, and its inverse, ¬C` . These are the two conditions for an
if...else statement. One condition, C, is stated in the
if clause, the other condition, C's inverse, is implied in
This is the Law of the Excluded Middle: we're claiming there's no missing alternative between the two conditions, C and ¬C. For a complex condition, though, this isn't always true.
If we have something like:
if weather == RAIN and plan == GO_OUT: bring("umbrella") else: bring("sunglasses")
It may not be immediately...
Saving intermediate results with the := "walrus"
Sometimes we'll have a complex condition where we want to preserve an expensive intermediate result for later use. Imagine a condition that involves a complex calculation; the cost of computing is high measured in time, or input-output, or memory, or network resource use. Resource use defines the cost of computation.
An example includes doing repetitive searches where the result of the search may be either a useful value or a sentinel value indicating that the target was not found. This is common in the Regular Expression (
re) package where the
match() method either returns a match object or a
None object as a sentinel showing the pattern wasn't found. Once this computation is completed, we may have several uses for the result, and we emphatically do not want to perform the computation again.
This is an example where it can be helpful to assign a name to the value of an expression. We&apos...
Avoiding a potential problem with break statements
The common way to understand a
for statement is that it creates a for all condition. At the end of the statement, we can assert that, for all items in a collection, some processing has been done.
This isn't the only meaning for a
for statement. When we introduce the
break statement inside the body of a
for, we change the semantics to there exists. When the
break statement leaves the
while) statement, we can assert only that there exists at least one item that caused the statement to end.
There's a side issue here. What if the
for statement ends without executing
break? Either way, we're at the statement after the
The condition that's true upon leaving a
while statement with a
break can be ambiguous. Did it end normally? Did it execute
break? We can't easily tell, so we'll provide a recipe that gives us some design guidance.
This can become an even bigger...
Leveraging exception matching rules
try statement lets us capture an exception. When an exception is raised, we have a number of choices for handling it:
- Ignore it: If we do nothing, the program stops. We can do this in two ways—don't use a
trystatement in the first place, or don't have a matching
exceptclause in the
- Log it: We can write a message and use a
raisestatement to let the exception propagate after writing to a log; generally, this will stop the program.
- Recover from it: We can write an
exceptclause to do some recovery action to undo any effects of the partially completed
- Silence it: If we do nothing (that is, use the
passstatement), then processing is resumed after the
trystatement. This silences the exception.
- Rewrite it: We can raise a different exception. The original exception becomes a context for the newly raised exception.
What about nested contexts? In this case...
Avoiding a potential problem with an except: clause
There are some common mistakes in exception handling. These can cause programs to become unresponsive.
One of the mistakes we can make is to use the
except: clause with no named exceptions to match. There are a few other mistakes that we can make if we're not cautious about the exceptions we try to handle.
This recipe will show some common exception handling errors that we can avoid.
When code can raise a variety of exceptions, it's sometimes tempting to try and match as many as possible. Matching too many exceptions can interfere with stopping a misbehaving Python program. We'll extend the idea of what not to do in this recipe.
How to do it...
We need to avoid using the bare
except: clause. Instead, use
except Exception: to match the most general kind of exception that an application can reasonably handle.
Handling too many exceptions can interfere with our ability to stop...
Concealing an exception root cause
In Python 3, exceptions contain a root cause. The default behavior of internally raised exceptions is to use an implicit
__context__ to include the root cause of an exception. In some cases, we may want to deemphasize the root cause because it's misleading or unhelpful for debugging.
This technique is almost always paired with an application or library that defines a unique exception. The idea is to show the unique exception without the clutter of an irrelevant exception from outside the application or library.
Assume we're writing some complex string processing. We'd like to treat a number of different kinds of detailed exceptions as a single generic error so that users of our software are insulated from the implementation details. We can attach details to the generic error.
How to do it...
- To create a new exception, we can do this:
>>> class MyAppError(Exception): .....
Managing a context using the with statement
There are many instances where our scripts will be entangled with external resources. The most common examples are disk files and network connections to external hosts. A common bug is retaining these entanglements forever, tying up these resources uselessly. These are sometimes called memory leaks because the available memory is reduced each time a new file is opened without closing a previously used file.
We'd like to isolate each entanglement so that we can be sure that the resource is acquired and released properly. The idea is to create a context in which our script uses an external resource. At the end of the context, our program is no longer bound to the resource and we want to be guaranteed that the resource is released.
Let's say we want to write lines of data to a file in CSV format. When we're done, we want to be sure that the file is closed and the various OS resources—including...