Functions

Defining functions

>>> def add_hello(who):
...  return "hello %s" % (who)
>>> def add_hello(who="guido"):
...  return "hello %s" % (who)

Calling functions

Functions can be called with positional arguments

def f(a, b, c):
 print "%s %s %s" % (a, b, c)
f("by", "it's", "toe")

Functions can be called with keyword arguments (param=value) in any order, but keywords must be the formal parameter names

f(c="toe", a="by", b="it's")

Both can be mixed as long as positional comes before keyword arguments

f("by", c="toe", b="it's")

Factorial

Implement factorial... n! = n * (n-1)! for n > 0

Functions are first class objects

Functions can be passed around and assigned just like any other value:

>>> def add_hello(who):
...  return "hello %s" % (who)
>>> type(add_hello)
?
>>> f = add_hello
>>> f
?
>>> f = add_hello("Bob")
>>> f
?

Scope, functions in functions

You can define a function in a function

>>> def gimme_func():
...   x = []
...   def add_to_list():
...    x.append("a")
...    return x
...   return add_to_list
... 
>>> f = gimme_func()
>>> f()
['a']
>>> f()
['a', 'a']

add_to_list retains access to the variables that were in scope when it was defined, even after gimme_func already returned. This is called a closure.

make_adder(x)

Implement a function, make_adder, that accepts one numeric parameter, x. make_adder will return a function that adds x to whatever is passed in to that function

>>> add_three = make_adder(3)
>>> add_twenty = make_adder(20)
>>> add_three(5)
8
>>> add_twenty(5)
25

make_adder(x) solution

>>> def make_adder(n):
...  def add(x):
...   return n + x
...  return add
... 

Arbitrary # of parameters

Arbitrary number of positional parameters will be wrapped in a tuple when using *args

>>> def f(*args):
...  print type(args)
>>> f("foo", "bar")
<type 'tuple'>

Arbitrary number of keyword parameters will be wrapped in a dict when using **kwargs

>>> def f(**kwargs):
...  print type(kwargs)
>>> f(a="foo", b="bar")
<type 'dict'>

Can be combined!

>>> def f(a, b, *args, **kwargs):
...

Food dictionary

food = [
    {'name':'carrot', 'food_group':'vegetable', \
	'tasty':True, 'color':'orange'},
    {'name':'eggplant', 'food_group':'vegetable', \
	'tasty':False, 'color':'purple'},
    {'name':'kale', 'food_group':'vegetable', \
	'tasty':True, 'color':'green'},
    {'name':'wheatgrass', 'food_group':'vegetable', \
	'tasty':False, 'color':'green'},
    {'name':'kohlrabi', 'food_group':'vegetable', \
	'tasty':True, 'color':'green'},
    {'name':'orange', 'food_group':'fruit', \
	'tasty':True, 'color':'orange'},
    {'name':'grapes', 'food_group':'fruit', \
	'tasty':False, 'color':'purple'},
    {'name':'kiwi', 'food_group':'fruit', \
	'tasty':True, 'color':'green'},
    {'name':'pistachio ice cream', 'food_group':'dairy', \
	'tasty':True, 'color':'green'}
]

Filter API

print filter_1(food, color='green', food_group='vegetable')

[{'color': 'green', 'food_group': 'vegetable', \
	'tasty': True, 'name': 'kale'}, 
	{'color': 'green', 'food_group': 'vegetable', \
	'tasty': False, 'name': 'wheatgrass'}, 
	{'color': 'green', 'food_group': \
	k'vegetable', 'tasty': True, 'name': 'kohlrabi'}]

Using for loops

def filter_1(list_of_dicts, **kwargs):
	filtered_list = []
	for item in list_of_dicts:
		add_to_list = True
		for k, v in kwargs.items():
			if item[k] != v:
				add_to_list = False
				break
		if add_to_list:
			filtered_list.append(item)
	return filtered_list

Using for loops and list comprehensions

def filter_2(list_of_dicts, **kwargs):
	"""iteratively filter list for each kwarg"""
	for k, v in kwargs.items():
		list_of_dicts = \
			[f for f in list_of_dicts if f[k] == v]
	return list_of_dicts

Using list comprehensions

def filter_3(list_of_dicts, **kwargs):
	"""[k for k,v in kwargs.iteritems() \
	if list_of_dicts[0][k] == v] 
    to build a list where all keys match, while outer 
	comprehension is to iterate across all dicts
	"""
	return [d for d in list_of_dicts \
		if len([k for k,v in kwargs.iteritems() \
		if d[k] == v]) == len(kwargs)]