# Chapter 4. Flow Control and Logic

Flow control is a high-level way of programming a computer to make decisions. These decisions can be simple or complicated, executed once or multiple times. The syntax for the different flow control mechanisms varies, but what they all share is that they determine an execution pathway for the program. Python has relatively few forms of flow control. They are conditionals, exceptions, and loops.

As someone primarily interested in physical reality, you might wonder why you should care about flow control and logic. In some ways, this is like asking why arithmetic is important. Logic presents rules that allow you to build up and represent more complex ideas. This enables the physics modeling you want to do by giving you a means to express the choices and behavior of your model to the computer. With basic flow control syntax, your models can make simple decisions. With more advanced flow control, your models can make more sophisticated choices more easily. In other situations, flow control allows you to reuse the same code many times. This makes the software model faster to write and easier to understand, because it has fewer total lines of code. Logic and flow control are indispensible to doing any significant amount of work with computers. So, without further delay, let’s jump into conditionals, our first bit of flow control.

# Conditionals

Conditionals are the simplest form of flow control. In English, they follow the syntax “if `x` is true, then do something; otherwise, do something else.” The shortest conditional is when there is only an `if` statement on its own. The format for such a statement is as follows:

````if` `<``condition``>``:`
`<``if``-``block``>````

Here, the Python keyword `if` is followed by an expression, `<condition>`, which is itself followed by a colon (`:`). When the Boolean representation of the condition, `bool(condition)`, is `True`, the code that is in the `<if-block>` is executed. If `bool(condition)` is `False`, then the code in the block is skipped. The condition may be composed of any of the comparison operators (or a combination of these operators) that were listed in Table 2-1. For convenience, just the comparison operators are shown again here in Table 4-1.

Table 4-1. Python logical operators useful for comparing the variables x, y, and z
Name Usage Returns

Unary operators

Negation

`not x`

Logical negation—`True` becomes `False`, and vice versa.

Bitwise invert

`~x`

Changes all zeros to ones and vice versa in `x`’s binary representation.

Binary operators

Logical and

`x and y`

`True` if `bool(x)` and `bool(y)` are `True`; `False` otherwise.

Logical or

`x or y`

`x` if `bool(x)` is `True`; otherwise the value of `y`.

Comparison binary operators

Equality

`x == y`

`True` or `False`.

Not equal

`x != y`

`True` or `False`.

Less than

`x < y`

`True` or `False`.

Less than or equal

`x <= y`

`True` or `False`.

Greater than

`x > y`

`True` or `False`.

Greater than or equal

`x >= y`

`True` or `False`.

Containment

`x in y`

`True` if `x` is an element of `y`.

Non-containment

`x not in y`

`False` if `x` is an element of `y`.

Identity test

`x is y`

`True` if `x` and `y` point to the same underlying value in memory.

Not identity test

`x is not y`

`False` if `x` and `y` point to the same underlying value in memory.

Ternary operators

Ternary compare

`x < y < z`

`True` or `False`, equivalent to `(x < y) and (y < z)`. The `<` here may be replaced by `>`, `<=`, or `>=` in any permutation.

For example, if we wanted to test if Planck’s constant is equal to one and then change its value if it is, we could write the following:

````h_bar` `=` `1.0`
`if` `h_bar` `==` `1.0``:`
`print``(``"h-bar isn't really unity! Resetting..."``)`
`h_bar` `=` `1.05457173e-34````

Here, since `h_bar` is `1.0` it is reset to its actual physical value (`1.05457173e-34`). If `h_bar` had been its original physical value, it would not have been reset.

A key Pythonism that is part of the `if` statement is that Python is whitespace separated. Unlike other languages, which use curly braces and semicolons, in Python the contents of the `if` block are determined by their indentation level. New statements must appear on their own lines. To exit the `if` block, the indentation level is returned back to its original column:

````h_bar` `=` `1`
`if` `h_bar` `==` `1``:`
`print``(``"h-bar isn't really unity! Resetting..."``)`
`h_bar` `=` `1.05457173e-34`
`h` `=` `h_bar` `*` `2` `*` `3.14159````

The last line here (the one that defines `h`) indicates that the `if` block has ended because its indentation level matches that of the `if` on the second line. The last line will always be executed, no matter what the conditional decides should be done for the `if` block.

