Chapter 4. Py Crust: Code Structures
In Chapters 1 through 3, you’ve seen many examples of data but have not done much with them. Most of the code examples used the interactive interpreter and were short. Now you’ll see how to structure Python code, not just data.
Many computer languages use characters such as
curly braces ({
and }
) or keywords such as begin
and end
to mark off sections of code.
In those languages,
it’s good practice to use consistent
indentation to make your program more readable
for yourself and others.
There are even tools to make your code line up nicely.
When he was designing the language that became Python, Guido van Rossum decided that the indentation itself was enough to define a program’s structure, and avoided typing all those parentheses and curly braces. Python is unusual in this use of white space to define program structure. It’s one of the first aspects that newcomers notice, and it can seem odd to those who have experience with other languages. It turns out that after writing Python for a little while, it feels natural and you stop noticing it. You even get used to doing more while typing less.
Comment with #
A comment is a piece of text in your program that is ignored
by the Python interpreter.
You might use comments to
clarify nearby Python code,
make notes to yourself to fix something someday,
or for whatever purpose you like.
You mark a comment by using the #
character; everything from that point on to the end of the current line is part of the comment.
You’ll usually see a comment on a line by itself, as shown here:
>>>
# 60 sec/min * 60 min/hr * 24 hr/day
>>>
seconds_per_day
=
86400
Or, on the same line as the code it’s commenting:
>>>
seconds_per_day
=
86400
# 60 sec/min * 60 min/hr * 24 hr/day
The #
character has many names:
hash, sharp, pound,
or the sinister-sounding
octothorpe.1
Whatever you call
it,2
its effect lasts only to the end of the line on which it appears.
Python does not have a multiline comment. You need to explicitly begin each comment line or section with a #
.
>>>
# I can say anything here, even if Python doesn't like it,
...
# because I'm protected by the awesome
...
# octothorpe.
...
>>>
However, if it’s in a text string, the all-powerful octothorpe reverts back to its role as a plain old #
character:
>>>
(
"No comment: quotes make the # harmless."
)
No comment: quotes make the # harmless.
Continue Lines with \
Programs are more readable when lines are reasonably short.
The recommended (not required) maximum line length is
80 characters.
If you can’t say everything you want to say in that length,
you can use the continuation character: \
(backslash).
Just put \
at the end of a line,
and Python will suddenly act as though you’re still on the
same line.
For example, if I wanted to build a long string from smaller ones, I could do it in steps:
>>>
alphabet
=
''
>>>
alphabet
+=
'abcdefg'
>>>
alphabet
+=
'hijklmnop'
>>>
alphabet
+=
'qrstuv'
>>>
alphabet
+=
'wxyz'
Or, I could do it in one step, using the continuation character:
>>>
alphabet
=
'abcdefg'
+
\...
'hijklmnop'
+
\...
'qrstuv'
+
\...
'wxyz'
Line continuation is also needed if a Python expression spans multiple lines:
>>>
1
+
2
+
File"<stdin>"
, line1
1
+
2
+
^
SyntaxError
:invalid syntax
>>>
1
+
2
+
\...
3
6
>>>
Compare with if, elif, and else
So far in this book,
we’ve talked almost entirely about data
structures.
Now, we finally take our first step into the
code structures
that weave data into programs.
(You got a little preview of these in the previous chapter’s section on sets. I hope no lasting damage was done.) Our first example
is this tiny Python program
that checks the value of
the boolean variable disaster
and prints an appropriate comment:
>>>
disaster
=
True
>>>
if
disaster
:
...
(
"Woe!"
)
...
else
:
...
(
"Whee!"
)
...
Woe!
>>>
The if
and else
lines are Python statements
that check whether a condition
(here, the value of disaster
)
is True
.
Remember,
print()
is Python’s built-in function to print things,
normally to your screen.
Note
If you’ve programmed in other languages,
note that you don’t need parentheses for the if
test.
Don’t say something such as if (disaster == True)
.
You do need the colon (:
) at the end.
If, like me, you forget to type the colon at times,
Python will display an error message.
Each print()
line is indented under its test.
I used four spaces to indent each subsection.
Although you can use any indentation you like,
Python expects you to be consistent with code within a section—the lines need to be indented the same amount,
lined up on the left.
The recommended style, called
PEP-8, is to use four spaces.
Don’t use tabs, or mix tabs and spaces; it messes up the indent count.
We did a number of things here, which I’ll explain more fully as the chapter progresses:
-
Assigned the boolean value
True
to the variable nameddisaster
-
Performed a conditional comparison by using
if
andelse
, executing different code depending on the value ofdisaster
-
Called the
print()
function to print some text
You can have tests within tests, as many levels deep as needed:
>>>
furry
=
True
>>>
small
=
True
>>>
if
furry
:
...
if
small
:
...
(
"It's a cat."
)
...
else
:
...
(
"It's a bear!"
)
...
else
:
...
if
small
:
...
(
"It's a skink!"
)
...
else
:
...
(
"It's a human. Or a hairless bear."
)
...
It's a cat.
In Python,
indentation determines how the if
and else
sections are paired.
Our first test was to check furry
.
Because furry
is True
,
Python goes to the indented if small
test.
Because we had set small
to True
,
if small
is evaluated as True
.
This makes Python run the next line and print
It's a cat.
If there are more than two possibilities to test,
use if
, elif
(meaning else if), and else
:
>>>
color
=
"puce"
>>>
if
color
==
"red"
:
...
(
"It's a tomato"
)
...
elif
color
==
"green"
:
...
(
"It's a green pepper"
)
...
elif
color
==
"bee purple"
:
...
(
"I don't know what it is, but only bees can see it"
)
...
else
:
...
(
"I've never heard of the color"
,
color
)
...
I've never heard of the color puce
In the preceding example, we tested for equality with the ==
operator.
Python’s comparison operators are:
equality |
|
inequality |
|
less than |
|
less than or equal |
|
greater than |
|
greater than or equal |
|
membership |
|
These return the boolean values True
or False
.
Let’s see how these all work, but first, assign a value to x
:
>>>
x
=
7
Now, let’s try some tests:
>>>
x
==
5
False
>>>
x
==
7
True
>>>
5
<
x
True
>>>
x
<
10
True
Note that two equals signs (==
) are used
to test equality; remember, a single equals sign (=
)
is what you use to assign a value to a variable.
If you need to make multiple comparisons
at the same time, you use the boolean operators
and
, or
, and not
to determine
the final boolean result.
Boolean operators have lower precedence
than the chunks of code that they’re comparing.
This means that the chunks are calculated
first, then compared.
In this example,
because we set x
to 7
,
5 < x
is calculated to be True
and x < 10
is also True
,
so we finally end up with
True and True
:
>>>
5
<
x
and
x
<
10
True
As “Precedence” points out, the easiest way to avoid confusion about precedence is to add parentheses:
>>>
(
5
<
x
)
and
(
x
<
10
)
True
Here are some other tests:
>>>
5
<
x
or
x
<
10
True
>>>
5
<
x
and
x
>
10
False
>>>
5
<
x
and
not
x
>
10
True
If you’re and
-ing multiple comparisons with one variable,
Python lets you do this:
>>>
5
<
x
<
10
True
It’s the same as 5 < x and x < 10
.
You can also write longer comparisons:
>>>
5
<
x
<
10
<
999
True
What Is True?
What if the element we’re checking isn’t a boolean?
What does Python consider True
and False
?
A false
value doesn’t necessarily need to explicitly be False
. For example, these are all considered False
:
boolean |
|
null |
|
zero integer |
|
zero float |
|
empty string |
|
empty list |
|
empty tuple |
|
empty dict |
|
empty set |
|
Anything else is considered True
.
Python programs use this definition
of “truthiness” (or in this case, “falsiness”)
to check for empty data structures
as well as False
conditions:
>>>
some_list
=
[]
>>>
if
some_list
:
...
(
"There's something in here"
)
...
else
:
...
(
"Hey, it's empty!"
)
...
Hey, it's empty!
If what you’re testing is an expression rather than a simple variable, Python evaluates the expression and returns a boolean result. So, if you type the following:
if color == "red":
Python evaluates color == "red"
.
In our example, we assigned the string "puce"
to color
earlier,
so color == "red"
is False
,
and Python moves on to the next test:
elif color == "green":
Do Multiple Comparisons with in
Say you have a letter and want to know if it’s a vowel.
One way would be to write a long if
statement:
>>>
letter
=
'o'
>>>
if
letter
==
'a'
or
letter
==
'e'
or
letter
==
'i'
\...
or
letter
==
'o'
or
letter
==
'u'
:
...
(
letter
,
'is a vowel'
)
...
else
:
...
(
letter
,
'is not a vowel'
)
...
o is a vowel
>>>
Whenever you need to make a lot of comparisons like that,
separated by or
, use Python’s in
feature instead.
Here’s how to check vowel-ness more Pythonically,
using in
with a string made of vowel characters:
>>>
vowels
=
'aeiou'
>>>
letter
=
'o'
>>>
letter
in
vowels
True
>>>
if
letter
in
vowels
:
...
(
letter
,
'is a vowel'
)
...
o is a vowel
In earlier chapters, we used in
to see if a value
exists in any of Python’s iterable data types,
notably
lists, tuples, sets, strings.
So, we could define vowels
with any of those data types too:
>>>
vowel_set
=
{
'a'
,
'e'
,
'i'
,
'o'
,
'u'
}
>>>
letter
in
vowel_set
True
>>>
vowel_list
=
[
'a'
,
'e'
,
'i'
,
'o'
,
'u'
]
>>>
letter
in
vowel_list
True
>>>
vowel_tuple
=
(
'a'
,
'e'
,
'i'
,
'o'
,
'u'
)
>>>
letter
in
vowel_tuple
True
>>>
vowel_dict
=
{
'a'
:
'apple'
,
'e'
:
'elephant'
,
...
'i'
:
'impala'
,
'o'
:
'ocelot'
,
'u'
:
'unicorn'
}
>>>
letter
in
vowel_dict
True
For the dictionary, in
looks at the keys instead of their values.
Repeat with while
Testing with if
, elif
, and else
runs from top to bottom.
Sometimes, we need to do something more than once.
We need a loop,
and the simplest looping mechanism in Python is while
.
Using the interactive interpreter,
try this next example, which is a simple loop that prints the numbers from 1 to 5:
>>>
count
=
1
>>>
while
count
<=
5
:
...
(
count
)
...
count
+=
1
...
1
2
3
4
5
>>>
We first assigned the value 1
to count
.
The while
loop compared the value of count
to 5
and continued if count
was less than
or equal to 5
.
Inside the loop,
we printed the value of count
and then incremented its value by one
with the statement count += 1
.
Python goes back to the top of the loop,
and again compares count
with 5
.
The value of count
is now 2
,
so the contents of the while
loop
are again executed, and count
is incremented to 3
.
This continues until count
is incremented
from 5
to 6
at the bottom of the loop.
On the next trip to the top,
count <= 5
is now False
,
and the while
loop ends.
Python moves on to the next lines.
Cancel with break
If you want to loop until something occurs,
but you’re not sure when that might happen,
you can use an infinite loop with a break
statement.
This time we’ll read a line of input from the keyboard
via Python’s input()
function
and then print it with the first letter capitalized.
We break out of the loop
when a line containing only the letter q
is typed:
>>>
while
True
:
...
stuff
=
input
(
"String to capitalize [type q to quit]: "
)
...
if
stuff
==
"q"
:
...
break
...
(
stuff
.
capitalize
())
...
String to capitalize [type q to quit]: test
Test
String to capitalize [type q to quit]: hey, it works
Hey, it works
String to capitalize [type q to quit]: q
>>>
Skip Ahead with continue
Sometimes you don’t want to break out of a loop
but just want to skip ahead to the next iteration for some reason.
Here’s a contrived example: let’s read an integer,
print its square if it’s odd, and skip it if it’s even.
We even added a few comments.
Again, we’ll use q
to stop the loop:
>>>
while
True
:
...
value
=
input
(
"Integer, please [q to quit]: "
)
...
if
value
==
'q'
:
# quit
...
break
...
number
=
int
(
value
)
...
if
number
%
2
==
0
:
# an even number
...
continue
...
(
number
,
"squared is"
,
number
*
number
)
...
Integer, please [q to quit]: 1
1 squared is 1
Integer, please [q to quit]: 2
Integer, please [q to quit]: 3
3 squared is 9
Integer, please [q to quit]: 4
Integer, please [q to quit]: 5
5 squared is 25
Integer, please [q to quit]: q
>>>
Check break Use with else
If the while
loop ended normally
(no break
call),
control passes to an optional else
.
You use this when you’ve coded a while
loop to check
for something,
and breaking as soon as it’s found.
The else
would be run if the while
loop completed
but the object was not found:
>>>
numbers
=
[
1
,
3
,
5
]
>>>
position
=
0
>>>
while
position
<
len
(
numbers
):
...
number
=
numbers
[
position
]
...
if
number
%
2
==
0
:
...
(
'Found even number'
,
number
)
...
break
...
position
+=
1
...
else
:
# break not called
...
(
'No even number found'
)
...
No even number found
Iterate with for
Python makes frequent use of iterators, for good reason. They make it possible for you to traverse data structures without knowing how large they are or how they are implemented. You can even iterate over data that is created on the fly, allowing processing of data streams that would otherwise not fit in the computer’s memory all at once.
It’s legal Python to step through a sequence like this:
>>>
rabbits
=
[
'Flopsy'
,
'Mopsy'
,
'Cottontail'
,
'Peter'
]
>>>
current
=
0
>>>
while
current
<
len
(
rabbits
):
...
(
rabbits
[
current
])
...
current
+=
1
...
Flopsy
Mopsy
Cottontail
Peter
But there’s a better, more Pythonic way:
>>>
for
rabbit
in
rabbits
:
...
(
rabbit
)
...
Flopsy
Mopsy
Cottontail
Peter
Lists such as rabbits
are one of Python’s
iterable objects,
along with strings, tuples, dictionaries, sets,
and some other elements.
Tuple or list iteration produces an item at a time.
String iteration produces a character at a time, as shown here:
>>>
word
=
'cat'
>>>
for
letter
in
word
:
...
(
letter
)
...
c
a
t
Iterating over a dictionary (or its keys()
function)
returns the keys.
In this example,
the keys are the types of cards in the board game Clue
(Cluedo outside of North America):
>>>
accusation
=
{
'room'
:
'ballroom'
,
'weapon'
:
'lead pipe'
,
'person': 'Col. Mustard'}
>>>
for
card
in
accusation
:
# or, for card in accusation.keys():
...
(
card
)
...
room
weapon
person
To iterate over the values rather than the keys, you use the dictionary’s values()
function:
>>>
for
value
in
accusation
.
values
():
...
(
value
)
...
ballroom
lead pipe
Col. Mustard
To return both the key and value in a tuple, you can use the items()
function:
>>>
for
item
in
accusation
.
items
():
...
(
item
)
...
('room', 'ballroom')
('weapon', 'lead pipe')
('person', 'Col. Mustard')
Remember that you can assign to a tuple in one step.
For each tuple returned by items()
,
assign the first value (the key)
to card
and the second (the value) to contents
:
>>>
for
card
,
contents
in
accusation
.
items
():
...
(
'Card'
,
card
,
'has the contents'
,
contents
)
...
Card weapon has the contents lead pipe
Card person has the contents Col. Mustard
Card room has the contents ballroom
Check break Use with else
Similar to while
, for
has an optional else
that checks if the for
completed normally.
If break
was not called,
the else
statement is run.
This is useful when you want to verify that the
previous for
loop ran to completion,
instead of being stopped early with a break
.
The for
loop in the following example prints the name of the cheese
and breaks if any cheese is found in the cheese shop:
>>>
cheeses
=
[]
>>>
for
cheese
in
cheeses
:
...
(
'This shop has some lovely'
,
cheese
)
...
break
...
else
:
# no break means no cheese
...
(
'This is not much of a cheese shop, is it?'
)
...
This is not much of a cheese shop, is it?
Note
As with while
, the use of else
with for
might seem nonintuitive.
It makes more sense if you think of the for
as
looking for something,
and else
being called if you didn’t find it.
To get the same effect without else
,
use some variable to indicate
whether you found what you wanted in the for
loop, as demonstrated here:
>>>
cheeses
=
[]
>>>
found_one
=
False
>>>
for
cheese
in
cheeses
:
...
found_one
=
True
...
(
'This shop has some lovely'
,
cheese
)
...
break
...
>>>
if
not
found_one
:
...
(
'This is not much of a cheese shop, is it?'
)
...
This is not much of a cheese shop, is it?
Iterate Multiple Sequences with zip()
There’s one more nice iteration trick: iterating over
multiple sequences in parallel by using the zip()
function:
>>>
days
=
[
'Monday'
,
'Tuesday'
,
'Wednesday'
]
>>>
fruits
=
[
'banana'
,
'orange'
,
'peach'
]
>>>
drinks
=
[
'coffee'
,
'tea'
,
'beer'
]
>>>
desserts
=
[
'tiramisu'
,
'ice cream'
,
'pie'
,
'pudding'
]
>>>
for
day
,
fruit
,
drink
,
dessert
in
zip
(
days
,
fruits
,
drinks
,
desserts
):
...
(
day
,
": drink"
,
drink
,
"- eat"
,
fruit
,
"- enjoy"
,
dessert
)
...
Monday : drink coffee - eat banana - enjoy tiramisu
Tuesday : drink tea - eat orange - enjoy ice cream
Wednesday : drink beer - eat peach - enjoy pie
zip()
stops when the shortest sequence is done.
One of the lists (desserts
) was longer than the others,
so no one gets any pudding unless we extend the other lists.
“Dictionaries” shows you how the dict()
function can create dictionaries from two-item sequences
like tuples, lists, or strings.
You can use zip()
to walk through multiple sequences
and make tuples from items at the same offsets.
Let’s make two tuples of corresponding English and
French words:
>>>
english
=
'Monday'
,
'Tuesday'
,
'Wednesday'
>>>
french
=
'Lundi'
,
'Mardi'
,
'Mercredi'
Now, use zip()
to pair these tuples.
The value returned by zip()
is itself not a tuple or list, but an iterable value that
can be turned into one:
>>>
list
(
zip
(
english
,
french
)
)
[('Monday', 'Lundi'), ('Tuesday', 'Mardi'), ('Wednesday', 'Mercredi')]
Feed the result of zip()
directly to dict()
and voilà:
a tiny English-French dictionary!
>>>
dict
(
zip
(
english
,
french
)
)
{'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi'}
Generate Number Sequences with range()
The range()
function returns a stream of numbers within a specified range.
without first having to create and store a large data structure such as a list or tuple.
This lets you create huge ranges
without using all the memory in your computer
and crashing your program.
You use range()
similar to how to you use slices:
range(
start
, stop
, step
)
.
If you omit start
, the range begins at 0
.
The only required value is stop
;
as with slices, the last value created will be
just before stop
.
The default value of step
is 1
,
but you can go backward with -1
.
Like zip()
, range()
returns an iterable object,
so you need to step through the values with for ... in
,
or convert the object to a sequence like a list.
Let’s make the range 0, 1, 2
:
>>>
for
x
in
range
(
0
,
3
):
...
(
x
)
...
0
1
2
>>>
list
(
range
(
0
,
3
)
)
[0, 1, 2]
Here’s how to make a range from 2
down to 0
:
>>>
for
x
in
range
(
2
,
-
1
,
-
1
):
...
(
x
)
...
2
1
0
>>>
list
(
range
(
2
,
-
1
,
-
1
)
)
[2, 1, 0]
The following snippet uses a step size of 2
to get the even numbers
from 0
to 10
:
>>>
list
(
range
(
0
,
11
,
2
)
)
[0, 2, 4, 6, 8, 10]
Comprehensions
A comprehension is a compact way of creating a Python data structure from one or more iterators. Comprehensions make it possible for you to combine loops and conditional tests with a less verbose syntax. Using a comprehension is sometimes taken as a sign that you know Python at more than a beginner’s level. In other words, it’s more Pythonic.
List Comprehensions
You could build a list of integers from 1
to 5
,
one item at a time, like this:
>>>
number_list
=
[]
>>>
number_list
.
append
(
1
)
>>>
number_list
.
append
(
2
)
>>>
number_list
.
append
(
3
)
>>>
number_list
.
append
(
4
)
>>>
number_list
.
append
(
5
)
>>>
number_list
[1, 2, 3, 4, 5]
Or, you could also use an iterator and the range()
function:
>>>
number_list
=
[]
>>>
for
number
in
range
(
1
,
6
):
...
number_list
.
append
(
number
)
...
>>>
number_list
[1, 2, 3, 4, 5]
Or, you could just turn the output of range()
into a list directly:
>>>
number_list
=
list
(
range
(
1
,
6
))
>>>
number_list
[1, 2, 3, 4, 5]
All of these approaches are valid Python code and will produce the same result. However, a more Pythonic way to build a list is by using a list comprehension. The simplest form of list comprehension is:
[
expression
for
item
in
iterable
]
Here’s how a list comprehension would build the integer list:
>>>
number_list
=
[
number
for
number
in
range
(
1
,
6
)]
>>>
number_list
[1, 2, 3, 4, 5]
In the first line,
you need the first number
variable to produce values for the list:
that is, to put a
result of the loop into number_list
.
The second number
is part of
the for
loop.
To show that the first number
is an expression, try
this variant:
>>>
number_list
=
[
number
-
1
for
number
in
range
(
1
,
6
)]
>>>
number_list
[0, 1, 2, 3, 4]
The list comprehension moves the loop inside the square brackets. This comprehension example really wasn’t simpler than the previous example, but there’s more. A list comprehension can include a conditional expression, looking something like this:
[
expression
for
item
in
iterable
if
condition
]
Let’s make a new comprehension that
builds a list of only the odd numbers between 1
and 5
(remember that number % 2
is True
for odd numbers and False
for even numbers):
>>>
a_list
=
[
number
for
number
in
range
(
1
,
6
)
if
number
%
2
==
1
]
>>>
a_list
[1, 3, 5]
Now, the comprehension is a little more compact than its traditional counterpart:
>>>
a_list
=
[]
>>>
for
number
in
range
(
1
,
6
):
...
if
number
%
2
==
1
:
...
a_list
.
append
(
number
)
...
>>>
a_list
[1, 3, 5]
Finally,
just as there can be nested loops,
there can be more than one set of for ...
clauses
in the corresponding comprehension.
To show this, let’s first try a plain, old
nested loop and print the results:
>>>
rows
=
range
(
1
,
4
)
>>>
cols
=
range
(
1
,
3
)
>>>
for
row
in
rows
:
...
for
col
in
cols
:
...
(
row
,
col
)
...
1 1
1 2
2 1
2 2
3 1
3 2
Now, let’s use a comprehension and assign it to the
variable cells
,
making it a list of (row, col)
tuples:
>>>
rows
=
range
(
1
,
4
)
>>>
cols
=
range
(
1
,
3
)
>>>
cells
=
[(
row
,
col
)
for
row
in
rows
for
col
in
cols
]
>>>
for
cell
in
cells
:
...
(
cell
)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
By the way, you can also use tuple unpacking
to
yank the row
and col
values from each tuple
as you iterate over the cells
list:
>>>
for
row
,
col
in
cells
:
...
(
row
,
col
)
...
1 1
1 2
2 1
2 2
3 1
3 2
The for row ...
and for col ...
fragments
in the list comprehension
could also have had their own if
tests.
Dictionary Comprehensions
Not to be outdone by mere lists, dictionaries also have comprehensions. The simplest form looks familiar:
{
key_expression
:
value_expression
for
expression
in
iterable
}
Similar to list comprehensions,
dictionary comprehensions can also have if
tests and multiple
for
clauses:
>>>
word
=
'letters'
>>>
letter_counts
=
{
letter
:
word
.
count
(
letter
)
for
letter
in
word
}
>>>
letter_counts
{'l': 1, 'e': 2, 't': 2, 'r': 1, 's': 1}
We are running a loop over each of the seven letters in the string
'letters'
and counting how many times that letter appears. Two of
our uses of word.count(letter)
are a waste of time because we have
to count all the e
’s twice and all the t
’s twice. But, when we
count the e
’s the second time, we do no harm because we just replace
the entry in the dictionary that was already there; the same goes for
counting the t
’s.
So, the following would have been a teeny bit more Pythonic:
>>>
word
=
'letters'
>>>
letter_counts
=
{
letter
:
word
.
count
(
letter
)
for
letter
in
set
(
word
)}
>>>
letter_counts
{'t': 2, 'l': 1, 'e': 2, 'r': 1, 's': 1}
The dictionary’s keys are in a different order than the previous example,
because iterating set(word)
returns letters in a different order than iterating
the string word
.
Set Comprehensions
No one wants to be left out, so even sets have comprehensions. The simplest version looks like the list and dictionary comprehensions that you’ve just seen:
{
expression
for
expression
in
iterable
}
The longer versions (if
tests, multiple for
clauses)
are also valid for sets:
>>>
a_set
=
{
number
for
number
in
range
(
1
,
6
)
if
number
%
3
==
1
}
>>>
a_set
{1, 4}
Generator Comprehensions
Tuples do not have comprehensions! You might have thought that changing the square brackets of a list comprehension to parentheses would create a tuple comprehension. And it would appear to work because there’s no exception if you type this:
>>>
number_thing
=
(
number
for
number
in
range
(
1
,
6
))
The thing between the parentheses is a generator comprehension, and it returns a generator object:
>>>
type
(
number_thing
)
<class 'generator'>
I’ll get into generators in more detail in “Generators”. A generator is one way to provide data to an iterator.
You can iterate over this generator object directly, as illustrated here:
>>>
for
number
in
number_thing
:
...
(
number
)
...
1
2
3
4
5
Or, you can wrap a list()
call around a
generator comprehension to make it work like a
list comprehension:
>>>
number_list
=
list
(
number_thing
)
>>>
number_list
[
1
,
2
,
3
,
4
,
5
]
Note
A generator can be run only once. Lists, sets, strings, and dictionaries exist in memory, but a generator creates its values on the fly and hands them out one at a time through an iterator. It doesn’t remember them, so you can’t restart or back up a generator.
If you try to re-iterate this generator, you’ll find that it’s tapped out:
>>>
try_again
=
list
(
number_thing
)
>>>
try_again
[]
You can create a generator from a generator comprehension, as we did here, or from a generator function. We’ll talk about functions in general first, and then we’ll get to the special case of generator functions.
Functions
So far, all our Python code examples have been little fragments. These are good for small tasks, but no one wants to retype fragments all the time. We need some way of organizing larger code into manageable pieces.
The first step to code reuse is the function: a named piece of code, separate from all others. A function can take any number and type of input parameters and return any number and type of output results.
You can do two things with a function:
-
Define it
-
Call it
To define a Python function,
you type def
, the function name,
parentheses enclosing any input parameters
to the function,
and then finally, a colon (:
).
Function names have the same rules as variable names
(they must start with a letter or _
and contain only
letters, numbers, or _
).
Let’s take things one step at a time, and first define and call a function that has no parameters. Here’s the simplest Python function:
>>>
def
do_nothing
():
...
pass
Even for a function with no parameters like this one,
you still need the parentheses
and the colon in its definition.
The next line needs to be indented,
just as you would indent code under an if
statement.
Python requires the pass
statement to show that
this function does nothing.
It’s the equivalent of
This page intentionally left blank
(even though it isn’t anymore).
You call this function just by typing its name and parentheses. It works as advertised, doing nothing very well:
>>>
do_nothing
()
>>>
Now, let’s define and call another function that has no parameters but prints a single word:
>>>
def
make_a_sound
():
...
(
'quack'
)
...
>>>
make_a_sound
()
quack
When you called the make_a_sound()
function,
Python ran the code inside its definition.
In this case, it printed a single word
and returned to the main program.
Let’s try a function that has no parameters but returns a value:
>>>
def
agree
():
...
return
True
...
You can call this function and test
its returned value by using if
:
>>>
if
agree
():
...
(
'Splendid!'
)
...
else
:
...
(
'That was unexpected.'
)
...
Splendid!
You’ve just made a big step.
The combination of functions with tests such as if
and loops such as while
make it possible for you to do things
that you could not do before.
At this point, it’s time to put something between those
parentheses.
Let’s define the function echo()
with one
parameter called anything
.
It uses the return
statement to
send the value of anything
back to its caller
twice, with a space between:
>>>
def
echo
(
anything
):
...
return
anything
+
' '
+
anything
...
>>>
Now let’s call echo()
with the string 'Rumplestiltskin'
:
>>>
echo
(
'Rumplestiltskin'
)
'Rumplestiltskin Rumplestiltskin'
The values you pass into the function when you call it
are known as arguments.
When you call a function with arguments,
the values of those
arguments are copied to their corresponding parameters inside the function.
In the previous example,
the function echo()
was called with the argument string
'Rumplestiltskin'
.
This value was copied within echo()
to the parameter
anything
, and then returned
(in this case doubled, with a space)
to the caller.
These function examples were pretty basic.
Let’s write a function that takes an input argument
and actually does something with it.
We’ll adapt the earlier code fragment that comments on a color.
Call it commentary
and have it take an input string parameter called color
.
Make it return the string description to its caller,
which can decide what to do with it:
>>>
def
commentary
(
color
):
...
if
color
==
'red'
:
...
return
"It's a tomato."
...
elif
color
==
"green"
:
...
return
"It's a green pepper."
...
elif
color
==
'bee purple'
:
...
return
"I don't know what it is, but only bees can see it."
...
else
:
...
return
"I've never heard of the color "
+
color
+
"."
...
>>>
Call the function commentary()
with the string argument 'blue'
.
>>>
comment
=
commentary
(
'blue'
)
The function does the following:
-
Assigns the value
'blue'
to the function’s internalcolor
parameter -
Runs through the
if
-elif
-else
logic chain -
Returns a string
-
Assigns the string to the variable
comment
What do we get back?
>>>
(
comment
)
I've never heard of the color blue.
A function can take any number of input arguments
(including zero) of any type. It can return any number of output results
(also including zero) of any type.
If a function doesn’t call return
explicitly,
the caller gets the result None
.
>>>
(
do_nothing
())
None
Positional Arguments
Python handles function arguments in a manner that’s unusually flexible, when compared to many languages. The most familiar types of arguments are positional arguments, whose values are copied to their corresponding parameters in order.
This function builds a dictionary from its positional input arguments and returns it:
>>>
def
menu
(
wine
,
entree
,
dessert
):
...
return
{
'wine'
:
wine
,
'entree'
:
entree
,
'dessert'
:
dessert
}
...
>>>
menu
(
'chardonnay'
,
'chicken'
,
'cake'
)
{'dessert': 'cake', 'wine': 'chardonnay', 'entree': 'chicken'}
Although very common, a downside of positional
arguments is that you need to remember the
meaning of each position.
If we forgot and called menu()
with wine as the last argument instead of the first, the meal would be
very different:
>>>
menu
(
'beef'
,
'bagel'
,
'bordeaux'
)
{'dessert': 'bordeaux', 'wine': 'beef', 'entree': 'bagel'}
Keyword Arguments
To avoid positional argument confusion, you can specify arguments by the names of their corresponding parameters, even in a different order from their definition in the function:
>>>
menu
(
entree
=
'beef'
,
dessert
=
'bagel'
,
wine
=
'bordeaux'
)
{'dessert': 'bagel', 'wine': 'bordeaux', 'entree': 'beef'}
You can mix positional and keyword arguments. Let’s specify the wine first, but use keyword arguments for the entree and dessert:
>>>
menu
(
'frontenac'
,
dessert
=
'flan'
,
entree
=
'fish'
)
{'entree': 'fish', 'dessert': 'flan', 'wine': 'frontenac'}
If you call a function with both positional and keyword arguments, the positional arguments need to come first.
Specify Default Parameter Values
You can specify default values for parameters. The default is used if the caller does not provide a corresponding argument. This bland-sounding feature can actually be quite useful. Using the previous example:
>>>
def
menu
(
wine
,
entree
,
dessert
=
'pudding'
):
...
return
{
'wine'
:
wine
,
'entree'
:
entree
,
'dessert'
:
dessert
}
This time, try calling menu()
without the dessert
argument:
>>>
menu
(
'chardonnay'
,
'chicken'
)
{'dessert': 'pudding', 'wine': 'chardonnay', 'entree': 'chicken'}
If you do provide an argument, it’s used instead of the default:
>>>
menu
(
'dunkelfelder'
,
'duck'
,
'doughnut'
)
{'dessert': 'doughnut', 'wine': 'dunkelfelder', 'entree': 'duck'}
Note
Default argument values are calculated when the function is defined, not when it is run. A common error with new (and sometimes not-so-new) Python programmers is to use a mutable data type such as a list or dictionary as a default argument.
In the following test,
the buggy()
function
is expected to run each time with a fresh
empty result
list,
add the arg
argument to it,
and then print a single-item list.
However, there’s a bug:
it’s empty only the first time it’s called.
The second time, result
still has one item
from the previous call:
>>>
def
buggy
(
arg
,
result
=
[]):
...
result
.
append
(
arg
)
...
(
result
)
...
>>>
buggy
(
'a'
)
['a']
>>>
buggy
(
'b'
)
# expect ['b']
['a', 'b']
It would have worked if it had been written like this:
>>>
def
works
(
arg
):
...
result
=
[]
...
result
.
append
(
arg
)
...
return
result
...
>>>
works
(
'a'
)
['a']
>>>
works
(
'b'
)
['b']
The fix is to pass in something else to indicate the first call:
>>>
def
nonbuggy
(
arg
,
result
=
None
):
...
if
result
is
None
:
...
result
=
[]
...
result
.
append
(
arg
)
...
(
result
)
...
>>>
nonbuggy
(
'a'
)
['a']
>>>
nonbuggy
(
'b'
)
['b']
Gather Positional Arguments with *
If you’ve programmed in C or C++, you might assume that
an asterisk (*
) in a Python program has something to do with a pointer.
Nope, Python doesn’t have pointers.
When used inside the function with a parameter,
an asterisk groups a variable number of positional arguments
into a tuple of parameter values.
In the following example, args
is the parameter
tuple that resulted from the arguments that were
passed to the function print_args()
:
>>>
def
print_args
(
*
args
):
...
(
'Positional argument tuple:'
,
args
)
...
If you call it with no arguments,
you get nothing in *args
:
>>>
print_args
()
Positional argument tuple: ()
Whatever you give it will be printed as the args
tuple:
>>>
print_args
(
3
,
2
,
1
,
'wait!'
,
'uh...'
)
Positional argument tuple: (3, 2, 1, 'wait!', 'uh...')
This is useful for writing functions such as print()
that accept
a variable number of arguments.
If your function has required positional arguments as well,
*args
goes at the end and grabs all the rest:
>>>
def
print_more
(
required1
,
required2
,
*
args
):
...
(
'Need this one:'
,
required1
)
...
(
'Need this one too:'
,
required2
)
...
(
'All the rest:'
,
args
)
...
>>>
print_more
(
'cap'
,
'gloves'
,
'scarf'
,
'monocle'
,
'mustache wax'
)
Need this one: cap
Need this one too: gloves
All the rest: ('scarf', 'monocle', 'mustache wax')
When using *
, you don’t need to call the tuple
parameter args
, but it’s a common idiom in Python.
Gather Keyword Arguments with **
You can use two asterisks (**
) to group keyword arguments into a dictionary,
where the argument names are the keys,
and their values are the corresponding dictionary values.
The following example defines the function print_kwargs()
to
print its keyword arguments:
>>>
def
print_kwargs
(
**
kwargs
):
...
(
'Keyword arguments:'
,
kwargs
)
...
Now, try calling it with some keyword arguments:
>>>
print_kwargs
(
wine
=
'merlot'
,
entree
=
'mutton'
,
dessert
=
'macaroon'
)
Keyword arguments: {'dessert': 'macaroon', 'wine': 'merlot', 'entree': 'mutton'}
Inside the function, kwargs
is a dictionary.
If you mix positional parameters with *args
and **kwargs
,
they need to occur in that order.
As with args
, you don’t need to call this keyword
parameter kwargs
, but it’s common usage.
Docstrings
Readability counts, says the Zen of Python. You can attach documentation to a function definition by including a string at the beginning of the function body. This is the function’s docstring:
>>>
def
echo
(
anything
):
...
'echo returns its input argument'
...
return
anything
You can make a docstring quite long and even add rich formatting, if you want, as is demonstrated in the following:
def print_if_true(thing, check):
'''
Prints the first argument if a second argument is true.
The operation is:
1. Check whether the *second* argument is true.
2. If it is, print the *first* argument.
'''
if check:
print(thing)
To print a function’s docstring,
call the Python help()
function.
Pass the function’s name to get a
listing of arguments along with the nicely formatted docstring:
>>>
help
(
echo
)
Help on function echo in module __main__:
echo(anything)
echo returns its input argument
If you want to see just the raw docstring, without the formatting:
>>>
(
echo
.
__doc__
)
echo returns its input argument
That odd-looking __doc__
is the internal name of the docstring
as a variable within the function.
“Uses of _ and __ in Names” explains the reason
behind all those underscores.
Functions Are First-Class Citizens
I’ve mentioned the Python mantra, everything is an object. This includes numbers, strings, tuples, lists, dictionaries—and functions, as well. Functions are first-class citizens in Python. You can assign them to variables, use them as arguments to other functions, and return them from functions. This gives you the capability to do some things in Python that are difficult-to-impossible to carry out in many other languages.
To test this, let’s define a simple
function called answer()
that doesn’t have any arguments; it just prints the number 42
:
>>>
def
answer
():
...
(
42
)
If you run this function, you know what you’ll get:
>>>
answer
()
42
Now, let’s define another function named
run_something
.
It has one argument called func
,
a function to run.
Once inside, it just calls the function.
>>>
def
run_something
(
func
):
...
func
()
If we pass answer
to run_something()
,
we’re using a function as data, just as with anything
else:
>>>
run_something
(
answer
)
42
Notice that you passed answer
, not answer()
.
In Python, those parentheses mean
call this function.
With no parentheses,
Python just treats the function like any other object.
That’s because, like everything else in Python,
it is an object:
>>>
type
(
run_something
)
<class 'function'>
Let’s try running a function with arguments.
Define a function add_args()
that
prints the sum of its two numeric arguments,
arg1
and arg2
:
>>>
def
add_args
(
arg1
,
arg2
):
...
(
arg1
+
arg2
)
And what is add_args()
?
>>>
type
(
add_args
)
<class 'function'>
At this point, let’s define a function
called run_something_with_args()
that takes three arguments:
-
func
—The function to run -
arg1
—The first argument forfunc
-
arg2
—The second argument forfunc
>>>
def
run_something_with_args
(
func
,
arg1
,
arg2
):
...
func
(
arg1
,
arg2
)
When you call run_something_with_args()
,
the function passed by the caller is assigned to the func
parameter,
whereas arg1
and arg2
get the values that follow in the argument list.
Then, running func(arg1, arg2)
executes that function
with those arguments
because the parentheses told Python to do so.
Let’s test it by passing the function name add_args
and the arguments 5
and 9
to
run_something_with_args()
:
>>>
run_something_with_args
(
add_args
,
5
,
9
)
14
Within the function run_something_with_args()
,
the function name argument add_args
was assigned
to the parameter func
,
5
to arg1
,
and 9
to arg2
.
This ended up running:
add_args(5, 9)
You can combine this with the *args
and **kwargs
techniques.
Let’s define a test function that takes
any number of positional arguments,
calculates their sum by using the
sum()
function,
and then returns that sum:
>>>
def
sum_args
(
*
args
):
...
return
sum
(
args
)
I haven’t mentioned sum()
before.
It’s a built-in Python function
that calculates the sum of the values in
its iterable numeric
(int or float) argument.
We’ll define the new function
run_with_positional_args()
, which takes a function
and any number of positional arguments
to pass to it:
>>>
def
run_with_positional_args
(
func
,
*
args
):
...
return
func
(
*
args
)
Now, go ahead and call it:
>>>
run_with_positional_args
(
sum_args
,
1
,
2
,
3
,
4
)
10
You can use functions as elements of lists, tuples, sets, and dictionaries. Functions are immutable, so you can also use them as dictionary keys.
Inner Functions
You can define a function within another function:
>>>
def
outer
(
a
,
b
):
...
def
inner
(
c
,
d
):
...
return
c
+
d
...
return
inner
(
a
,
b
)
...
>>>
>>>
outer
(
4
,
7
)
11
An inner function can be useful when performing some complex task more than once within another function, to avoid loops or code duplication. For a string example, this inner function adds some text to its argument:
>>>
def
knights
(
saying
):
...
def
inner
(
quote
):
...
return
"We are the knights who say: '
%s
'"
%
quote
...
return
inner
(
saying
)
...
>>>
knights
(
'Ni!'
)
"We are the knights who say: 'Ni!'"
Closures
An inner function can act as a closure. This is a function that is dynamically generated by another function and can both change and remember the values of variables that were created outside the function.
The following example builds on the previous knights()
example.
Let’s call the new one knights2()
,
because we have no imagination,
and turn the inner()
function into a closure
called inner2()
.
Here are the differences:
-
inner2()
uses the outersaying
parameter directly instead of getting it as an argument. -
knights2()
returns theinner2
function name instead of calling it.
>>>
def
knights2
(
saying
):
...
def
inner2
():
...
return
"We are the knights who say: '
%s
'"
%
saying
...
return
inner2
...
The inner2()
function knows the
value of saying
that was passed in
and remembers it.
The line return inner2
returns this
specialized copy of the inner2
function
(but doesn’t call it).
That’s a closure: a dynamically created
function that remembers where it came from.
Let’s call knights2()
twice,
with different arguments:
>>>
a
=
knights2
(
'Duck'
)
>>>
b
=
knights2
(
'Hasenpfeffer'
)
Okay, so what are a
and b
?
>>>
type
(
a
)
<class 'function'>
>>>
type
(
b
)
<class 'function'>
They’re functions, but they’re also closures:
>>>
a
<function knights2.<locals>.inner2 at 0x10193e158>
>>>
b
<function knights2.<locals>.inner2 at 0x10193e1e0>
If we call them, they remember the saying
that was used when they were created
by knights2
:
>>>
a
()
"We are the knights who say: 'Duck'"
>>>
b
()
"We are the knights who say: 'Hasenpfeffer'"
Anonymous Functions: the lambda() Function
In Python, a lambda function is an anonymous function expressed as a single statement. You can use it instead of a normal tiny function.
To illustrate it, let’s first make an example
that uses normal functions.
To begin, we’ll define the function edit_story()
.
Its arguments are the following:
-
words
—a list of words -
func
—a function to apply to each word inwords
>>>
def
edit_story
(
words
,
func
):
...
for
word
in
words
:
...
(
func
(
word
))
Now, we need a list of words and a function to apply to each word. For the words, here’s a list of (hypothetical) sounds made by my cat if he (hypothetically) missed one of the stairs:
>>>
stairs
=
[
'thud'
,
'meow'
,
'thud'
,
'hiss'
]
And for the function, this will capitalize each word and append an exclamation point, perfect for feline tabloid newspaper headlines:
>>>
def
enliven
(
word
):
# give that prose more punch
...
return
word
.
capitalize
()
+
'!'
Mixing our ingredients:
>>>
edit_story
(
stairs
,
enliven
)
Thud!
Meow!
Thud!
Hiss!
Finally, we get to the lambda.
The enliven()
function was so brief that we
could replace it with a lambda:
>>>
>>>
edit_story
(
stairs
,
lambda
word
:
word
.
capitalize
()
+
'!'
)
Thud!
Meow!
Thud!
Hiss!
>>>
The lambda takes one argument, which we call word
here.
Everything
between the colon and the terminating parenthesis is the definition of the function.
Often, using real functions such as enliven()
is much clearer than using lambdas.
Lambdas are mostly useful for cases in which
you would otherwise need
to define many tiny functions
and remember what you called them all.
In particular, you can use lambdas in graphical user interfaces
to define
callback functions;
see Appendix A for examples.
Generators
A generator is a Python sequence creation object.
With it, you can iterate through potentially huge sequences
without creating and storing the entire sequence in memory at once.
Generators are often the source of data for iterators.
If you recall, we already used one of them, range()
, in earlier code examples to generate a series of integers. In Python 2, range()
returns a list,
which limits it to fit in memory.
Python 2 also has the generator xrange()
,
which became the normal range()
in Python 3.
This example adds all the integers from 1 to 100:
>>>
sum
(
range
(
1
,
101
))
5050
Every time you iterate through a generator, it keeps track of where it was the last time it was called and returns the next value. This is different from a normal function, which has no memory of previous calls and always starts at its first line with the same state.
If you want to create a potentially large sequence,
and the code is too large for
a generator comprehension,
write a generator function.
It’s a normal function, but it
returns its value with a yield
statement rather than return
.
Let’s write our own version of range()
:
>>>
def
my_range
(
first
=
0
,
last
=
10
,
step
=
1
):
...
number
=
first
...
while
number
<
last
:
...
yield
number
...
number
+=
step
...
It’s a normal function:
>>>
my_range
<function my_range at 0x10193e268>
And it returns a generator object:
>>>
ranger
=
my_range
(
1
,
5
)
>>>
ranger
<generator object my_range at 0x101a0a168>
We can iterate over this generator object:
>>>
for
x
in
ranger
:
...
(
x
)
...
1
2
3
4
Decorators
Sometimes, you want to modify an existing function without changing its source code. A common example is adding a debugging statement to see what arguments were passed in.
A decorator is a function that takes one function as input and returns another function. We’ll dig into our bag of Python tricks and use the following:
-
*args
and**kwargs
-
Inner functions
-
Functions as arguments
The function document_it()
defines a decorator
that will do the following:
-
Print the function’s name and the values of its arguments
-
Run the function with the arguments
-
Print the result
-
Return the modified function for use
Here’s what the code looks like:
>>>
def
document_it
(
func
):
...
def
new_function
(
*
args
,
**
kwargs
):
...
(
'Running function:'
,
func
.
__name__
)
...
(
'Positional arguments:'
,
args
)
...
(
'Keyword arguments:'
,
kwargs
)
...
result
=
func
(
*
args
,
**
kwargs
)
...
(
'Result:'
,
result
)
...
return
result
...
return
new_function
Whatever func
you pass to document_it()
, you get a new function that includes the
extra statements that document_it()
adds. A decorator doesn’t actually
have to run any code from func
, but document_it()
calls func
part way through so that you get the results of func
as well as all
the extras.
So, how do you use this? You can apply the decorator manually:
>>>
def
add_ints
(
a
,
b
):
...
return
a
+
b
...
>>>
add_ints
(
3
,
5
)
8
>>>
cooler_add_ints
=
document_it
(
add_ints
)
# manual decorator assignment
>>>
cooler_add_ints
(
3
,
5
)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 8
8
As an alternative to the manual
decorator assignment above,
just add @decorator_name
before
the function that you want to decorate:
>>>
@document_it
...
def
add_ints
(
a
,
b
):
...
return
a
+
b
...
>>>
add_ints
(
3
,
5
)
Start function add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 8
8
You can have more than one decorator for a function.
Let’s write another decorator called
square_it()
that squares the result:
>>>
def
square_it
(
func
):
...
def
new_function
(
*
args
,
**
kwargs
):
...
result
=
func
(
*
args
,
**
kwargs
)
...
return
result
*
result
...
return
new_function
...
The decorator that’s used closest to the function (just above the def
)
runs first and
then the one above it.
Either order gives the same end result,
but you can see how the intermediate steps change:
>>>
@document_it
...
@square_it
...
def
add_ints
(
a
,
b
):
...
return
a
+
b
...
>>>
add_ints
(
3
,
5
)
Running function: new_function
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 64
64
Let’s try reversing the decorator order:
>>>
@square_it
...
@document_it
...
def
add_ints
(
a
,
b
):
...
return
a
+
b
...
>>>
add_ints
(
3
,
5
)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 8
64
Namespaces and Scope
A name can refer to different things, depending on where it’s used. Python programs have various namespaces—sections within which a particular name is unique and unrelated to the same name in other namespaces.
Each function defines its own namespace.
If you define a variable called x
in a main program and another variable called x
in a function,
they refer to different things.
But the walls can be breached:
if you need to, you can access names in other namespaces in various ways.
The main part of a program defines the global namespace; thus, the variables in that namespace are global variables.
You can get the value of a global variable from within a function:
>>>
animal
=
'fruitbat'
>>>
def
print_global
():
...
(
'inside print_global:'
,
animal
)
...
>>>
(
'at the top level:'
,
animal
)
at the top level: fruitbat
>>>
print_global
()
inside print_global: fruitbat
But, if you try to get the value of the global variable and change it within the function, you get an error:
>>>
def
change_and_print_global
():
...
(
'inside change_and_print_global:'
,
animal
)
...
animal
=
'wombat'
...
(
'after the change:'
,
animal
)
...
>>>
change_and_print_global
()
Traceback (most recent call last):
File"<stdin>"
, line1
, in<module>
File"<stdin>"
, line2
, inchange_and_print_global
UnboundLocalError
:local variable 'animal' referenced before assignment
If you just change it,
it changes a different variable also named animal
, but this variable is inside the function:
>>>
def
change_local
():
...
animal
=
'wombat'
...
(
'inside change_local:'
,
animal
,
id
(
animal
))
...
>>>
change_local
()
inside change_local: wombat 4330406160
>>>
animal
'fruitbat'
>>>
id
(
animal
)
4330390832
What happened here?
The first line assigned the string 'fruitbat'
to a global variable named animal
.
The change_local()
function also has a variable named animal
,
but that’s in its local namespace.
We used the Python function id()
here to print the unique value for each
object and prove that the variable animal
inside change_local()
is not the same as animal
at the main level of the program.
To access the global variable rather than the local one
within a function,
you need to be explicit
and use the global
keyword
(you knew this was coming:
explicit is better than implicit):
>>>
animal
=
'fruitbat'
>>>
def
change_and_print_global
():
...
global
animal
...
animal
=
'wombat'
...
(
'inside change_and_print_global:'
,
animal
)
...
>>>
animal
'fruitbat'
>>>
change_and_print_global
()
inside change_and_print_global: wombat
>>>
animal
'wombat'
If you don’t say global
within a function,
Python uses the local namespace and the variable
is local. It goes away after the function completes.
Python provides two functions to access the contents of your namespaces:
-
locals()
returns a dictionary of the contents of the local namespace. -
globals()
returns a dictionary of the contents of the global namespace.
And, here they are in use:
>>>
animal
=
'fruitbat'
>>>
def
change_local
():
...
animal
=
'wombat'
# local variable
...
(
'locals:'
,
locals
())
...
>>>
animal
'fruitbat'
>>>
change_local
()
locals: {'animal': 'wombat'}
>>>
(
'globals:'
,
globals
())
# reformatted a little for presentation
globals: {'animal': 'fruitbat',
'__doc__': None,
'change_local': <function change_it at 0x1006c0170>,
'__package__': None,
'__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__builtins__': <module 'builtins'>}
>>>
animal
'fruitbat'
The local namespace within change_local()
contained only the local variable animal
.
The global namespace contained
the separate global variable animal
and a number of other things.
Uses of _ and __ in Names
Names that begin and end with two underscores (__
)
are reserved for use within Python,
so you should not use them with your own variables.
This naming pattern was chosen because it seemed
unlikely to be selected by application
developers for their own variables.
For instance, the name of a function
is in the system variable
function
.__name__
,
and its documentation string is
function
.__doc__
:
>>>
def
amazing
():
...
'''This is the amazing function.
...
Want to see it again?'''
...
(
'This function is named:'
,
amazing
.
__name__
)
...
(
'And its docstring is:'
,
amazing
.
__doc__
)
...
>>>
amazing
()
This function is named: amazing
And its docstring is: This is the amazing function.
Want to see it again?
As you saw in the earlier globals
printout,
the main program is assigned the special name
__main__
.
Handle Errors with try and except
Do, or do not. There is no try.
Yoda
In some languages, errors are indicated by special function return values. Python uses exceptions: code that is executed when an associated error occurs.
You’ve seen some of these already, such as accessing a list or tuple with an out-of-range position, or a dictionary with a nonexistent key. When you run code that might fail under some circumstances, you also need appropriate exception handlers to intercept any potential errors.
It’s good practice to add exception handling anywhere an exception might occur to let the user know what is happening. You might not be able to fix the problem, but at least you can note the circumstances and shut your program down gracefully. If an exception occurs in some function and is not caught there, it bubbles up until it is caught by a matching handler in some calling function. If you don’t provide your own exception handler, Python prints an error message and some information about where the error occurred and then terminates the program, as demonstrated in the following snippet.
>>>
short_list
=
[
1
,
2
,
3
]
>>>
position
=
5
>>>
short_list
[
position
]
Traceback (most recent call last):
File"<stdin>"
, line1
, in<module>
IndexError
:list index out of range
Rather than leaving it to chance, use try
to wrap your code,
and except
to provide the error handling:
>>>
short_list
=
[
1
,
2
,
3
]
>>>
position
=
5
>>>
try
:
...
short_list
[
position
]
...
except
:
...
(
'Need a position between 0 and'
,
len
(
short_list
)
-
1
,
' but got'
,
...
position
)
...
Need a position between 0 and 2 but got 5
The code inside the try
block is run.
If there is an error,
an exception is raised and
the code inside the except
block runs.
If there are no errors,
the except
block is skipped.
Specifying a plain except
with no arguments,
as we did here,
is a catchall for any
exception type.
If more than one type of exception could occur,
it’s best to provide a separate exception handler for each.
No one forces you to do this;
you can use a bare except
to catch all exceptions,
but your treatment of them would probably be generic
(something akin to printing Some error occurred).
You can use any number of specific exception handlers.
Sometimes, you want exception details beyond the type. You get the full exception object in the variable name if you use the form:
exceptexceptiontype
asname
The example that follows looks for an IndexError
first,
because that’s the exception type raised
when you provide an illegal position to a sequence.
It saves an IndexError
exception in the variable err
,
and any other exception in the variable other
.
The example prints everything stored in other
to show what you get
in that object.
>>>
short_list
=
[
1
,
2
,
3
]
>>>
while
True
:
...
value
=
input
(
'Position [q to quit]? '
)
...
if
value
==
'q'
:
...
break
...
try
:
...
position
=
int
(
value
)
...
(
short_list
[
position
])
...
except
IndexError
as
err
:
...
(
'Bad index:'
,
position
)
...
except
Exception
as
other
:
...
(
'Something else broke:'
,
other
)
...
Position [q to quit]? 1
2
Position [q to quit]? 0
1
Position [q to quit]? 2
3
Position [q to quit]? 3
Bad index: 3
Position [q to quit]? 2
3
Position [q to quit]? two
Something else broke: invalid literal for int() with base 10: 'two'
Position [q to quit]? q
Inputting position 3
raised an IndexError
as expected.
Entering two
annoyed the int()
function,
which we handled in our second, catchall except
code.
Make Your Own Exceptions
The previous section discussed handling exceptions,
but all of the exceptions (such as IndexError
)
were predefined in Python or its standard library.
You can use any of these for your own purposes.
You can also define your own exception types to handle special situations that
might arise in your own programs.
Note
This requires defining a new object type with a class—something we don’t get into until Chapter 6. So, if you’re unfamiliar with classes, you might want to return to this section later.
An exception is a class. It is a child of the class Exception
.
Let’s make an exception called UppercaseException
and raise it when we
encounter an uppercase word in a string.
>>>
class
UppercaseException
(
Exception
):
...
pass
...
>>>
words
=
[
'eeenie'
,
'meenie'
,
'miny'
,
'MO'
]
>>>
for
word
in
words
:
...
if
word
.
isupper
():
...
raise
UppercaseException
(
word
)
...
Traceback (most recent call last):
File"<stdin>"
, line3
, in<module>
__main__.UppercaseException
:MO
We didn’t even define any behavior for UppercaseException
(notice we just used pass
),
letting its parent class Exception
figure out what to print when the exception was raised.
You can access the exception object itself and print it:
>>>
try
:
...
raise
OopsException
(
'panic'
)
...
except
OopsException
as
exc
:
...
(
exc
)
...
panic
Things to Do
4.1 Assign the value 7
to the variable guess_me
. Then, write the conditional tests (if
, else
, and elif
) to print the string 'too low'
if guess_me
is less than 7
, 'too high'
if greater than 7
, and 'just right'
if equal to 7
.
4.2 Assign the value 7
to the variable guess_me
and the value 1
to the variable start
. Write a while
loop that compares start
with guess_me
. Print too low
if start
is less than guess me
. If start
equals guess_me
, print 'found it!'
and exit the loop. If start
is greater than guess_me
, print 'oops'
and exit the loop. Increment start
at the end of the loop.
4.3 Use a for
loop to print the values of the list [3, 2, 1, 0]
.
4.4 Use a list comprehension to make a list of the even numbers in range(10)
.
4.5 Use a dictionary comprehension to create the dictionary squares
. Use range(10)
to return the keys, and use the square of each key as its value.
4.6 Use a set comprehension to create the set odd
from the odd numbers in range(10)
.
4.7 Use a generator comprehension to return the string 'Got '
and a number for the numbers in range(10)
. Iterate through this by using a for
loop.
4.8 Define a function called good
that returns the list ['Harry', 'Ron', 'Hermione']
.
4.9 Define a generator function called get_odds
that returns the odd numbers from range(10)
. Use a for
loop to find and print the third value returned.
4.10 Define a decorator called test
that prints 'start'
when a function is called and 'end'
when it finishes.
4.11 Define an exception called OopsException
. Raise this exception to see what happens. Then write the code to catch this exception and print 'Caught an oops'
.
4.12 Use zip()
to make a dictionary called movies
that pairs these lists: titles = ['Creature of Habit', 'Crewel Fate']
and plots = ['A nun turns into a monster', 'A haunted yarn shop']
.
Get Introducing Python now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.