bool(0), bool(500)
NB: Introduction to Functions
Objectives - Explain the benefits of functions - Illustrate how to use built-in functions - Illustrate how to create and use your own (user-defined) functions - Demonstrate the scope and lifetime of a variable - Illustrate global and local nature of variables through functions - Demonstrate function parameter use - Provide recommendations on how to create and document functions - Show how to print and write docstrings
Concepts - functions - built-in functions - user-defined functions - variable scope - global versus local variables - default arguments - *args - function call - docstring
Introduction
A function is piece of source code, separate fom the larger program, that performs a specific task.
This section of code is given a name and can be called from the main program. It is called by using its given name.
Functions are the verbs of a programming language. They signify action, and take subjects and objects (as it were).
Functions take input and produce output.
- Function inputs are called both parameters and arguments.
- Outputs are called return values
Functions are always written with parentheses at the end of their names, e.g.
len(some_list)
Internally, they contain a block of code to do their work.
Often the producte a transformation … from simple to complex.
When you use a function, we say you call a function. Programmers speak of “function calls” and “callbacks”.
Benefits
Reduce complex tasks into simpler tasks.
Eliminate duplicate code – no need to re-write, reuse function as needed.
Code reuse. Once function is written, you can reuse it in any other program.
Distribute tasks to multiple programmers. For example, each function can be written by someone.
Hide implementation details, i.e. abstraction.
Increase code readability.
Improve debugging by improving traceability. Things are easier to follow; you can jump from function to function.
Built-in Functions
Python provides many built-in functions. See Python built-in functions.
We’ve looked at many of these already.
These are functions that are available to use any time your are running Python.
To take one simple example, this is a built-in function: bool()
.
Takes an argument \(x\) and returns a boolean value, i.e. True
or False
.
Imported Functions
Python is meant to be a highly modular language.
It is not designed to have a lot of special purpose functions built into it.
These keeps it light and highly customizable.
Many functions (and other stuff) can be imported into a program to add to the functions that you can call in a script.
There are also many packages to bring in additional functions.
Packages and Libraries
User-Defined Functions
Python makes it easy for you to write your own functions. These are called user-defined functions.
Let’s write a function to compare the list against a threshold.
def vals_greater_than_or_equal_to_threshold(vals, thresh):
'''
This is the "docstring" of a function. It is optional but expected. It describes it's
purpose and the nature of the input and return values, as well as a sense of what it does.
More elaborate information should appear in external documentation packages with the function.
PURPOSE: Given a list of values, compare each value against a threshold
INPUTS
vals list of ints or floats
thresh int or float
OUTPUT
bools list of booleans
'''
= [val >= thresh for val in vals]
bools
return bools
Let’s break down the components
The function definition starts with def
, followed by name, one or more arguments in parenthesis, and then a colon.
Next comes a docstring to provide information to users about how and why to use the function.
The function body follows.
:astly is a return
statement
The function call allows for the function to be used.
It consists of function name and required arguments:
vals_greater_than_or_equal_to_threshold(arg1, arg2)
where arg1
, arg2
are arbitrary names.
About the docstring
A docstring m occurs as first statement in module, function, class, or method definition
Internally, it is saved in __doc__
attribute of the function object.
It needs to be indented.
It can be a single line or a multi-line string.
Let’s test our function
The function body used a list comprehension
for the compare:
[val >= thresh for val in vals]
## validate that it works for ints
= [3, 4]
x = 4
thr
vals_greater_than_or_equal_to_threshold(x, thr)
[False, True]
## validate that it works for floats
= [3.0, 4.2]
x = 4.2
thr
vals_greater_than_or_equal_to_threshold(x, thr)
## vals_greater_than_or_equal_to_threshold("foo", "bar")
This gives correct results and does exactly what we want.
Users can print the docstring
print(vals_greater_than_or_equal_to_threshold.__doc__)
print the help
help(vals_greater_than_or_equal_to_threshold)
?vals_greater_than_or_equal_to_threshold
Let’s test our function
The function body used a list comprehension
for the comparison:
[val >= thresh for val in vals]
## validate that it works for ints
= [3, 4]
x = 4
thr
vals_greater_than_or_equal_to_threshold(x, thr)
## validate that it works for floats
= [3.0, 4.2]
x = 4.2
thr
vals_greater_than_or_equal_to_threshold(x, thr)
This gives correct results and does exactly what we want.
Print the docstring
print(vals_greater_than_or_equal_to_threshold.__doc__)
Print the help
help(vals_greater_than_or_equal_to_threshold)
Use the ?
prefix …
?vals_greater_than_or_equal_to_threshold
Passing Parameters
Functions need to be called with correct number of parameters.
This function requires two params, but the function call includes only one param.
def fcn_bad_args(x, y):
return x + y
10) fcn_bad_args(
TypeError: fcn_bad_args() missing 1 required positional argument: 'y'
Parameter Order
When calling a function, parameter order matters.
def fcn_swapped_args(x, y):
= 5 * x + y
out return out
= 1
x = 2 y
fcn_swapped_args(x, y)
7
fcn_swapped_args(y, x)
11
Generally it’s best to keep parameters in order.
You can swap the order by putting the parameter names in the function call.
=y, x=x) fcn_swapped_args(y
Weirdness Alert
Note that the same name can be used for the parameter names and the variables passed to them.
The names themselves have nothng to do with each other!
In other words, just because a function names an argument foo
,
the variables passed to it don’t have to name foo
or anything like it.
They can even be named the same thing – it does not matter.
Unpacking List-likes with *args
The *
prefix operator can be passed to avoid specifying the arguments individually.
def show_arg_expansion(*models):
print("models :", models)
print("input arg type :", type(models))
print("input arg length:", len(models))
print("-----------------------------")
for mod in models:
print(mod)
We can pass a tuple of values to the function …
"logreg", "naive_bayes", "gbm") show_arg_expansion(
models : ('logreg', 'naive_bayes', 'gbm')
input arg type : <class 'tuple'>
input arg length: 3
-----------------------------
logreg
naive_bayes
gbm
You can also pass a list to the function.
If you want the elements unpacked, put *
before the list.
= ["logreg", "naive_bayes", "gbm"]
models *models) show_arg_expansion(
models : ('logreg', 'naive_bayes', 'gbm')
input arg type : <class 'tuple'>
input arg length: 3
-----------------------------
logreg
naive_bayes
gbm
This approach allows your function to accept an arbitrary number of arguments.
'a b c d e f g'.split()) show_arg_expansion(
The reverse is true, too.
You can use the *
prefix to pass list-like objects to a function that specifies its arguments.
def arg_expansion_example(x, y):
return x**y
= [2, 8]
my_args *my_args) arg_expansion_example(
But, the passed object must be the right length.
= [2, 8, 5]
my_args2 *my_args2) arg_expansion_example(
## **my_dict
Default Arguments
Use default arguments to set the value of arguments when left unspecified.
def show_results(precision, printing=True):
= round(precision, 2)
precision if printing:
print('precision =', precision)
return precision
= 0.912
pr = show_results(pr) res
precision = 0.91
The function call didn’t specify printing
, so it defaulted to True.
NOTE: Default arguments must follow non-default arguments. This causes trouble:
def show_results(precision, printing=True, uhoh):
= round(precision, 2)
precision if printing:
print('precision =', precision)
return precision
SyntaxError: non-default argument follows default argument (<ipython-input-19-29f5905a75a5>, line 1)
Returning Values
Functions are not required to have return statement.
If there is no return statement, a function returns None
.
Functions can return no value (None
), one value, or many.
Many values are returned as a tuple.
Any Python object can be returned.
## returns None, and prints.
def fcn_nothing_to_return(x, y):
= 'nothing to see here!'
out print(out)
fcn_nothing_to_return(x, y)
nothing to see here!
= fcn_nothing_to_return(1, 1)
r print(r)
nothing to see here!
None
## returns three values
def negate_coords(x, y, z):
return -x, -y, -z
= negate_coords(10, 20, 30)
a, b, c print('a =', a)
print('b =', b)
print('c =', c)
a = -10
b = -20
c = -30
= negate_coords(10, 20, 30) foo
len(foo) foo,
((-10, -20, -30), 3)
If you don’t need an output, use the dummy variable _
= negate_coords(10,20,30)
d, e, _ print('d =', d)
print('e =', e)
Note: It’s generally a good idea to include return statements, even if not returning a value.
This shows that you did not forget to consider the return value.
You can use return
or return None
.
Functions can contain multiple return statements.
These may be used under different logical conditions.
def absolute_value(num):
if num >= 0:
return num
return -num
-4) absolute_value(
4) absolute_value(
For non-negative values, the first return
is reached.
For negative values, the second return
is reached.
Function Design
A function is not just a bag of code!
Some good practices for creating and using functions:
- design a function to do one thing
Make them as simple as possible, which makes them:
- more comprehensible
- easier to maintain
- reusable
This helps avoid situations where a team has 20 variations of similar functions.
Give your function a good name.
- It should reflect the action it performs.
- Be consistent in your naming conventions.
- A name like
compute_variances_sort_save_print
suggests the function is overworked!
If the function compute_variances
also produces plots and updates variables, it will cause confusion.
Always give your function a docstring - Particularly important since indicating data types is not required.
- As a side note, you can include this information by using type annotation.
Finally, at some point you may be interested to learn some of the formatting languages that have been developed to write docstrings. See Lutz 2019 and this web page about Documenting Python Code for more info.