Homework review
"Instruments of Torture"
http://www.flickr.com/photos/alstonfamily/2237347597
Exercise 1: return largest element in list
One try:
def maximum(numbers):
"""
Return the largest element in a list.
"""
high = 0
for x in numbers:
if x > high:
high = x
return high
But this doesn't work in every case! How is it broken? How can it be fixed?
There are numbers smaller than zero?
>>> from utils import maximum >>> maximum([-2, -4, -1]) 0
A better version
def maximum(numbers):
"""
Return the largest element in a list.
"""
high = numbers[0]
for x in numbers:
if x > high:
high = x
return high
Now we can handle an all-negative list, too. But we start with the first element of the list, and then the first time through the loop we compare that to itself. Is that really necessary?
Too clever?
def maximum(numbers):
"""
Return the largest element in a list.
"""
high = numbers[0]
for x in numbers[1:]:
if x > high:
high = x
return high
Clever. But: we are now copying the entire list minus one element, which could be very expensive if we are passed a large list. Probably better to do one extra comparison than to copy the entire list. But I digress...
And now for something completely different
def maximum(numbers):
"""
Return the largest element in a list.
"""
numbers.sort()
return numbers[-1]
Lists have a built-in .sort() method! But...
Danger!
Function arguments in Python are passed by reference, not by value. If an argument is mutable (strings and numbers are not, lists and dictionaries are), if you change it inside the function the change will appear outside the function, too:
>>> from utils import maximum >>> my_list = [2, 1, 7, 4, 3] >>> maximum(my_list) 7 >>> my_list [1, 2, 3, 4, 7]
This might surprise someone calling your function!
A solution: sorted()
def maximum(numbers):
"""
Return the largest element in a list.
"""
return sorted(numbers)[-1]
sorted() returns a sorted version of the list, it
doesn't modify the list you pass to it.
I told them we've already got one
All that work... and it's actually already built-in to Python:
>>> my_list = [2, 1, 7, 4, 3] >>> max(my_list) 7
Exercise 2: return name of oldest in names->ages dict
def oldest(people):
"""
Expects a dictionary of people's names -> ages.
Returns the name of the oldest person.
"""
high = None
oldest = None
for name in people:
age = people[name]
if high is None or age > high:
high = age
oldest = name
return oldest
This is subtly broken, too; or at least under-specified, because the assignment itself was not clearly specified. How?
Multiple people can have the same age?
>>> from utils import oldest
>>> people = {'Jeffrey': 23, 'Aloysius': 37, 'Selena': 37}
>>> oldest(people)
Aloysius
Building up a list
def oldest(people):
"""
Expects a dictionary of people's names -> ages.
Returns the name of the oldest person.
"""
high = None
oldests = []
for name in people:
age = people[name]
if high is None or age > high:
high = age
oldests = [name]
elif age == high:
oldests.append(name)
return oldests
Exercise 3: square all the numbers in a list
def squared(numbers):
"""
Expects a list of numbers.
Returns a corresponding list with each element squared.
"""
return [x**2 for x in numbers]
Pretty simple.
Common problems
http://www.flickr.com/photos/ctorok/358096814/
print vs return
>>> def times2(x): print x*2 >>> a = times2(7) 14 >>> print a None >>> def times2(x): return x*2 >>> a = times2(7) >>> print a 14
In web apps, we never use print (except maybe debugging). Nobody's looking at the server screen. A function that generates some value should return that value so the rest of your program can use it.
Iterating vs indexing
for elem in my_list:
... do something with elem ...
for i in range(len(mylist)):
elem = mylist[i]
... do something with elem ...
In Python, you very rarely want the latter. Why? The former is cleaner, easier to read, and works with a wider variety of "iterable" objects. In those rare cases when you really need the counter variable, do this:
for i, elem in enumerate(mylist):
... do something with i and elem ...
Overriding builtin names
Python lets you do this. But it's a bad idea, because it makes things confusing. "Is 'list' here the built-in type 'list', or some other variable 'list'"?
>>> print list
<type 'list'>
>>> def some_func(list):
... print list
>>> some_func('blah')
'blah'
Some names of builtin types: list, dict, str,
bool. Also None. Also all the functions you see
when you type dir(__builtins__).
Code reuse: the holy grail
Don't hesitate to reuse your own code! That's what functions are for; to allow you to set apart a logical chunk of code and reuse it. Don't reimplement something if you've already written a function to do it.
Or if someone else has.
(note to self: stop assigning homework exercises to reimplement builtin functions, then.)
Coding style
Function and variable names should be lowercase, with underscores
as needed for readability. Module (file) names lowercase, with
underscores used sparingly if at all. my_list, utils.py
&c.
Avoid variables named "l", "O", or "I", because you're just asking for trouble.
For all the gory details, read PEP 8.
Why bother? Readability and maintainability count (code is read far more often than it is written). These are the conventions other Python programmers will expect.