While we are on the subject, it is important to bring up the distinction between the equality operator (`==`) and the identity operator (`is`). The equality operator tests if two values are equivalent. For example, `1 == 1.0` is `True` even though `1` is an integer and `1.0` is a float. On the other hand, the identity operator tests if two variable names are references to the same underlying value in memory. For example, `1 is 1.0` is `False` because the types are different, and therefore they cannot actually be references to the same value. `is` is much faster than `==`, but also much more strict. In general, you want to use `is` for singletons like `None` and use the safer `==` in most other situations. The following examples show typical use cases and gotchas:

Code Output
````1`` ``==`` ``1````
``````
````1`` ``==`` ``1.0````
``````
````1`` ``is`` ``1.0````
``````
````1`` ``is`` ``1`` ````
``````
````10``*``*``10`` ``==`` ``10``*``*``10````
``````
````10``*``*``10`` ``is`` ``10``*``*``10`` ````
``````
````None`` ``is`` ``None````
``````
````0`` ``is`` ``None`` ````
``````
````0`` ``==`` ``None````

To help with performance, Python only stores a single copy of small integers. So for small `int`s, every usage will be the same value in memory.

However, for big integers a new copy is computed each time.

Only `None` is `None`.

```True

True

False

True

True

False

True

False

False```

Before we move on, it is important to note that, by tradition, Python uses four spaces per level to indent all code blocks. Two spaces, eight spaces, or any other spacing is looked down upon. Tabs cause many more problems than they are worth. Most text editors have an option to automatically convert tabs to spaces, and enabling this can help prevent common errors. Some people find the whitespace syntax a little awkward to begin with, but it becomes easy and natural very quickly. The whitespace-aware aspect of Python is a codification of what is a best-practice coding style in other languages. It forces programmers to write more legible code.

## if-else Statements

Every `if` statement may be followed by an optional `else` statement. This is the keyword `else` followed by a colon (`:`) at the same indentation level as the original `if`. The `<else-block>` lines following this are indented just like the `if` block. The code in the `else` block is executed when the condition is `False`:

````if` `<``condition``>``:`
`<``if``-``block``>`
`else``:`
`<``else``-``block``>````

For example, consider the expression `sin(1/x)`. This function is computable everywhere except a `x = 0`. At this point, L’Hôpital’s rule shows that the result is also zero. This could be expressed with an `if-else` statement as follows:

````if` `x` `==` `0``:`
`y` `=` `0`
`else``:`
`y` `=` `sin``(``1``/``x``)````

This is equivalent to negating the conditional and switching the `if` and `else` blocks:

````if` `x` `!=` `0``:`
`y` `=` `sin``(``1``/``x``)`
`else``:`
`y` `=` `0````

However, it is generally considered a good practice to use positive conditionals (`==`) rather than negative ones (`!=`). This is because humans tend to think about an expression being true rather than it being false. This is not a hard and fast rule, but it does help eliminate easy-to-miss logic bugs.

## if-elif-else Statements

Python also allows multiple optional `elif` statements. The `elif` keyword is an abbreviation for “else if,” and such statements come after the `if` statement and before the `else` statement. The `elif` statements have much the same form as the `if` statement, and there may be as many of them as desired. The first conditional that evaluates to `True` determines the block that is entered, and no further conditionals or blocks are executed. The syntax is as follows:

````if` `<``condition0``>``:`
`<``if``-``block``>`
`elif` `<``condition1``>``:`
`<``elif``-``block1``>`
`elif` `<``condition2``>``:`
`<``elif``-``block2``>`
`...`
`else``:`
`<``else``-``block``>````

Suppose that you wanted to design a simple mid-band filter whose signal is 1 if the frequency is between 1 and 10 Hertz and 0 otherwise. This could be done with an `if-elif-else` statement:

````if` `omega` `<` `1.0``:`
`signal` `=` `0.0`
`elif` `omega` `>` `10.0``:`
`signal` `=` `0.0`
`else``:`
`signal` `=` `1.0````

A more realistic example might include ramping on either side of the band:

