Functions are blocks of code that execute when the function is called by name. If a function is never called, the code never gets executed. If a function is called more than once, then the body of the code can be executed more than once although the programmer only had to code it one time. This ability for code re-use is one of the advantages of using functions. They make your code shorter by reducing repetition.

Functions are called functions because they are similar in concept to functions in math. There a function y = f(x) maps a value x to a value y. A function z = f(x,y) maps the combination of x and y values to a new value z. The existing values are like input and the new values are like output. Functions take input values or input data and do stuff with them, basicially.

"Do stuff" often means returning a new value like in math class, but functions in programming are more general, for they do not have to return a value. They may instead print output or perform a computation that modifies other data in your program. A function that modifies other data in your program is said to have "side effects". You've seen functions that do not return values before in your Java programming classes; they are called void methods in Java. Value methods in Java are methods that return a value (duh!).

Another way functions are more general in programming is that they also do not have to take input values. This leads us to a more precise definition of a function:

A function is a block of code that may accept input data, performs a computation in its body, and may return output data

Since functions are blocks of code that get executed when called, that block of code has to be written before the function can be called. The programmer indicates what that block of code is in the function definition. In Python, the def keyword is used to introduce a function definition, and the body of the function is indented with respect to this first line:

Notice that once the function has been defined, it gets called by using its name (which the programmer chose when they wrote the function) followed by () immediately after the name. We'll later see that the only modification to this syntax is that functions that accept arguments or parameters will be passed the values for those parameters in a comma-separated list inside the ().

It's been mentioned several times in I427 to take a "data-driven" approach to your programming. Always be aware of and be able to articulate what data your program is manipulating at each step. Be able to say for each object what that objects name is, what information that object stores, what you need to do with that information, and what type that object is in the program. Functions help with all of these things. You have to articulate what the input and output data is, and if you have, say, a document in a corpus stored in an object and you need to tokenize that document, then writing a function called tokenize_document that takes a document as its input and returns a list of tokens as output is a very strong way to keep track of the flow of data through your code.

Software specifications

Functions’ behavior is typically specified in advance (often in written form in an aptly named “specification”), and most specifications of behavior do not indicate that functions will produce output. So if the specification does not indicate a function prints output, then it doesn't! Specifications also typically indicate

  1. the name of the function
  2. what arguments or parameters the function takes and in what order they occur, if any
  3. what computation the function performs, including any input it reads or output it prints during its execution
  4. what output it will return, if any.

The name must be specified because programming done in professional teams involves multiple programmers (hence the team). Everyone must agree on the names of things so that one team member can write the function while another team member can call it. If the dev writing the function uses a different name, there will be errors when the other dev attempts to call the non-existant function.

In items 2 and 4 above, not only must the kind of arguments be specified, the types must be as well. A function that is written as returning "the client's bank balance" is vague. It needs to indicate what type that return value you should be. Is it an integer (the number of cents in the balance), a floating point number, or a string? If it's a string, how is it formatted? These type details should also be specified for the arguments a function takes as well.

Return values

To make a function that returns a value, we need only add a return statement to the method. Once we’ve done that, we can use the function in two ways. We can invoke it and get its returned value as immediate output, or we can save that value in an object to use its value again later in the program as needed.

Note also that the name f was re-used above. That f called above is not the same as the one defined earlier in this notebook that printed output instead of returning it. If I change the function that’s the name f references by re-defining it, Python has no trouble doing that. This is another example of Python being a dynamically-typed language.

Function arguments or parameters

To define a function that takes arguments, we simply name the arguments in order in between the parentheses after the function name.

In the previous two examples, the input data in the arguments was used to produce output data whose value depended on the arguments. In a nutshell, this is what functions do. The allow programmers to simplify code by replacing long blocks of code with function calls (where the long blocks of code still happen, but you don’t have to read them in the code at that point). Moreover, functions can be called repeatedly so that the block of code in the function body only needs to be written once even though it can be used many times—each time it is called. Even better, if the block of code needs to be changed, it only needs to be changed once inside the body of the function. As long as the input and the result of the computation on that input doesn’t change, the program will still work the same.

A brief word on f-strings

The final thing to note is that the previous two examples introduced f-strings. F-strings are a quick- and-dirty way to produce strings that contain the values of other data in them. Adding an f before the single- or double-quotes makes it an f-string rather than a regular, constant string. Curly brackets can be used within the quotes to include expressions that are evaluated so that there values appear in the resulting strings. Above, the expressions were other strings, and they were stored in variables, but neither of those are required.

how parameter passing works

I see an inexecusable amount of students who have nominally passed I210 and I211 yet do not know how methods in Java or functions in Python work. I see A LOT of code like this example.

Sample Homework Problem: Write a function antipattern_function that adds three to its argument argument1 and returns that value. Below is an example of antipattern_function being used:

returned_value = antipattern_function(3)
print(returned_value) # prints 6 because 3 + 3 == 6

Here is the code many students write for this kind of specification:

def antipattern_function(argument1):
    argument1 = 6
    return argument1

The problem with this approach is that it's wrong! The function above happens to return the correct answer then the value 3 is passed in, but it doesn't work for any other value of argument1. Instead, the function needs to be:

def antipattern_function(argument1):
    return argument1 + 3

The antipattern I see so much in students' code is the immediate assigning of a new value to the variable representing a function argument. That line argument1 = 6 above is the problem. When that function is called, a value for that argument must be passed in. The argument's name in the function declaration, which is argument1 in this example, acts as a local variable (see next section) in the function that holds the value passed in. When the call

antipattern_function(3)

is made, the variable argument1 will hold the value 3 as the statements in the body of the function are executed. The body of this version of the function

def antipattern_function(argument1):
    argument1 = 6
    return argument1

immediately replaces whatever value is passed in and stored in argument1 with the hard-coded value 6. So it doesn't matter what value was passed in. Many students have the incorrect idea that they need to somehow initialize the value of function arguments in the body of the function. That's not how it works; function values are passed in instead!

Local Variables in functions

The body of the function carries out a computation. You often need to use variables to do that computation, and it's allowed to create new variables in the body of the function. These are called local variables, and they only exist while the body of the function is being executed. They cannot be used outside the function.

Sample specification: A function concatenate_list should take a list of strings list_of_strings as its arguments and return a string that includes each item from list_of_strings concatenated with , as a separator.

In the example above, we see that the local variable is not accessible outside the function. We also see how to make a local variable accessible outside the function: we return its value. Once we've returned the value, we can use it immediately as output or save the value into a variable/object to use later.

Global variables

Variables that exist outside of the functions in your program are global variables. You can use them inside your functions, but you shouldn't. It's a bad programming habit that won't be accepted for full credit in I427.

One reason using global variables in a function is a bad habit is that it's tempting to think we can modify these variables this way, but we can't. When we attempt to by assigning what we assume is a new value to a global variable, we're realling hiding the global variable by creating a local variable with the same name. We can verify that behavior by checking the value of the global_variable outside of the function after we called it. It hasn't changed!

There is a way to use global variables in your Python function so that you can modify them. I won't show it to you here becuase you don't need to use global variables inside your functions in I427. Functions should be self-contained. Any data they need to do their job will be passed in as input parameters.