Functions (advanced)
Contents
Functions (advanced)#
Review: what is a function?#
A function is a re-usable piece of code that performs some operation (typically on some input), and then typically returns a result (i.e., an output).
Breaking this down:
Input: a variable defined by the user that is passed into a function using the
(input)
syntax.Also called an argument.
Output: the variable returned by a function after this operation is performed.
If a
return
value is not specified, a function will returnNone
.
def square(x):
"""Returns the square of X."""
return x**2
square(2)
4
Goals of today’s lecture#
Today we’ll be covering some more advanced topics with functions:
Returning multiple values.
Namespaces.
lambda
functions.Varying number of arguments.
We’ll also be getting more hands-on practice with functions!
Returning multiple values#
Functions can return
multiple values, or even another function.
This can be useful when:
The goal of a
function
can’t be distilled into a single value.You want to
return
multiple bits of information about something, e.g., itslen
, its value, and so on.
Multiple values can be separated with a ,
.
Multiple return
values: an example#
Suppose we wanted a function that takes two numbers as input, and returns both:
Their sum.
Their product.
def sum_product(a, b):
return a + b, a * b
sum_product(10, 200)
(210, 2000)
Check-in#
What do you notice about the type
of the object that gets returned when a function returns multiple values?
sum_product(5, 2)
(7, 10)
return
and tuple
s#
By default, a function
will package these multiple values into a tuple
.
It’s possible to return them in another form, e.g., in a structured dictionary.
But if you use the
return a, b
syntax,a
andb
will returned like:(a, b)
Namespaces#
Review: what is a namespace?#
A namespace is the “space” where a given set of variable names have been declared.
Python has several types of namespaces:
Built-in: Built-in objects within Python (e.g., Exceptions, lists, and more). These can be accessed from anywhere.
Global: Any objects defined in the main program. These can be accessed anywhere in the main program once you’ve defined them, but not in another Jupyter notebook, etc.
Local: If you define new variables within a function, those variables can only be accessed within the “scope” of that function.
The global namespace#
So far, we’ve mostly been working with variables defined in the global namespace.
I.e., once we define a variable in a notebook (and run that cell), we can reference it in another cell.
## define global variable
my_var = 2
## reference global variable
print(my_var)
2
Functions have their own namespace#
If you declare a variable within a function definition, that variable does not persist outside the scope of that function.
In the function below, we declare a new variable called answer
, which is eventually return
ed.
However, the variable itself does not exist outside the function.
def exponentiate(num, exp):
### "answer" is a new variable
answer = num ** exp
return answer
### This will throw an error
print(answer)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [9], in <cell line: 2>()
1 ### This will throw an error
----> 2 print(answer)
NameError: name 'answer' is not defined
Global variables can be referenced inside a function#
If you’ve defined a variable in the global namespace, you can reference it inside a function.
Word of caution ⚠️: this can sometimes make for confusing code.
## define global variable
my_var = 2
## define function
def add_two(x):
## references my_var
return x + my_var
add_two(2)
4
Check-in#
What would value of new_var
be after running the code below?
What about test_var
?
test_var = 2
def test_func(x):
test_var = x ** 2
return test_var
new_var = test_func(5)
Solution#
The global variable test_var
is unaltered by test_func
.
test_var = 2 ## global variable
def test_func(x):
test_var = x ** 2
return test_var
new_var = test_func(5)
print(new_var)
print(test_var)
25
2
Using whos
#
Remember that you can check which variables are defined using whos
.
whos
Variable Type Data/Info
------------------------------------
add_two function <function add_two at 0x7fd2f2e8bb80>
exponentiate function <function exponentiate at 0x7fd2d0133d30>
my_var int 2
new_var int 25
square function <function square at 0x7fd2d0133820>
test_func function <function test_func at 0x7fd2f2e5f1f0>
test_var int 2
lambda
functions#
So far, we’ve focused on creating functions using the def func_name(...)
syntax.
However, Python also has something called lambda functions.
Syntax:
lambda x: ...
.Main advantage: can be written in a single line, best if you want a simple function.
Excellent for passing as arguments into other functions, such as
sorted
.
square = lambda x: x**2
print(square(2))
print(square(4))
4
16
In theory, lambda
functions can have multiple arguments.
exp = lambda x, y: x*y
print(exp(2, 3))
6
Check-in#
Convert the function below into a lambda
function.
def add_one(x):
## Adds 1 to x
return x + 1
### Your code here
Solution#
# Lambda solution
add_one = lambda x: x + 1
print(add_one(1))
2
lambda
: summary#
lambda
is an easy, efficient way to define a simple function.In practice,
lambda
is most useful when defining functions “on the fly”.As arguments to pass into another function.
As nested functions within another function.
Varying number of arguments#
So far, we’ve assumed that we know how many arguments will be passed into a function at any given time. But this isn’t always the case.
Fortunately, Python gives us two ways to handle an arbitrary number of arguments:
*args
: allows afunction
to receive an arbitrary number of (positional) arguments, which can be “unpacked” as needed. The function treats them as atuple
.**kwargs
: allows afunction
to receive adictionary
of (keyword) arguments, which can be “unpacked” as needed.
*args
in practice#
The *args
syntax allows you to input an arbitrary number of arguments into a function.
def my_function(*fruits):
print("The last fruit is " + fruits[-1] + ".")
my_function("strawberry")
The last fruit is strawberry.
my_function("strawberry", "apple")
The last fruit is apple.
Check-in#
How exactly is this working? That is, what is my_function
treating *fruits
as?
Try print
ing out fruits
to see what’s going on.
### Your code here
Solution#
The *args
syntax tells Python to allow an arbitrary number of arguments. Any argument ends up being packaged as a tuple
, which the body of your function code can then unpack.
def my_function(*fruits):
print(fruits)
my_function("apple", "strawberry", "banana")
('apple', 'strawberry', 'banana')
**kargs
in practice#
The *kwargs
is similar to *args
, but allows for an arbitrary number of keyword arguments.
These are treated as a
dict
by the function.
def my_function(**fruits):
print(fruits)
### Keyword and value are automatically placed into dictionary
my_function(name = "apple", amount = 5)
{'name': 'apple', 'amount': 5}
### The specific keyword can be altered as needed
my_function(name = "banana", cost = 10)
{'name': 'banana', 'cost': 10}
Why use this?#
In general, **kwargs
is useful when you want flexibility.
For example, suppose you have a website, in which people can (optionally) fill out the following information:
Name
.Email
.Phone number
.Location
.
But because not everyone fills out every field, the function you use to store this information needs to be flexible about how many arguments it receives.
def store_user(**info):
## For now, this is just a placeholder to demonstrate
for item in info.items():
print(item)
store_user(Name = "Sean", Location = "San Diego")
('Name', 'Sean')
('Location', 'San Diego')
Conclusion#
This is the end of our unit on functions––but we’ll continue getting practice throughout the rest of the course!
Practice problems#
One of the best ways to learn a new concept is to actually practice it. Thus, I’m including a number of practice problems at the end of this lecture, which we’ll work through.
Problem 1: find the maximum number of a list
#
Goal: write a function that takes in a list
of numbers as input, and finds the maximum of the list
.
The catch: you can’t use the operator max
.
Things to consider:
If the input
list
is empty, you should returnNone
.Since you can’t use
max
, you might consider using afor
loop, checking the value of each number in turn.
### Your code here
Solution#
def find_max(lst):
if len(lst) == 0:
return None
### Start by setting max to first item in list
m = lst[0]
### Then iterate through each item
for i in lst:
### if item is greater than first item...
if i > m:
### Reset max to new item
m = i
return m
find_max([1, 2, 10, 5])
10
find_max([])
Problem 2: find the maximum number in a set of *args
#
Goal: write a function that takes in an arbitrary number of arguments (i.e., uses *args
), and finds the maximum.
The catch: you can’t use the operator max
.
Things to consider:
If there are no arguments, you should return
None
.Since you can’t use
max
, you might consider using afor
loop, checking the value of each number in turn.
### Your code here
Solution#
def find_max(*args):
## if no args are presented, "args" = ([],)
if args[0] == []:
return None
### Start by setting max to first item in list
m = args[0]
### Then iterate through each item
for i in args:
### if item is greater than first item...
if i > m:
### Reset max to new item
m = i
return m
find_max([1, 2, 10, 5])
[1, 2, 10, 5]
find_max([])
Problem 3: find the even numbers#
Goal: write a function that takes in a list
of numbers, and prints the even ones.
### Your code here
Solution#
def print_even(numbers):
for i in numbers:
if i % 2 == 0:
print(i)
print_even([1, 2, 8])
2
8
print_even([104, 57])
104
Problem 4: find the tallest in a dictionary.#
Suppose we want a function
that takes in a dict
of Names
and Heights
. That is, each key is a Name
, and it maps onto a Height
.
We want the function to return the Name
of the person with the largest Height
, as well as the Height
itself.
## Can't just max...that'll return "Sean"
heights = {'Sean': 67, 'Ben': 72, 'Anne': 66}
### Your code here
Solution#
def get_tallest(info):
## Get all tuples...
### turn into list so we can index into it
items = list(info.items())
## Start by assigning tallest to first in list
tallest = items[0]
## Then iterate through tuples...
for i in items:
### if item is taller than initial
if i[1] > tallest[1]:
### Reset "tallest"
tallest = i
return tallest
get_tallest(heights)
('Ben', 72)
get_tallest({'A': 20, 'B': 10, 'C': 200})
('C', 200)