````if` `omega` `<` `0.9``:`
`signal` `=` `0.0`
`elif` `omega` `>` `0.9` `and` `omega` `<` `1.0``:`
`signal` `=` `(``omega` `-` `0.9``)` `/` `0.1`
`elif` `omega` `>` `10.0` `and` `omega` `<` `10.1``:`
`signal` `=` `(``10.1` `-` `omega``)` `/` `0.1`
`elif` `omega` `>` `10.1``:`
`signal` `=` `0.0`
`else``:`
`signal` `=` `1.0````

## if-else Expression

The final syntax covered here is the ternary conditional operator. It allows simple `if-else` conditionals to be evaluated in a single expression. This has the following syntax:

``x` `if` `<``condition``>` `else` `y``

If the condition evaluates to `True`, then `x` is returned. Otherwise, `y` is returned. This turns out to be extraordinarily handy for variable assignment. Using this kind of expression, we can write the `h_bar` conditional example in one line:

``h_bar` `=` `1.05457173e-34` `if` `h_bar` `==` `1.0` `else` `h_bar``

Note that when using this format you must always include the `else` clause. This fills the same role as the `condition?x:y` operator that is available in other languages. Writing out `if` and `else` arguably makes the Python way much more readable, though also more verbose.

# Exceptions

Python, like most modern programming languages, has a mechanism for exception handling. This is a language feature that allows the programmer to work around situations where the unexpected and catastrophic happen. Exception handling is for the truly exceptional cases: a user manually types in an impossible value, a file is deleted while it is being written, coffee spills on the laptop and fries the motherboard.

###### Warning

Exceptions are not meant for normal flow control and dealing with expected behavior! Use conditionals in cases where behavior is anticipated.

The syntax for handling exceptions is known as a `try-except` block. Both `try` and `except` are Python keywords. `try-except`s look very similar to `if-else` statements, but without the condition:

````try``:`
`<``try``-``block``>`
`except``:`
`<``except``-``block``>````

The `try` block will attempt to execute its code. If there are no errors, then the program skips the `except` block and proceeds normally. If any error at all happens, then the `except` block is immediately entered, no matter how far into the `try` block Python has gone. For this reason, it is generally a good idea to keep the `try` block as small as possible. Single-line `try` blocks are strongly preferred.

As an example, say that a user manually inputs a value and then the program takes the inverse of this value. Normally this computes just fine, with the exception of when the user enters `0`:

````In` `[``1``]:` `val` `=` `0.0`

`In` `[``2``]:` `1.0` `/` `val`

`ZeroDivisionError`                         `Traceback` `(``most` `recent` `call` `last``)`
`<``ipython``-``input``-``2``-``3``ac1864780ca``>` `in` `<``module``>``()`
`---->` `1` `1.0` `/` `val`

`ZeroDivisionError``:` `float` `division` `by` `zero````

This error could be handled with a `try-except`, which would prevent the program from crashing:

````try``:`
`inv` `=` `1.0` `/` `val`
`except``:`
`print``(``"A bad value was submitted {0}, please try again"``.``format``(``val``))````

The `except` statement also allows for the precise error that is anticipated to be caught. This allows for more specific behavior than the generic catch-all exception. The error name is placed right after the `except` keyword but before the colon. In the preceding example, we would catch a `ZeroDivisionError` by writing:

````try``:`
`inv` `=` `1.0` `/` `val`
`except` `ZeroDivisionError``:`
`print``(``"A zero value was submitted, please try again"``)````

Multiple `except` blocks may be chained together, much like `elif` statements. The first exception that matches determines the `except` block that is executed. The previous two examples could therefore be combined as follows:

````try``:`
`inv` `=` `1.0` `/` `val`
`except` `ZeroDivisionError``:`
`print``(``"A zero value was submitted, please try again"``)`
`except``:`
`print``(``"A bad value was submitted {0}, please try again"``.``format``(``val``))````

## Raising Exceptions

The other half of exception handling is raising them yourself. The `raise` keyword will throw an exception or error, which may then be caught by a `try-except` block elsewhere. This syntax provides a standard way for signaling that the program has run into an unallowed situation and can no longer continue executing.

`raise` statements may appear anywhere, but it is common to put them inside of conditionals so that they are not executed unless they need to be. Continuing with the inverse example, instead of letting Python raise a `ZeroDivisionError` we could check for a zero value and raise it ourselves:

