FUNCTIONS PART II
Objectives
- Use the * and ** operator as parameters to a function and outside of a function
- Leverage dictionary and tuple unpacking to create more flexible functions
- Understand what a lambda is and how they are used
- Explain what closure is and how it works in Python
- Use built in functions to sort, reverse and calculate aggregate information
*args
A special operator we can pass to functions
Gathers remaining arguments as a tuple
This is just a parameter - you can call it whatever you want!
Example
def sum_all_values(*args):
total = 0
for val in args:
total += val
return total
sum_all_values(1, 2, 3) # 6
sum_all_values(1, 2, 3, 4, 5) # 15
Another Example
def ensure_correct_info(*args):
if "Colt" in args and "Steele" in args:
return "Welcome back Colt!"
return "Not sure who you are..."
ensure_correct_info() # Not sure who you are...
ensure_correct_info(1, True, "Steele", "Colt")
The order does not matter!
**kwargs
A special operator we can pass to functions
Gathers remaining keyword arguments as a dictionary
This is just a parameter - you can call it whatever you want!
Example
def favorite_colors(**kwargs):
for key, value in kwargs.items():
print(f"{key}'s favorite color is {value}")
favorite_colors(rusty='green', colt='blue')
# rusty's favorite color is green
# colt's favorite color is blue
Another Example
def special_greeting(**kwargs):
if "Colt" in kwargs and kwargs["Colt"] == "special":
return "You get a special greeting Colt!"
elif "Colt" in kwargs:
return f"{kwargs["Colt"]} Colt!"
return "Not sure who this is..."
special_greeting(Colt='Hello') # Hello Colt!
special_greeting(Bob='hello') # Not sure who this is...
special_greeting(Colt='special') # You get a special greeting Colt!
Parameter Ordering
- parameters
- *args
- default parameters
- **kwargs
Combined Example
def display_info(a, b, *args, instructor="Colt", **kwargs):
return [a, b, args, instructor, kwargs]
display_info(1, 2, 3, last_name="Steele", job="Instructor")
[1, 2, (3,), 'Colt', {'job': 'Instructor', 'last_name': 'Steele'}]
What's going on with with that (3,) ?
When you have a tuple with one item - Python needs to distinguish between parenthesis and a tuple!
Using * as an Argument:
Argument Unpacking
def sum_all_values(*args):
# there's a built in sum function - we'll see more later!
return sum(args)
sum_all_values([1, 2, 3, 4]) # nope...
sum_all_values((1, 2, 3, 4)) # this does not work either...
sum_all_values(*[1, 2, 3, 4]) # 10
sum_all_values(*(1, 2, 3, 4)) # 10
We can use * as an argument to a function to "unpack" values
Using ** as an Argument:
Dictionary Unpacking
def display_names(first, second):
return f"{first} says hello to {second}"
names = {"first": "Colt", "second": "Rusty"}
display_names(names) # nope..
display_names(**names) "Colt says hello to Rusty"
We can use ** as an argument to a function to "unpack" dictionary values into keyword arguments
Example with **
as an Argument
def display_names(first, second):
return f"{first} says hello to {second}"
names = {"first": "Colt", "second": "Rusty"}
display_names(names) # nope..
display_names(**names) "Colt says hello to Rusty"
YOUR TURN
Lambdas
def first_function():
return 'Hello!'
first_function() # 'Hello!'
first_function.__name__ # first_function'
But lambdas are anonymous functions!
first_lambda = lambda x: x + 5
first_lambda(10) # 15
first_lambda.__name__ # '<lambda>'
Normal functions have names...
Lambda Syntax
add_values = lambda x, y: x + y
multiply_values = lambda x, y: x + y
add_values(10, 20) # 30
multiply_values(10, 20) # 200
lambda parameters : body of function
map
l = [1, 2, 3, 4]
doubles = list(map(lambda x: x * 2, l))
evens # [2, 4, 6, 8]
A standard function that accepts at least two arguments, a function and an "iterable"
iterable - something that can be iterated over (lists, strings, dictionaries, sets, tuples)
runs the lambda for each value in the iterable and returns a map object which can be converted into another data structure
in Action
l = [1,2,3,4]
doubles = list(map(lambda x: x*2, l))
evens # [2,4,6,8]
names = [
{'first':'Rusty', 'last': 'Steele'},
{'first':'Colt', 'last': 'Steele', },
{'first':'Blue', 'last': 'Steele', }
]
first_names = list(map(lambda x: x['first'], names))
first_names # ['Rusty', 'Colt', 'Blue']
map
filter
l = [1,2,3,4]
evens = list(filter(lambda x: x % 2 == 0, l))
evens # [2,4]
There is a lambda for each value in the iterable.
Returns filter object which can be converted into other iterables
The object contains only the values that return true to the lambda
Combining filter and map
names = ['Lassie', 'Colt', 'Rusty']
Given this list of names:
list(map(lambda name: f"Your instructor is {name}",
filter(lambda value: len(value) < 5, names)))
# ['Your instructor is Colt']
Return a new list with the string
"Your instructor is " + each value in the array,
but only if the value is less than 5 characters
What about
List Comprehension?
names = ['Lassie', 'Colt', 'Rusty']
Given this list of names:
[f"Your instructor is {name}" for name in names if len(name) < 5]
Return a new list with the string:
"Your instructor is " + each value in the array,
but only if the value is less than 5 characters
reduce
from functools import reduce
l = [1,2,3,4]
product = reduce(lambda x, y: x * y, l)
l = [1,2,3,4]
total = reduce(lambda x, y: x + y, l, 10)
runs a function of two arguments cumulatively to the items of iterable, from left to right, which reduces the iterable to a single value
You will not be using reduce frequently so it's good to know it exists, but you will not find yourself using it since we have a better option in most cases
reduce or List Comprehension?
from functools import reduce
l = [1,2,3,4]
product = reduce(lambda x, y: x * y, l)
For almost all problems especially at this stage, use list comprehension - you will see it far more in the wild
YOUR TURN
Closures
Accessing variables defined in outer functions after they have returned!
- private variables
- not using global variables
Example
Let's imagine we want a counter variable and would like to keep track of it
"Public" Counter
count = 0
def counter():
global count
count += 1
return count
This works, but anyone can change count!
"Private" Counter
def counter():
count = 0
count += 1
return count
No one can change count directly, but it keeps getting redefined!
Closures using nonlocal
def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
Here we're making a variable count inside the counter function, which can only be accessed by counter and inner.
Once we return inner, we can still remember count through closure!
Closures using Objects
def counter():
counter.count = 0
def inner():
counter.count += 1
return counter.count
return inner
Here we're making a property on the counter function which can only be accessed by counter and inner.
Once we return inner, we can still remember the count property through closure!
Partial Application with Closures
def outer(a):
def inner(b):
return a+b
return inner
result = outer(10)
result(20) # 30
You will see this pattern again when you learn about decorators!
When you are just using (not modifying) a variable through closure, you don't need to use nonlocal or objects!
YOUR TURN
Built-in
Functions
all
all([0,1,2,3]) # False
all([char for char in 'eio' if char in 'aeiou'])
all([num for num in [4,2,10,6,8] if num % 2 == 0]) # True
Return True if all elements of the iterable are truthy (or if the iterable is empty)
any
any([0, 1, 2, 3]) # True
any([val for val in [1,2,3] if val > 2]) # True
any([val for val in [1,2,3] if val > 5]) # False
Return True if any element of the iterable is truthy. If the iterable is empty, return False.
sorted
# sorted (works on anything that is iterable)
more_numbers = [6,1,8,2]
sorted(more_numbers) # [1, 2, 6, 8]
print(more_numbers) # [6, 1, 8, 2]
Returns a new sorted list from the items in iterable
reversed
more_numbers = [6, 1, 8, 2]
reversed(more_numbers) # <list_reverseiterator at 0x1049f7da0>
print(list(reversed(more_numbers))) # [2, 8, 1, 6]
Return a reverse iterator.
Use slices or .reverse!
YOUR TURN
max
# max (strings, dicts with same keys)
max([3,4,1,2]) # 4
max((1,2,3,4)) # 4
max('awesome') # 'w'
max({1:'a', 3:'c', 2:'b'}) # 3
Return the largest item in an iterable or the largest of two or more arguments.
min
# min (strings, dicts with same keys)
min([3,4,1,2]) # 1
min((1,2,3,4)) # 1
min('awesome') # 'a'
min({1:'a', 3:'c', 2:'b'}) # 1
Return the smallest item in an iterable or the smallest of two or more arguments.
len
len('awesome') # 7
len((1,2,3,4)) # 4
len([1,2,3,4]) # 4
len(range(0,10) # 10
len({1,2,3,4}) # 4
len({'a':1, 'b':2, 'c':2} # 3
Return the length (the number of items) of an object. The argument may be a sequence (such as a string, tuple, list, or range) or a collection (such as a dictionary, set)
abs
abs(-5) # 5
abs(5) # 5
Return the absolute value of a number. The argument may be an integer or a floating point number.
sum
sum([1,2,3,4]) # 10
sum([1,2,3,4], -10) # 0
- Takes an iterable and an optional start.
- Returns the sum of start and the items of an iterable from left to right and returns the total.
- start defaults to 0
round
round(10.2) # 10
round(1.212121, 2) # 1.21
Return number rounded to ndigits precision after the decimal point. If ndigits is omitted or is None, it returns the nearest integer to its input.
zip
first_zip = zip([1,2,3], [4,5,6])
list(first_zip) # [(1, 4), (2, 5), (3, 6)]
dict(first_zip) # {1: 4, 2: 5, 3: 6}
Make an iterator that aggregates elements from each of the iterables.
Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables.
The iterator stops when the shortest input iterable is exhausted.
zip
five_by_two = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
list(zip(*five_by_two))
[(0, 1, 2, 3, 4), (1, 2, 3, 4, 5)]
Very common when working with more complex data structures!
YOUR TURN
Recap
*args is useful for accepting a variable number of arguments
**kwargs is useful when accepting a variable number of keyword arguments
you can use * to unpack argument values
you can use ** to unpack dictionary values
closures are very useful for private variables
lambdas are annonymous functions that are useful with map, filter and reduce
map is useful for transforming lists into different lists of the same size
filter is useful for transforming lists into lists of different sizes
Python has quite a few built in functions - make sure to spend the time learning them!
Functions Part II
By colt
Functions Part II
- 13,171