So now we've been programming nearly two weeks, we've no doubt come across a number of tedious tasks that we must 'program repeatedly', as opposed to 'program to repeat'; e.g. looping until a blank line is entered. Often these common tasks are similar, but not exactly the same, e.g. different things need to be done in the loop until a blank line is reached. Ideally we want a labour saving device, and thus we turn to the function. We have already been introduced to the use of functions by calling various built in functions, but ultimately we want to be able to make our own functions.
We define a new function using the def statement, which takes the form
def <funcname>([parameter name][, parameter name][, ...]): statement [statement] [...]
When this 'def' statement is executed, a new object with the name 'funcname' is created. Whenever this object is used followed by pair of round braces, the statements listed in the 'def' statement, commonly called the function definition, are executed, and once completed, execution continues from where it was before the function statements were entered. This process is known as calling a function.
It is important to understand that functions provide us with a radically different way to influence the sequence or flow in which our programs execute. Conditions and loops still progress in a roughly top to bottom fashion, but functions allow us to 'dive into' a small block of statements, and come back up to where we were just prior to our dive, more of an in and out form of control.
Of great use is that functions can return a value. In fact in python, all functions return a value, even if not given a value to return (in which case the return value is None). This is done by the use of the return statement. The return statement, when used within a function definition, simply causes that function to return the value of the expression immediately following the 'return'. Formally, return statements take the form
return <expression>
As an example, here is the definition of a simple function to print a triangle of height 3.
def triangle3(): print "*" print "**" print "***"
Henceforth, whenever python encounters triangle3()
the
three print statements will be executed. Note the brackets! They
indicate triangle3 should be called, rather than treated as a variable.
Looking at triangle three, there's nothing we couldn't do without cut
and paste, so why bother? Well recall that way back in the Basic Input
section we said that functions are like mini-programs, in that they
take input and produce output. Well that would mean a function would
produce different output depending on its input, but how do we give a
function input?
Functions receive their input from parameters, which are values given in a comma separated list (i.e. a tuple) when calling the function, and are unpacked within the function automatically. We have to define how many parameters a function takes, and what each ones names would be, so we can give a function appropriate parameters later on. For example let's define a function that accepts one parameter, called 'seconds' and returns how many complete minutes are in that many seconds, and then call it...
Python 2.4.3 (#1, Oct 2 2006, 21:50:13) [GCC 3.4.6 (Gentoo 3.4.6-r1, ssp-3.4.5-1.0, pie-8.7.9)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> def minutes(seconds): ... print seconds ... return seconds/60 ... >>> minutes <function minutes at 0xb7cfcdbc> >>> minutes(71) 71 1 >>> minutes(71.0) 71.0 1.1833333333333333 >>> minutes("bob") bob Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 2, in minutes TypeError: unsupported operand type(s) for /: 'str' and 'int' >>>
In the above example we can already see a number of things about functions and how they handle parameters. Firstly, the 'def statement' is executed and 'minutes' is created as a function 'object', as evidenced by the value returned after typing in 'minutes'. Note however when we call it by typing 'minutes(71)', instead python actually executes the statements in the definition of the function 'minutes'. The first statement in the definition prints out the value of seconds as 71. But where did the value 71 come from?
Well, thinking back to tuples, let's treat everything between the
open and close brackets in a function call as if it were forming a
tuple, so in the case of the function call minutes(71)
we
have formed a tuple '(71, )'. Before the first statement of the
function definition is executed, the tuple created by calling the
function is unpacked using the names in the function definition's
parameter list in order. In our case this creates an
implicit line before print seconds
which looks like
seconds, = 71,
Note in the example transcript how the type of the parameter
'seconds' changes according to the type of the value used in the
function. In fact in the third function call made, the type used
actually causes an error, which makes sense since we cannot divide a
string by an integer (60). In the error case, we see that the first
statement in the function definition (print seconds
) is
executed without error, and that the error occurs later, meaning we are
able to differentiate where within a particular function execution
flows, and the functions are not treated as atomic.
The concepts of parameter passing are best illustrated in the case of multiple parameters. For example, if we define a function which takes two parameters, prints out both (solely for the purpose of example), and returns the smaller of the two parameters ...
>>> def minimum(a, b): ... print a ... print b ... if a < b: ... return a ... else: ... return b ... >>> minimum(9, 3) 9 3 3 >>>
Okay, we're taking a few extra baby steps here... let's look at what
we've done. We've defined a new function called 'minimum', it has two
parameters, 'a' and 'b'. By virtue of putting a, b
in the
parameter list in the function definition, we are implicitly putting
the line a, b = <passed in tuple>
in as the first
line, where the passed in tuple is formed by the function call itself,
rather than the function definition. Next we notice that we can branch
execution flow using an 'if statement' within a function definition. We
can also loop, in fact we can do pretty much anything we want within a
function definition. Finally let's focus on the 'return statement'.
Each return statement exits the function immediately, and replaces the
value of the function call with the value of the expression following
the reserved word 'return'. So following minimum(9,3)
we
see that program execution 'dives into' the function 'minimum', sets
'a' and 'b' to 9 and 3 respectively via the implicit tuple unpacking,
prints 'a', prints 'b', and never executes return a
but
instead executes return b
, at which point execution leaves
the function definition, and returns (hence the name return) to the
point of the function call, and making the value of the function call
3.
Of course, since we can do 'pretty much anything' in a function definition, we can also call other functions, both python built in functions, and our own defined ones, as in an example to get two coordinates from the user.
def getcoords(): x = raw_input("Enter an X coordinate: ") y = raw_input("Enter a Y coordinate: ") print "The smaller coordinate is %i" % ( minimum(int(x), int(y)) ) return x, y
So here we have called both a built in function (raw_input) and our own previously defined function (minimum) within the function definition. Also note that we form a tuple in our return statement. We wish to return two values, but the 'return statement' can only return the value of a singe expression, so to return multiple values, that expression must be a tuple (or list) of values.
Just as expressions can be composed to form more complex expression, so can functions be composed in the strict mathematical sense, i.e. given two functions 'f(a)' and 'g(b)', we can obtain a value for f of g of x. Similarly in python we could say,
print minimum(5, minimum(int(raw_input("> ")), 8))
Remember that expressions are evaluated inside to outside, so the value of raw_input is determined first, then it is converted to an integer, then compared against 8 in the inner minimum function, and finally the value of the inner minimum function is compared against 5 in the outer minimum function. Thus we have composed minimum with itself, and further with raw_input.
In the same way that the range of 'g(b)' must fall within the domain of 'f(a)', so too must we ensure in python when doing our function compositions that the results of inner functions are of appropriate type and value for use as parameters to outer functions.
Considering programming is all about being lazy and getting computers to do the work for us, we find that entering every single parameter every time we call a function gets tedious. Especially if 90% of the time we are entering the same values. We want to be able to specify a default value to assume when we feel lazy and want to leave those parameters out. We do this by 'assigning' a default value when specifying the parameter list in the function definition.
#a simple function to change the radix (base) of a number, and return a string #representing the number in the chosen radix #this is most commonly used for hexadecimal notation, hence the default of 16 def radix(number, r = 16, case = "upper"): upper = "0123456789ABCDEF" lower = "0123456789abcdef" if case == "upper": digits = upper else: digits = lower ret = "" while number > 0: ret = digits[number % r] + ret number /= r return ret print radix(17) print radix(255) print radix(17,8) print radix(17,2)
The example above produces the following output
11 FF 21 10001
Deconstruction time! Note first how we define the parameter list of
the 'radix' function. We assign defaults. When we call 'radix' the
first time, we only provide one parameter, so during the implicit tuple
unpacking, we get the equivalent of number, r, case = 17,
which would be problematic because the right hand tuple is not big
enough. In this case python fills up the tuple as necessary with the
default values, yielding in fact number, r, case = 17, 16,
"upper"
.
Looking at our third call of the 'radix' function, we have provided
two parameters, so python unpacks the tuple as number, r, case =
17, 8,
, and fills up to the size required with default values,
so we get
number, r, case = 17, 8, "upper"
. Note how
the default values fill up their respective positions in the
tuple, and not from left to right, i.e. the first missing
parameter ('case') is filled up not with the first default parameter
(16), but rather the default for the parameter at the missing position
('upper'). Because passed in values are not associated by name to the
parameter names in the function definition, but rather by position, it
is imperative that we specify default values (thus allowing optional
parameters) after all mandatory parameters. Obviously,
if all the parameters are provided, no defaults are used.
There's a gotcha to defining default values though. Remember when we were learning about lists, they were caled mutable objects? Well, you can't use a mutable object as a parameter default. This is because the value of defaults is evaluated when the function definition line is executed (i.e. only once), not each time the function is called. This is counter-intuitive, but an example will make it very clear.
>>> def store(item, container = ["wallet", "keys", "cellphone"]): ... #put unique items in containers, by default we have some stuff ... #in pockets ... ... if item in container: ... print "Already got one of those" ... else: ... container.append(item) ... return container ... >>> gym_bag = [] #start with an empty gym bag >>> store("towel", gym_bag) #and tell store which container to use ['towel'] >>> store("deodorant", gym_bag) ['towel', 'deodorant'] >>> store("shoes", gym_bag) ['towel', 'deodorant', 'shoes'] >>> gym_bag ['towel', 'deodorant', 'shoes'] >>> store("fork", gym_bag) #so far so good Already got one of those >>> store("ID") #use a NEW default container (pockets) ['wallet', 'keys', 'cellphone', 'ID'] >>> store("string") #use a NEW default container (pockets) ['wallet', 'keys', 'cellphone', 'ID', 'string']
Wait! What? Why is 'ID'
in the last line of output? That's
because on the very first line a new list is created as the default value
for container
. When we call store
for the first
time without giving it a second parameter, we append 'ID'
to
the list, thus changing the contents of the default list. If you need to
provide an empty list, dictionary or other mutable object as a default,
then rather use None
as the default, and set the default in
the function, like this
>>> def store(item, container = None): ... #put unique items in containers, by default we have some stuff ... #in pockets ... ... if container is None: ... container = ["wallet", "keys", "cellphone"] ... if item in container: ... print "Already got one of those" ... else: ... container.append(item) ... return container ... >>> gym_bag = [] #start with an empty gym bag >>> store("towel", gym_bag) #and tell store which container to use ['towel'] >>> store("deodorant", gym_bag) #still lets us override the default ['towel', 'deodorant'] >>> store("shoes", gym_bag) ['towel', 'deodorant', 'shoes'] >>> gym_bag ['towel', 'deodorant', 'shoes'] >>> store("fork", gym_bag) #so far so good Already got one of those >>> store("ID") #use a NEW default container (pockets) ['wallet', 'keys', 'cellphone', 'ID'] >>> store("string") #use a NEW default container (pockets) ['wallet', 'keys', 'cellphone', 'string'] #The default is newly created each time
There is one more way in which parameters can be passed. That being by keyword. Python uses a very specific format for specifying the passing of parameters by keyword,
def <funcname>([parameter 1][, parameter 2][...][[,] **kwargs]): statement [statement] [...]
Ending a parameter list in a function definition with the special
'expression' **kwargs
(meaning key word arguments), means
that python will provide a dictionary inside function definition called
'kwargs', containing all the parameters passed in in keyword form. The
'**kwargs' can also be alone in the parameter list. Re-examining the
radix example we have
#a simple function to change the radix (base) of a number, and return a string #representing the number in the chosen radix #this is most commonly used for hexadecimal notation, hence the default of 16 def radix(number, **kwargs): upper = "0123456789ABCDEF" lower = "0123456789abcdef" if "radix" in kwargs: r = kwargs["radix"] else: r = 16 digits = lower if "case" in kwargs: if kwargs["case"] == "upper": digits = upper ret = "" while number > 0: ret = digits[number % r] + ret number /= r return ret print radix(17) print radix(255, case = "upper") print radix(17, radix = 8) print radix(17, radix = 2) print radix(17, case = "upper", radix = 12)
Which gives us the following output
11 ff 21 10001
Using **kwargs
allows us to call the function and name
the parameters we will assign values to explicitly, as opposed to
implicitly by position. But for the same reasons default parameters
must be specified after mandatory parameters, '**kwargs' must appear
after default parameters (if any), otherwise after mandatory
parameters.
As we have seen, we can refer to the function itself, as a value, without calling it by using the name of the function without round brackets and a parameter list. If we can refer to the function as a value, then we can assign that value to variable. Then we can use the variable to call the function. First a toy example for explanatory purposes.
#!/usr/bin/python def plus(a, b): return a + b def prod(a, b): return a * b f = plus f(4,5) f = prod f(4,5)
As we can see, we are able to assign a function to a variable;
f = plus
. Next we see that we can call the function
currently assigned to 'f' by using the same call
syntax we would use with a function proper, namely using round braces
and a parameter list; f(4,5)
. Note that assigning a
different function to 'f', and 'calling' 'f' again, we are in fact
calling the newly assigned function.
At first we might think this merely serves to confuse things, but there is a use for the concept of function pointers as they are called. Suppose we have a list, and we wish to apply a function to every to every element in that list, such that we obtain a new list containing the result of the function applied to the respective element in the original list. We could create an empty list, loop over the original list, and call the chosen function with each element of the original list as a parameter, appending the results to the new list. Now suppose we find that we perform this activity on a regular basis, so regular a basis that we would want to make a function to do this. The only problem is that the function we would want applied to the list changes. Which means we can't hard code it into our 'application' function definition, but we must somehow accept the function to apply as a parameter to our 'application' function. Using function pointers we can do exactly this...
#!/usr/bin/python #a short program to demonstrate the use of function pointer #we define a functions that 'applies' a function to each element in a list, and #returns a list of the results of that function. Both the list and the function #are specified as parameters def apply(f, l): ret = [] for e in l: ret.append(f(e)) return ret #imagine we had received four 'numbers' from raw_input calls, which means they #are in fact strings inputs = ["1", "2", "3", "4"] #but we need them as integers int_list = apply(int, inputs) print int_list #or perhaps even as floats float_list = apply(float, inputs) #or even converted to names of numbers def nameofnum(n): return ["one", "two", "three", "four", "five"][int(n)-1] name_list = apply(nameofnum, inputs)
Try running this program and see what the output is.
Given the code:
def suffix(m, s, chop = None): if chop == None: chop = -len(m) return m[:-(chop)]+s def prefix(m, p): return p+m s = "hand" print suffix(s, "y") print suffix("conceivable", "y", 1) print prefix("conceivable", "in")