````if` `val` `==` `0.0``:`
`raise` `ZeroDivisionError`
`inv` `=` `1.0` `/` `val````

If `val` happens to be zero, then the `inv = 1.0 / val` line will never be run. If `val` is nonzero, then the error is never raised.

All errors can be called with a custom string message. The helps locate, identify, and squash bugs. Error messages should be as detailed as necessary while remaining concise and readable. A message that states “An error occurred here” does not help anyone! A better version of the preceding code is:

````if` `val` `==` `0.0``:`
`raise` `ZeroDivisionError``(``"taking the inverse of zero is forbidden!"``)`
`inv` `=` `1.0` `/` `val````

Python comes with 150+ error and exception types. (This is not as many as it seems at first glance—these exceptions are sufficient to cover the more than one million lines of code in Python itself!) Table 4-2 lists some of the most common ones you will see in computational physics.

Table 4-2. Common Python errors and exceptions
Exception Description

`AssertionError`

Used when the `assert` operator sees `False`.

`AttributeError`

Occurs when Python cannot find a variable that lives on another variable. Usually this results from a typo.

`ImportError`

Occurs when a package or module cannot be found. This is typically the result of either a typo or a dependency that hasn’t been installed.

`IOError`

Happens when Python cannot read or write to an external file.

`KeyboardInterrupt`

Automatically raised when an external user kills the running Python process with Ctrl-c.

`KeyError`

Raised when a key cannot be found in a dictionary.

`MemoryError`

Raised when your computer runs out of RAM.

`NameError`

Occurs when a local or global variable name cannot be found. Usually the result of a typo.

`RuntimeError`

Generic exception for when something, somewhere has gone wrong. The error message itself normally has more information.

`SyntaxError`

Raised when the program tries to run non-Python code. This is typically the result of a typo, such as a missing colon or closing bracket.

`ZeroDivisionError`

Occurs when Python has tried to divide by zero, and is not happy about it.

It is often tempting to create custom exceptions for specific cases. You’ll find more information on how to do this in Chapter 6. However, custom exception types are rarely necessary—99% of the time there is already a built-in error that covers the exceptional situation at hand. It is generally better to use message strings to customize existing error types rather than creating brand new ones.

# Loops

While computers are not superb at synthesizing new tasks, they are very good at performing the same tasks over and over. So far in this chapter, we’ve been discussing the single execution of indented code blocks. Loops are how to execute the same block multiple times. Python has a few looping formats that are essential to know: `while` loops, `for` loops, and comprehensions.

## while Loops

`while` loops are related to `if` statements because they continue to execute “while a condition is true.” They have nearly the same syntax, except the `if` is replaced with the `while` keyword. Thus, the syntax has the following format:

````while` `<``condition``>``:`
`<``while``-``block``>````

The condition here is evaluated right before every loop iteration. If the condition is or remains `True`, then the block is executed. If the condition is `False`, then the `while` block is skipped and the program continues. Here is a simple countdown timer:

Code Output
````t` `=` `3`
`while` `0` `<` `t``:`
`print``(``"t-minus "` `+` `str``(``t``))`
`t` `=` `t` `-` `1`
`print``(``"blastoff!"``)````
```t-minus 3
t-minus 2
t-minus 1
blastoff!```

If the condition evaluates to `False`, then the `while` block will never be entered. For example:

Code Output
````while` `False``:`
`print``(``"I am sorry, Dave."``)`
`print``(``"I can't print that for you."``)````
`I can`'`t print that `for` you.`

On the other hand, if the condition always evaluates to `True`, the `while` block will continue to be executed no matter what. This is known as an infinite or nonterminating loop. Normally this is not the intended behavior. A slight modification to the countdown timer means it will never finish on its own:

Code Output
````t` `=` `3`
`while` `True``:`
`print``(``"t-minus "` `+` `str``(``t``))`
`t` `=` `t` `-` `1`
`print``(``"blastoff!"``)````
```t-minus 3
t-minus 2
t-minus 1
t-minus 0
t-minus -1
t-minus -2
t-minus -3
t-minus -4
t-minus -5
...
`# blastoff is never reached````

