Imagine you were building a bike from scratch. I mean, really from scratch. You would have to harvest rubber from rubber trees to make the tyres. You'd have to invent new tools for working with the materials- spanners and welding torches. You'd have to design a means of converting the vertical motion of your feet into the horizontal motion of the bike. You would literally be reinventing the wheel.
In programming, when a programmer wants to write a simple calculator program, she does not have to worry about how the calculator will be displayed on screen, or how the numbers will be input because somebody's already invented Microsoft Windows, and somebody already invented the electronic keyboard. She uses these pre-existing tools that perform a very specific, comparitively simple task to build a larger, more complex program.
We've used this idea already- functions like np.linspace, or np.sin are pieces of code which someone else wrote, that we can then reuse to construct larger pieces of code. We can then package these pieces of code up into new functions which can be used again and again. This stops us duplicating our efforts, and breaks computer programs up into smaller, more understandable functions.
Suppose we want to evaluate the quadratic equation $y=x^2+3x-2$ for x=3
print (3**2 + 3*3 - 2)
we saw how to simplifiy this with variables earlier:
x=3
print (x**2 + 3*x - 2)
x=4
print (x**2 + 3*x - 2)
But is there a way we can simplify the quadratic itself? We can define a function called quadratic which does all of the calculating for us. The function looks like this:
def quadratic(x):
return(x**2 + 3*x -2)
Then to use the function we can simply type:
print (quadratic(3))
print (quadratic(4))
So our function takes the number (x), evaluates our quadratic for that value, and gives us back the answer. The idea of a function which takes a variable and gives you back a number should be familiar to you: for the above example you might write
$$f(x) = x^2+3x-2$$for instance. The input variable is the number $x$: we put in our number, 3 and our function gives us back (returns) the result 16.
And we aren't limited to one number in, one number out: we might have a function which looks like this:
$$ f(x,y) = xy^2$$This takes two numbers, $x$ and $y$, and returns a single number. We could implement this in python as follows
def new_function(x,y):
return(x*y**2)
print (new_function(3,4))
Every function that we define in python starts with def (define). This instructs python that what follows is a function
def function_name(input1,input2):
We follow this with the name of the function. E.G. we've already see functions called 'quadratic' and 'new_function' above. This means that when we later type function_name(3,4) python knows to execute the function we've defined.
The brackets then contain any input variables. Here we've called them 'input1' and 'input2', but you can see above examples where we've used just a single variable called 'x' or two variables called 'x' and 'y'. Even if your function doesn't take any input variables, you still need the brackets.
We close the statement with a colon ':'. This tells python that the definition statement is complete and what follows is the code we want to execute.
What happens next is one of the unique things about python: the text underneath the definition line must all be indented by the same amount until we've finished defining our function: something like this
def my_function():
x=np.linspace(1,10,10)
y=x**2
return(y)
#more code down here
You see how all of the lines in our definition are indented by four spaces? (four is the python standard) This lets python know which bits of code are part of our definition, and which are just ordinary bits of code. It also means that if you have a big block of code it's easy to follow the logic, because we can see just from the indenting where functions start and end.
If you are defining functions on repl.it, it will automatically indent for you after you type a function definition i.e. type
def my_function():
and hit enter and repl will automatically position the cursor four spaces in:
def my_function():
|
The function body contains all of the code you want to execute. So for instance, a function which takes two numbers, squares them, sums them together, and then takes the square root of the result would look like
def complicated_function(a,b):
a_squared=a*a
b_squared=b*b
c_squared=a_squared+b_squared
c=np.sqrt(c_squared)
return(c)
Once we have done all the operations we want to do, we can return a result with the return statement. This is indented the same as the function body. In the above example we return the value of the variable 'c'. This means that if we call the function like this
y=complicated_function(3,4)
the result (5) is stored in the variable y.
When you want to use your function, you just type the name of the functon, and the variables you want it to use in brackets
y=complicated_function(3,4)
print (complicated_function(3,4))
x=3
angle=np.pi/2
answer= complicated_function(x,np.sin(angle))
The code belows defines two functions- one calculates $\sin^2$ of a number, the other to find the inverse of a number.
def sin_squared(x):
s=np.sin(x)
return(s*s)
def invert(x):
return(1/x)
Now we can use these functions to define a more complicated function which takes a number and returns the inverse of the sine squared, i.e. $f(x) = 1/(\sin^2 x)$
def compound_function(x):
s=sin_squared(x)
return (invert(s))
print (compound_function(3))
It would have been possible to do all the steps in the one function, but instead we have avoided repeating ourselves by using the simpler functions we had already defined. You should always aim to code like this- instead of making one function which does everything, make smaller functions which do simple things, and then build more complex functions out of these. This makes your code easier to understand, easier to edit, and more resistant to errors.
Once you have read this information then proceed to try the exercises "3.0 Functions" and "2.1 Debug Functions"