Some Tips, Tricks, and Common Errors¶
These are small summaries of ideas, tips, and commonly seen errors that might be helpful to those beginning Python.
Functions¶
Functions help us with our mental chunking: they allow us to group together statements for a high-level purpose, e.g. a function to sort a list of items, a function to make the turtle draw a spiral, or a function to compute the mean and standard deviation of some measurements.
There are two kinds of functions: fruitful, or value-returning functions, which calculate and return a value, and we use them
because we’re primarily interested in the value they’ll return. Void (non-fruitful) functions
are used because they perform actions that we want done — e.g. make a turtle draw a rectangle, or
print the first thousand prime numbers. They always return None
— a special dummy value.
Tip: None
is not a string
Values like None
, True
and False
are not strings: they are special values
in Python, and are in the list of keywords we gave in chapter 2 (Variables, expressions, and statements). Keywords are special
in the language: they are part of the syntax. So we cannot create our own
variable or function with a name True
— we’ll get a syntax error.
(Built-in functions are not privileged like keywords: we can define our own
variable or function called len
, but we’d be silly to do so!)
Along with the fruitful/void families of functions, there are two flavors of the
return
statement in Python: one that returns
a useful value, and the other that returns nothing, or None
. And if we get to the end of
any function and we have not explicitly executed any return
statement, Python automatically
returns the value None
.
Tip: Understand what the function needs to return
Perhaps nothing — some functions exists purely to perform actions rather than to calculate and return a result. But if the function should return a value, make sure all execution paths do return the value.
To make functions more useful, they are given parameters. So a function to make a turtle draw
a square might have two parameters — one for the turtle that needs to do the drawing, and another
for the size of the square. See the first example in Chapter 4 (Functions) — that function can be used with any turtle,
and for any size square. So it is much more general than a function that always uses a specific turtle,
say tess
to draw a square of a specific size, say 30.
Tip: Use parameters to generalize functions
Understand which parts of the function will be hard-coded and unchangeable, and which parts should become parameters so that they can be customized by the caller of the function.
Tip: Try to relate Python functions to ideas we already know
In math, we’re familiar with functions like f(x) = 3x + 5
. We already understand
that when we call the function f(3)
we make some association between the parameter x
and the argument 3. Try to draw parallels to argument passing in Python.
Quiz: Is the function f(z) = 3z + 5
the same as function f
above?
Problems with logic and flow of control¶
We often want to know if some condition holds for any item in a list, e.g. “does the list have any odd numbers?” This is a common mistake:
1 2 3 4 5 6 7 def any_odd(xs): # Buggy version """ Return True if there is an odd number in xs, a list of integers. """ for v in xs: if v % 2 == 1: return True else: return False
Can we spot two problems here? As soon as we execute a return
, we’ll leave the function.
So the logic of saying “If I find an odd number I can return True
” is fine. However, we cannot
return False
after only looking at one item — we can only return False
if we’ve been through
all the items, and none of them are odd. So line 6 should not be there, and line 7 has to be
outside the loop. To find the second problem above, consider what happens if you call this function
with an argument that is an empty list. Here is a corrected version:
1 2 3 4 5 6 def any_odd(xs): """ Return True if there is an odd number in xs, a list of integers. """ for v in xs: if v % 2 == 1: return True return False
This “eureka”, or “short-circuit” style of returning from a function as soon as we are certain what the outcome will be was first seen in Section 8.10, in the chapter on strings.
It is preferred over this one, which also works correctly:
1 2 3 4 5 6 7 8 9 10 def any_odd(xs): """ Return True if there is an odd number in xs, a list of integers. """ count = 0 for v in xs: if v % 2 == 1: count += 1 # Count the odd numbers if count > 0: return True else: return False
The performance disadvantage of this one is that it traverses the whole list, even if it knows the outcome very early on.
Tip: Think about the return conditions of the function
Do I need to look at all elements in all cases? Can I shortcut and take an early exit? Under what conditions? When will I have to examine all the items in the list?
The code in lines 7-10 can also be tightened up. The expression count > 0
evaluates to a Boolean value, either True
or False
. The value can be used
directly in the return
statement. So we could cut out that code and simply
have the following:
1 2 3 4 5 6 7 8 def any_odd(xs): """ Return True if there is an odd number in xs, a list of integers. """ count = 0 for v in xs: if v % 2 == 1: count += 1 # Count the odd numbers return count > 0 # Aha! a programmer who understands that Boolean # expressions are not just used in if statements!
Although this code is tighter, it is not as nice as the one that did the short-circuit return as soon as the first odd number was found.
Tip: Generalize your use of Booleans
Mature programmers won’t write if is_prime(n) == True:
when they could
say instead if is_prime(n):
Think more generally about Boolean values,
not just in the context of if
or while
statements. Like arithmetic
expressions, they have their own set of operators (and
, or
, not
) and
values (True
, False
) and can be assigned to variables, put into lists, etc.
A good resource for improving your use of Booleans is
http://en.wikibooks.org/wiki/Non-Programmer%27s_Tutorial_for_Python_3/Boolean_Expressions
Exercise time:
- How would we adapt this to make another function which returns
True
if all the numbers are odd? Can you still use a short-circuit style? - How would we adapt it to return
True
if at least three of the numbers are odd? Short-circuit the traversal when the third odd number is found — don’t traverse the whole list unless we have to.
Local variables¶
Functions are called, or activated, and while they’re busy they create their own stack frame which holds local variables. A local variable is one that belongs to the current activation. As soon as the function returns (whether from an explicit return statement or because Python reached the last statement), the stack frame and its local variables are all destroyed. The important consequence of this is that a function cannot use its own variables to remember any kind of state between different activations. It cannot count how many times it has been called, or remember to switch colors between red and blue UNLESS it makes use of variables that are global. Global variables will survive even after our function has exited, so they are the correct way to maintain information between calls.
1 2 3 4 5 6 7 sz = 2 def h2(): """ Draw the next step of a spiral on each call. """ global sz tess.turn(42) tess.forward(sz) sz += 1
This fragment assumes our turtle is tess
. Each time we call h2()
it turns, draws, and increases
the global variable sz
. Python always assumes that an assignment to a variable (as in line 7) means
that we want a new local variable, unless we’ve provided a global
declaration (on line 4). So
leaving out the global declaration means this does not work.
Tip: Local variables do not survive when you exit the function
Use a Python visualizer like the one at http://netserv.ict.ru.ac.za/python3_viz to build a strong understanding of function calls, stack frames, local variables, and function returns.
Tip: Assignment in a function creates a local variable
Any assignment to a variable within a function means Python will make a local variable,
unless we override with global
.
Event handler functions¶
Our chapter on event handling showed three different kinds of events that we could handle. They each have their own subtle points that can trip us up.
- Event handlers are void functions — they don’t return any values.
- They’re automatically called by the Python interpreter in response to an event, so we don’t get to see the code that calls them.
- A mouse-click event passes two coordinate arguments to its handler, so when we write this handler
we have to provide for two parameters (usually named
x
andy
). This is how the handler knows where the mouse click occurred. - A keypress event handler has to be bound to the key it responds to. There is a messy extra step
when using keypresses: we have to remember to issue a
wn.listen()
before our program will receive any keypresses. But if the user presses the key 10 times, the handler will be called ten times. - Using a timer to create a future-dated event only causes one call to the handler. If we want
repeated periodic handler activations, then from within the handler we
call
wn.ontimer(....)
to set up the next event.
String handling¶
There are only four really important operations on strings, and we’ll be able to do just about anything. There are many more nice-to-have methods (we’ll call them sugar coating) that can make life easier, but if we can work with the basic four operations smoothly, we’ll have a great grounding.
- len(str) finds the length of a string.
- str[i] the subscript operation extracts the i’th character of the string, as a new string.
- str[i:j] the slice operation extracts a substring out of a string.
- str.find(target) returns the index where target occurs within the string, or -1 if it is not found.
So if we need to know if “snake” occurs as a substring within s
, we could write
1 2 if s.find("snake") >= 0: ... if "snake" in s: ... # Also works, nice-to-know sugar coating!
It would be wrong to split the string into words unless we were asked whether the word “snake” occurred in the string.
Suppose we’re asked to read some lines of data and find function definitions, e.g.: def some_function_name(x, y):
,
and we are further asked to isolate and work with the name of the function. (Let’s say, print it.)
1 2 3 4 5 6 s = "..." # Get the next line from somewhere def_pos = s.find("def ") # Look for "def " in the line if def_pos == 0: # If it occurs at the left margin op_index = s.find("(") # Find the index of the open parenthesis fnname = s[4:op_index] # Slice out the function name print(fnname) # ... and work with it.
One can extend these ideas:
- What if the function def was indented, and didn’t start at column 0?
The code would need a bit of adjustment, and we’d probably want to be sure that
all the characters in front of the
def_pos
position were spaces. We would not want to do the wrong thing on data like this:# I def initely like Python!
- We’ve assumed on line 3 that we will find an open parenthesis. It may need to be checked that we did!
- We have also assumed that there was exactly one space between the keyword
def
and the start of the function name. It will not work nicely fordef f(x)
As we’ve already mentioned, there are many more “sugar-coated” methods that let us
work more easily with strings. There is an rfind
method, like find
, that searches from the
end of the string backwards. It is useful if we want to find the last occurrence of something.
The lower
and upper
methods can do case conversion. And the split
method is great for
breaking a string into a list of words, or into a list of lines. We’ve also made extensive use
in this book of the format
method. In fact, if we want to
practice reading the Python documentation and learning some new methods on our own, the
string methods are an excellent resource.
Exercises:
- Suppose any line of text can contain at most one url that starts with “http://”
and ends at the next space in the line. Write a fragment of code to
extract and print the full url if it is present. (Hint: read the documentation
for
find
. It takes some extra arguments, so you can set a starting point from which it will search.) - Suppose a string contains at most one substring “< ... >”. Write a fragment of code to extract and print the portion of the string between the angle brackets.
Looping and lists¶
Computers are useful because they can repeat computation, accurately and fast. So loops are going to be a central feature of almost all programs you encounter.
Tip: Don’t create unnecessary lists
Lists are useful if you need to keep data for later computation. But if you don’t need lists, it is probably better not to generate them.
Here are two functions that both generate ten million random numbers, and return the sum of the numbers. They both work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import random joe = random.Random() def sum1(): """ Build a list of random numbers, then sum them """ xs = [] for i in range(10000000): num = joe.randrange(1000) # Generate one random number xs.append(num) # Save it in our list tot = sum(xs) return tot def sum2(): """ Sum the random numbers as we generate them """ tot = 0 for i in range(10000000): num = joe.randrange(1000) tot += num return tot print(sum1()) print(sum2())
What reasons are there for preferring the second version here?
(Hint: open a tool like the Performance Monitor on your computer, and watch the memory
usage. How big can you make the list before you get a fatal memory error in sum1
?)
In a similar way, when working with files, we often have an option to read the whole file contents into a single string, or we can read one line at a time and process each line as we read it. Line-at-a-time is the more traditional and perhaps safer way to do things — you’ll be able to work comfortably no matter how large the file is. (And, of course, this mode of processing the files was essential in the old days when computer memories were much smaller.) But you may find whole-file-at-once is sometimes more convenient!