Integers counting down to negative infinity is not correct behavior in most situations.

###### Note

Interestingly, it is impossible to predict whether a loop (or any program) will terminate without actually running it. This is known as the halting problem and was originally shown by Alan Turing. If you do happen to accidentally start an infinite loop, you can always hit Ctrl-c to exit the Python program.

The `break` statement is Python’s way of leaving a loop early. The keyword `break` simply appears on its own line, and the loop is immediately exited. Consider the following `while` loop, which computes successive elements of the Fibonacci series and adds them to the `fib` list. This loop will continue forever unless it finds an entry that is divisible by 12, at which point it will immediately leave the loop and not add the entry to the list:

Code Output
````fib` `=` `[``1``,` `1``]`
`while` `True``:`
`x` `=` `fib``[``-``2``]` `+` `fib``[``-``1``]`
`if` `x``%``12` `==` `0``:`
`break`
`fib``.``append``(``x``)````
``[`1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89`]``

This loop does terminate, because `55 + 89 == 144` and `144 == 12**2`. Also note that the `if` statement is part of the `while` block. This means that the `break` statement needs to be additionally indented. Additional levels of indentation allow for code blocks to be nested within one another. Nesting can be arbitrarily deep as long as the correct flow control is used.

## for Loops

Though `while` loops are helpful for repeating statements, it is typically more useful to iterate over a container or other “iterable,” grabbing a single element each time through and exiting the loop when there are no more elements in the container. In Python, `for` loops fill this role. They use the `for` and `in` keywords and have the following syntax:

````for` `<``loop``-``var``>` `in` `<``iterable``>``:`
`<``for``-``block``>````

The `<loop-var>` is a variable name that is assigned to a new element of the iterable on each pass through the loop. The `<iterable>` is any Python object that can return elements. All containers (lists, tuples, sets, dictionaries) and strings are iterable. The `for` block is a series of statements whose execution is repeated. This is the same as what was seen for `while` blocks. Using a `for` loop, we could rewrite our countdown timer to loop over the list of integers `[3, 2, 1]` as follows:

````for` `t` `in` `[``3``,` `2``,` `1``]:`
`print``(``"t-minus "` `+` `str``(``t``))`
`print``(``"blastoff!"``)````

Again, the value of `t` changes on each iteration. Here, though, the `t = t - 1` line is not needed because `t` is automatically reassigned to the next value in the list. Additionally, the `0 < t` condition is not needed to stop the list; when there are no more elements in the list, the loop ends.

The `break` statement can be used with `for` loops just like with `while` loops. Additionally, the `continue` statement can be used with both `for` and `while` loops. This exits out of the current iteration of the loop only and continues on with the next iteration. It does not break out of the whole loop. Consider the case where we want to count down every `t` but want to skip reporting the even times:

Code Output
````for` `t` `in` `[``7``,` `6``,` `5``,` `4``,` `3``,` `2``,` `1``]:`
`if` `t``%``2` `==` `0``:`
`continue`
`print``(``"t-minus "` `+` `str``(``t``))`
`print``(``"blastoff!"``)````
```t-minus 7
t-minus 5
t-minus 3
t-minus 1
blastoff!```

Note that containers choose how they are iterated over. For sequences (strings, lists, tuples), there is a natural iteration order. String iteration produces each letter in turn:

Code Output
````for` `letter` `in` `"Gorgus"``:`
`print``(``letter``)````
```G
o
r
g
u
s```

However, unordered data structures (sets, dictionaries) have an unpredictable iteration ordering. All elements are guaranteed to be iterated over, but when each element comes out is not predictable. The iteration order is not the order that the object was created with. The following is an example of set iteration:

Code Output
````for` `x` `in` `{``"Gorgus"``,` `0``,` `True``}:`
`print``(``x``)````
```0
True
Gorgus```

Dictionaries have further ambiguity in addition to being unordered. The loop variable could be the keys, the values, or both (the items). Python chooses to return the keys when looping over a dictionary. It is assumed that the values can be looked up normally. It is very common to use `key` or `k` as the loop variable name. For example:

