So far we've been using functions from the builtin module, which is automatically loaded in most cases.
Occasionally we've accessed functions in other modules using import.
import random
a = random.random()
We'll now look at building our own functions.
Basic function
Functions are a block begun with a function declaration or header:
def function_name ():
# Suite of statements
# Code calling function_name
In a standard script, functions need to be defined before you use them.
Example
def printit():
print("hello world")
printit() # The function can be called
printit() # as many times as you like.
printit()
Passing info in
Functions can be parameterised; that is, made flexible through variables which are set up when the function is called.
Note that the arguments sent in are allocated to the parameter variables.
def printit(text):
print(text)
printit("hello world")
a = "hi you"
printit(a)
Variable labels
What is happening here is we're attaching two labels to the same variable:
def printit(text):
print(text)
a = "hi you"
printit(a)
The effect this has depends whether the variable is mutable or immutable.
Variable labels
For mutable variables, changes inside the function change the variable outside:
def printit(text):
text[0] = text[0] + ", Pikachu"
print(text[0])
a = ["hi you"]
printit(a) # Call the function.
print(a) # Check the value of a.
As lists are mutable, the value in the list outside the method has changed to "hi you, Pikachu".
Variable labels
For immutable variables, changes inside the function just create a new variable inside the function. Even though it has the same name, it isn't the same variable:
def printit(text):
text = text + ", Pikachu" # New text variable created.
print(text)
a = "hi you"
printit(a) # Call the function.
print(a) # Check the value of a.
As text is immutable, the value in the list outside the method is still "hi you".
Passing info in
Python (unlike many languages) doesn't worry about the type passed in
def printit(var):
print(str(var))
By default, functions invisibly return None (you can do so explicitly, as is sometimes useful).
But you can pass values back:
def get_pi():
return 3.14
pi = get_pi()
print(str(pi))
print(str(get_pi()))
If something comes back, you either need to attach a label to it, or use it.
You can find out the type of a returned object with type(variable_name).
If you need to return more than one thing, return a tuple.
Multiple in (single out)
Arguments are allocated to parameters like this just on position:
def add(num1, num2):
return num1 + num2
answer = add(20,30)
These are called positional arguments. num1 gets 20, as it is first; num2 gets 30 as it is second.
Defaults
You can set up default values if a parameter is missing:
def add(num1 = 0, num2 = 0):
return num1 + num2
answer = add(3)
With this type of parameter, positional arguments are allocated left to right, so here, num1 is 3, and num2 is nothing.
Ordering
In the absence of a default, an argument must be passed in.
If we did this:
def add(num1=0, num2):
return num1 + num2
answer = add(3)
print(answer)
It wouldn't be clear if we wanted num1 or num2 to be 3. Because of this, parameters with defaults must come after any undefaulted parameters. All parameters to the right of a defaulted parameter must have defaults (except * and ** - which we'll see shortly).
Keyword arguments
You can also name arguments, these are called keyword arguments or kwargs. Note that this use of "keywords" has nothing to do with the generic use for words you can't use as identifiers.
def add(num1, num2):
return num1 + num2
answer = add(num2 = 30, num1 = 50)
Here we've swapped the order of the positional arguments by naming the parameters to assign their values to. num2 gets 30; num1 = 50, ignoring the position.
Note that once Python has started to deal with kwargs, it won't go back and start allocating positional arguments, so all positional arguments have to be left of the kwargs.
You can force parameters to the right to be kwargs by putting a * in the parameters
Note also that you can't allocate to the same parameter twice:
answer = add(3, num1 = 5)
Flexible parameterisation
You can allow for more positional arguments than you have parameters using *tuple_name, thus:
def sum (num1, num2, *others):
sum = num1
sum += num2
for num in others:
sum += num
return sum
answer = sum(1,2,3,4,5,6,7)
* is known as the iterable un/packing operator. If nothing is allocated, the tuple is empty.
Iterable unpacking
You can equally use the * operator with lists or tuples to generate parameters:
def sum (num1, num2, num3, num4):
return num1 + num2 + num3 + num4
a = [1,2,3,4]
answer = sum(*a)
Note that these can also be in the middle.
a = [10,20]
answer = sum(1,*a, 2)
Iterable unpacking
You can, therefore, use these in both positions:
def sum(*nums):
sum = 0
for num in nums:
sum += num
return sum
a = [1,2,3]
answer = sum(*a)
Flexible parameterisation
The same can be done with **dict_name (** is the dictionary unpacking operator), which will make a dictionary from unallocated kwargs:
def print_details (a, **details):
first = details["first"]
surname = details["surname"]
print (first + " " + surname + " has " + a + " pounds")
Note that you can't use one of these to override other variables. If nothing is allocated, the dictionary is empty.
Unpacking dictionaries
You can also use ** to create keyword arguments:
def print_details(a, first, surname):
print (first + " " + surname + " has " + a + " pounds")
d = {"first":"George","surname":"Formby"}
print_details("5",**d)
Unpacking quirks
Note also that just as with standard arguments *tuple_name must come before **dict_name if you use both.
*tuple_name must come after positional parameters and **dict_name after kwargs.
It is, therefore usual to place them after their associated variables or together at the end.
def func(a,b,*c,d,**e) # d has to be a kwarg.
def func(a,b,d,*c,**e) # abd,bd,or d can be kwargs.
But this is also how you force variables to be kwargs.
Note that attempts to do this:
def a(**d):
print(d)
a(d={1:2})
End up with a dictionary variable inside the dictionary, with the same name: {d:{1,2}}
Summary of order
Parameters:
Undefaulted parameters
Defaulted parameters
*tuple_name
Forced keyword parameters
**dict_name
Arguments:
Positional arguments
Keyword arguments
Unpacked lists, tuples, or dicts may be anywhere
Nested functions
Note that there is nothing to stop you having functions within functions:
def a():
print("1")
def b():
print("2")
b()
a()