Functions

We have already seen two kinds of functions:

  • Functions that have an effect. For example, print has the effect of printing stuff to the screen.
  • Functions that are used to compute a value. For example, the value of math.sqrt(9) is 3.0

Let's write our first function, piratically-themed in honour of the International Talk Like a Pirate Day.

In [1]:
def pirate_print(s):
    '''Print the piratified version of the string s: "Ahoy! [s] Yarr!"
    
    Arguments:
    s -- a string
    '''
    print("Ahoy! " + s + " Yarrrrr!")

Here is what a function skeleton looks like:

    def <function-name>(<parameters, separated by commans>):
        '''Docstring -- a description of the function. The first sentence says what the function 
        should do, in the imperative. 

        Arguments:
        <a list of the arguments/parameters, briefly describing what they are>
        '''
        <body of the function>

The docstring -- a string enclosed in triple quotes right after the first line of the function definition -- is conventionally included to make things more readable.

Let's now use the function. The proper terminology for using a function is calling a function.

In [2]:
pirate_print("I love CIV!")
Ahoy! I love CIV! Yarrrrr!
In [3]:
pirate_print("Praxis too!!!")
Ahoy! Praxis too!!! Yarrrrr!

What is happening there is that the argument ("I love CIV!", or "Praxis too!") gets assigned to the parameter s. Then, the body of the function (in this case, print("Ahoy! " + s + " Yarrrrr!")) gets executed.

This is the nice thing about functions -- we can write the once, but use them multiple times, as you see above.

Note that pirate_print() is not like the functions we know from math -- we use it because it has an effect. The effic is printing stuff to the screen. But we don't use it to compute some value. Here is a function that can be used to compute a value.

In [4]:
def pirate_transform(s):
    '''Return a "piratified" version of the string s, with "Ahoy!" in front,
    and " Yarrrrr!" at the end
    
    Arguments:
    s -- a string
    '''
    piratified_s = "Ahoy! " + s + " Yarrrrr!"
    return piratified_s

We have a new construct here -- the return statement. The value of pirate_transform(s) is whatever comes after the return. For example,

In [5]:
pirate_transform("I love PHY!")
Out[5]:
'Ahoy! I love PHY! Yarrrrr!'

The value of pirate_transform("I love PHY!") is 'Ahoy! I love PHY! Yarrrrr!'. Note that this is different from what happenned before -- we can tell by the fact that there are quotes around the string that we are just looking at the value of an expression. It's as if we just said.

In [6]:
'Ahoy! I love PHY! Yarrrrr!'
Out[6]:
'Ahoy! I love PHY! Yarrrrr!'

The line pirate_transform("I love PHY!") would not print anything to the screen if the line were part of the program. This line would, however:

In [7]:
print(pirate_transform("I love PHY!"))
Ahoy! I love PHY! Yarrrrr!

We can go one step further:

In [8]:
print(pirate_transform(pirate_transform("I love PHY!")))
Ahoy! Ahoy! I love PHY! Yarrrrr! Yarrrrr!

What happenned here? First, we evaluated pirate_transform("I love PHY!"), and it's value turned out to be "Ahoy! I love PHY! Yarrrrr!". That means the line print(pirate_transform(pirate_transform("I love PHY!"))) has the same effect as

In [9]:
print(pirate_transform("Ahoy! I love PHY! Yarrrrr!"))
Ahoy! Ahoy! I love PHY! Yarrrrr! Yarrrrr!

Note that pirate_transform(s) is very similar to the functions that you know from school algebra, like $f(x) = x^2$. The value of $f(3)$ is 9 in the same way that the value of pirate_transform("hi!") is "Ahoy! hi! Yarrrrr!".

Local variables

Let's write one more function:

In [10]:
def has_roots(a, b, c):
    '''Return True iff ax^2+bx+c=0 has real roots
    
    [Note: Return True iff ... means, in Python (but not in math!) Return 
    True if ax^2+bx+c=0 has real roots and False otherwise]
    
    Arguments:
    a, b, c -- floats
    '''
    
    disc = b**2-4*a*c
    
    #This is not the nicest way to write this -- we'll rewrite this function later
    if disc >= 0:
        return True
    else:
        return False
    

As you can see, a function can have more than one parameter. In this case, it has three -- a, b, and c. Let's use the function:

In [11]:
has_roots(1, 2, 3)
Out[11]:
False
In [12]:
has_roots(1, 2, -3)
Out[12]:
True

Let's now try to see what the value of the discriminant is:

In [13]:
disc
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-f3867841c4ba> in <module>()
----> 1 disc

NameError: name 'disc' is not defined

We cannot see the value of disc. That's because disc is a local variable inside the function has_roots. You can think of local variables as "scratch work". After the function finishes running, all the local variables inside it get discarded.

The parameters are also local variables:

In [14]:
a
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-14-60b725f10c9c> in <module>()
----> 1 a

NameError: name 'a' is not defined

Let's now see another demonstration of this:

In [15]:
def plunder_grade():
    grade = 0
    
if __name__ == '__main__':
    grade = 95
    plunder_grade()
    print(grade)
    
95

The line grade = 0 defines a local variable grade. It gets discarded after plunder_grade() is executed. The global value of grade stays at 95.

Note: if name == 'main'

I'll sometimes call the block of code under if __name__ == '__main__': is called the "main block." For now, think of the line if __name__ == '__main__': as just separating the function definitions from the code that uses the functions. We'll have more of an explanation soon.

Note: functions with no parameters

plunder_grade() has no parameters. It's defined as you see above, and it's called using (). Functions without parameters have their uses -- see print_grade() below.

Global variables

Global variables are variables defined outside of any function. For example, the variable grade in the main block above is global. Here is an example:

In [16]:
def print_grade():
    print(grade)
    
if __name__ == '__main__':
    grade = 95
    print_grade()
95

Since there is no local variable grade, the line print(grade) prints the global variable grade.

Changing global variables

Here is how we can actually modify a global variable:

In [17]:
def actually_plunder_grade():
    global grade
    grade = 56
    
if __name__ == '__main__'    :
    grade = 95
    actually_plunder_grade()
    print(grade)
56

The line global grade in the function actually_plunder_grade() means that when we say grade = 56, we mean we want to assign 56 to grade.