Code Output
````d` `=` `{``"first"``:` `"Albert"``,`
`"last"``:` `"Einstein"``,`
`"birthday"``:` `[``1879``,` `3``,` `14``]}`

`for` `key` `in` `d``:`
`print``(``key``)`
`print``(``d``[``key``])`
`print``(``"======"``)````
```birthday
`[`1879, 3, 14`]`
`======`
last
`Einstein`
`======`
first
`Albert`
`======````

Dictionaries may also be explicitly looped through their keys, values, or items using the `keys()`, `values()`, or `items()` methods:

Code Output
````d` `=` `{``"first"``:` `"Albert"``,`
`"last"``:` `"Einstein"``,`
`"birthday"``:` `[``1879``,` `3``,` `14``]}`

`print``(``"Keys:"``)`
`for` `key` `in` `d``.``keys``():`
`print``(``key``)`

`print``(``"``\n``======``\n``"``)`

`print``(``"Values:"``)`
`for` `value` `in` `d``.``values``():`
`print``(``value``)`

`print``(``"``\n``======``\n``"``)`

`print``(``"Items:"``)`
`for` `key``,` `value` `in` `d``.``items``():`
`print``(``key``,` `value``)````
```Keys:
birthday
last
`first`

`======`

Values:
`[`1879, 3, 14`]`
Einstein
`Albert`

`======`

Items:
`(``'birthday'`, `[`1879, 3, 14`])`
`(``'last'`, `'Einstein'``)`
`(``'first'`, `'Albert'``)````

When iterating over items, the elements come back as key/value tuples. These can be unpacked into their own loop variables (called `key` and `value` here for consistency, though this is not mandatory). Alternatively, the items could remain packed, in which case the loop variable would still be a tuple:

Code Output
````for` `item` `in` `d``.``items``():`
`print``(``item``)````
````(``'birthday'`, `[`1879, 3, 14`])`
`(``'last'`, `'Einstein'``)`
`(``'first'`, `'Albert'``)````

It is a very strong idiom in Python that the loop variable name is a singular noun and the iterable is the corresponding plural noun. This makes the loop more natural to read. This pattern expressed in code is shown here:

````for` `single` `in` `plural``:`
`...````

For example, looping through the set of quark names would be done as follows:

````quarks` `=` `{``'up'``,` `'down'``,` `'top'``,` `'bottom'``,` `'charm'``,` `'strange'``}`
`for` `quark` `in` `quarks``:`
`print``(``quark``)````

## Comprehensions

`for` and `while` loops are fantastic, but they always take up at least two lines: one for the loop itself and another for the block. And often when you’re looping through a container the result of each loop iteration needs to be placed in a new corresponding list, set, dictionary, etc. This takes at least three lines. For example, converting the quarks set to a list of uppercase strings requires first setting up an empty list:

Code Output
````upper_quarks` `=` `[]`
`for` `quark` `in` `quarks``:`
`upper_quarks``.``append``(``quark``.``upper``())````
````upper_quarks` `=` `[``'BOTTOM'`, `'TOP'`,
`'UP'`, `'DOWN'`,
`'STRANGE'`, `'CHARM'``]````

However, it seems as though this whole loop could be done in one line. This is because there is only one meaningful expression where work is performed: namely `upper_quarks.append(quark.upper())`. Enter comprehensions.

Comprehensions are a syntax for spelling out simple `for` loops in a single expression. List, set, and dictionary comprehensions exist, depending on the type of container that the expression should return. Since they are simple, the main limitation is that the `for` block may only be a single expression itself. The syntax for these is as follows:

````# List comprehension`
`[``<``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>``]`

`# Set comprehension`
`{``<``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>``}`

