Virtually every programming language has functions and procedures, a way of separating out a block of code that can be called many times from different places in your program, and a way to pass parameters into them. Python is no different, so we'll quickly run over the standard stuff that most languages have, then take a look at some of the cool stuff Python has to offer.
Positional Function Parameters in Python
Here's a really simply function:
[python]
def foo(val1, val2, val3):
return val1 + val2 + val3
[/python]
When used, we get the following:
[python]
>>> print(foo(1, 2, 3))
6
[/python]
This function has 3 positional parameters (each one gets the next value passed to the function when it is called – val1
gets the first value (1), val2
gets the second value (2), etc).
Named Python Functional Parameters (with defaults)
Python also supports named parameters, so that when a function is called, parameters can be explicitly assigned a value by name. These are often used to implement default, or optional, values. For example:
[python]
def foo(val1, val2, val3, calcSum=True):
# Calculate the sum
if calcSum:
return val1 + val2 + val3
# Calculate the average instead
else:
return (val1 + val2 + val3) / 3
[/python]
Using the function gives us the following:
[python]
>>> print(foo(1, 2, 3))
6
>>> print(foo(1, 2, 3, calcSum=False))
2
[/python]
In this example, calcSum
is optional - if it's not specified when you call the function, it gets a default value of True
.
Potential Python Function Parameter Problems
One thing to keep in mind is that default values are evaluated when the function is compiled, which is an important distinction if the value is mutable. The behaviour below is probably not what was intended:
[python]
def foo(val, arr=[]):
arr.append(val)
return arr
[/python]
When using the function:
[python]
>>> print(foo(1))
[1]
>>> print(foo(2))
[1, 2]
[/python]
This happens because the default value (an empty list) was evaluated once, when the function was compiled, then re-used on every call to the function. To get an empty list on every call, the code needs to be written like this:
[python]
def foo(val, arr=None):
if arr is None:
arr = []
arr.append(val)
return arr
[/python]
When using the function, we get:
[python]
>>> print(foo(1))
[1]
>>> print(foo(2))
[2]
[/python]
Python Function Parameter Order
Unlike positional parameters, it doesn't matter what order named parameters are specified in:
[python]
def foo(val1=0, val2=0):
return val1 – val2
[/python]
When used, we get the following:
[python]
>>> print(foo(val1=10, val2=3))
7
>>> # Note: parameters are in a different order
>>> print(foo(val2=3, val1=10))
7
[/python]
Unusually, Python also allows positional parameters to be specified by name:
[python]
def foo(val1, val2):
''' Note: parameters are positional, not named. '''
return val1 – val2
[/python]
When used, we get the following:
[python]
>>> # But we can still set them by name
>>> print(foo(val1=10, val2=3))
7
>>> # And in any order!
>>> print(foo(val2=3, val1=10))
7
[/python]
Here's a more complex example:
[python]
def foo(p1, p2, p3, n1=None, n2=None):
print('[%d %d %d]' % (p1, p2, p3))
if n1:
print('n1=%d' % n1)
if n2:
print('n2=%d' % n2)
[/python]
When used, we get the following:
[python]
>>> foo(1, 2, 3, n2=99)
[1 2 3]
n2=99
>>> foo(1, 2, n1=42, p3=3)
[1 2 3]
n1=42
[/python]
This seems really confusing, but the key to understanding how it works is to recognize that the function's parameter list is a dictionary (a set of key/value pairs). Python matches up the positional parameters first, then assigns any named parameters specified in the function call.
Variable Python Function Parameter Lists
The Python coolness really kicks in when you start to look at variable parameter lists. You can write your functions without even knowing what parameters will be passed in!
In the function below, the asterisk in front of the vals parameter means any other positional parameters.
[python]
def lessThan(cutoffVal, *vals) :
''' Return a list of values less than the cutoff. '''
arr = []
for val in vals :
if val < cutoffVal:
arr.append(val)
return arr
[/python]
When using the function, we get:
[python]
>>> print(lessThan(10, 2, 17, -3, 42))
[2, -3]
[/python]
The first positional value we specify in the function call (10)
is given to the first parameter in the function (cutoffVal
), then all the remaining positional values are put in a tuple and assigned to vals. We then iterate through these values, looking for any that are less than the cutoff value.
We can also do the same kind of thing with named parameters. A double asterisk in front of the dict parameter below means any other named parameters. This time, Python will give them to us as key/value pairs in a dictionary.
[python]
def printVals(prefix='', **dict):
# Print out the extra named parameters and their values
for key, val in dict.items():
print('%s [%s] => [%s]' % (prefix, str(key), str(val)))
[/python]
When using the function, we get:
[python]
>>> printVals(prefix='..', foo=42, bar='!!!')
[foo] => [42]
[bar] => [!!!]
>>> printVals(prefix='..', one=1, two=2)
[two] => [2]
[one] => [1]
[/python]
Note in the last example that the values are not printed out in the same order that they were specified in the function call. This is because these extra named parameters are passed through in a dictionary, which is an un-ordered data structure.
A Real World Example
So, what would you use all this for? As an example, it's common for programs to generate messages from a template that has placeholders for values that will be inserted at run-time. For example:
Hello {name}. Your account balance is {1}, you have {2} available credit.
The function below takes such a template and a set of parameters that will be used to replace the placeholders.
[python]
def formatString(stringTemplate, *args, **kwargs):
# Replace any positional parameters
for i in range(0, len(args)):
tmp = '{%s}' % str(1+i)
while True:
pos = stringTemplate.find(tmp)
if pos < 0:
break
stringTemplate = stringTemplate[:pos] + \
str(args[i]) + \
stringTemplate[pos+len(tmp):]
# Replace any named parameters
for key, val in kwargs.items():
tmp = '{%s}' % key
while True:
pos = stringTemplate.find(tmp)
if pos < 0:
break
stringTemplate = stringTemplate[:pos] + \
str(val) + \
stringTemplate[pos+len(tmp):]
return stringTemplate
[/python]
Here it is in action:
[python]
>>> stringTemplate = 'pos1={1} pos2={2} pos3={3} foo={foo} bar={bar}'
>>> print(formatString(stringTemplate, 1, 2))
pos1=1 pos2=2 pos3={3} foo={foo} bar={bar}
>>> print(formatString(stringTemplate, 42, bar=123, foo='hello'))
pos1=42 pos2={2} pos3={3} foo=hello bar=123
[/python]