`# Dictionary comprehension`
`{``<``key``-``expr``>``:` `<``value``-``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>``}````

Note that these comprehensions retain as much of the original container syntax as possible. The list uses square brackets (`[]`), the set uses curly braces (`{}`), and the dictionary uses curly braces `{}` with keys and values separated by a colon (`:`). The `upper_quarks` loop in the previous example can be thus transformed into the following single line:

``upper_quarks` `=` `[``quark``.``upper``()` `for` `quark` `in` `quarks``]``

Sometimes you might want to use a set comprehension instead of a list comprehension. This situation arises when the result should have unique entries but the expression may return duplicated values. For example, if users are allowed to enter data that you know ahead of time is categorical, then you can normalize the data inside of a set comprehension to find all unique entries. Consider that users might be asked to enter quark names, and lowercasing the entries will produce a common spelling. The following set comprehension will produce a set of just `{'top', 'charm', 'strange'}`, even though there are multiple spellings of the same quarks:

````entries` `=` `[``'top'``,` `'CHARm'``,` `'Top'``,` `'sTraNGe'``,` `'strangE'``,` `'top'``]`
`quarks` `=` `{``quark``.``lower``()` `for` `quark` `in` `entries``}````

It is also sometimes useful to write dictionary comprehensions. This often comes up when you want to execute an expression over some data but also need to retain a mapping from the input to the result. For instance, suppose that we want to create a dictionary that maps numbers in an `entries` list to the results of `x**2 + 42`. This can be done with:

````entries` `=` `[``1``,` `10``,` `12.5``,` `65``,` `88``]`
`results` `=` `{``x``:` `x``**``2` `+` `42` `for` `x` `in` `entries``}````

Comprehensions may optionally include a filter. This is a conditional that comes after the iterable. If the condition evaluates to `True`, then the loop expression is evaluated and added to the list, set, or dictionary normally. If the condition is `False`, then the iteration is skipped. The syntax uses the `if` keyword, as follows:

````# List comprehension with filter`
`[``<``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>` `if` `<``condition``>``]`

`# Set comprehension with filter`
`{``<``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>` `if` `<``condition``>``}`

`# Dictionary comprehension with filter`
`{``<``key``-``expr``>``:` `<``value``-``expr``>` `for` `<``loop``-``var``>` `in` `<``iterable``>` `if` `<``condition``>``}````

Thus, list comprehensions with a filter are effectively shorthand for the following code pattern:

````new_list` `=` `[]`
`for` `<``loop``-``var``>` `in` `<``iterable``>``:`
`if` `<``condition``>``:`
`new_list``.``append``(``<``expr``>``)````

Suppose you had a list of words, `pm`, that represented the entire text of Principia Mathematica by Isaac Newton and you wanted to find all of the words, in order, that started with the letter `t`. This operation could be performed in one line with the following list comprehension with a filter:

``t_words` `=` `[``word` `for` `word` `in` `pm` `if` `word``.``startswith``(``'t'``)]``

Alternatively, take the case where you want to compute the set of squares of Fibonacci numbers, but only where the Fibonacci number is divisible by five. Given a list of Fibonacci numbers `fib`, the desired set is computable via this set comprehension:

``{``x``**``2` `for` `x` `in` `fib` `if` `x``%``5` `==` `0``}``

Lastly, dictionary comprehensions with filters are most often used to retain or remove items from another dictionary. This is often used when there also exists a set of “good” or “bad” keys. Suppose you have a dictionary that maps coordinate axes to indexes. From this dictionary, you only want to retain the polar coordinates. The corresponding dictionary comprehension would be implemented as follows:

````coords` `=` `{``'x'``:` `1``,` `'y'``:` `2``,` `'z'``:` `3``,` `'r'``:` `1``,` `'theta'``:` `2``,` `'phi'``:` `3``}`
`polar_keys` `=` `{``'r'``,` `'theta'``,` `'phi'``}`
`polar` `=` `{``key``:` `value` `for` `key``,` `value` `in` `coords``.``items``()` `if` `key` `in` `polar_keys``}````

Comprehensions are incredibly powerful and expressive. The reasoning goes that if the operation cannot fit into a comprehension, then it should probably be split up into multiple lines in a normal `for` loop anyway. It is possible to nest comprehensions inside of one another, just like loops may be nested. However, this can become pretty convoluted to think about since two or more loops are on the same line. Python allows for simple looping situations to be dealt with simply, and encourages complex loops to be made readable.

# Flow Control and Logic Wrap-up

Having reached the end of this chapter, you should be familiar with the following big ideas:

• How to make decisions with `if-else` statements

• Handling the worst situations with exceptions

• Reusing code with loops

• The `for single in plural` loop pattern

• Using comprehensions to write concise loops

And now that you have seen the basics of decision making and code reuse, it is time to step those ideas up to the next level with functions in Chapter 5.

Get Effective Computation in Physics now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.