## 7.1 Functions (3)

arbitrary arguments, generator, Lambda function


def make_pizza(toppings):
print(toppings)

make_pizza('pepperoni')

• If we want to pass multiple arguments, we may create a list at first.


x=['mushrooms', 'green peppers', 'extra cheese']
def make_pizza(toppings):
for y in toppings:
print(y, end=', ')

make_pizza(x)


def make_pizza(topping_1, topping_2, topping_3):
print(topping_1, topping_2, topping_3)

make_pizza('mushrooms', 'green peppers', 'extra cheese')

• However, sometimes we are not sure about the exact number of the arguments.

• We can pass multiple arguments at once in the following way.


def make_pizza(*toppings):
print(toppings)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

• Note that Python packs the arguments into a tuple, even if the function receives only one value.


def make_pizza(*toppings):
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print("- " + topping)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')


Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

• Mixing Positional and Arbitrary Arguments

• If you want a function to accept several different kinds of arguments, the parameter that accepts an arbitrary number of arguments must be placed last in the function definition.


def make_pizza(size, *toppings):
print("\nMaking a " + str(size) +
"-inch pizza with the following toppings:")
for topping in toppings:
print("- " + topping)

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')


Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese

• Using Arbitrary Keyword Arguments


def build_profile(**user_info):
print(user_info)

build_profile(location='Shanghai',field='Management')


{'location': 'Shanghai', 'field': 'Management'}

• It builds up a dictionary.


def build_profile(**user_info):
profile={}
for key, value in user_info.items():
profile[key] = value
return profile

user_profile = build_profile(location='Shanghai',
field='Management')
print(user_profile)

• Using Arbitrary Keyword Arguments


def build_profile(first, last, **user_info):
profile = {}
profile['first_name'] = first
profile['last_name'] = last
for key, value in user_info.items():
profile[key] = value
return profile

user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)


{'first_name': 'albert', 'last_name': 'einstein',
'location': 'princeton', 'field': 'physics'}


def build_profile(*name, **user_info):
print(name)
print(user_info)

build_profile('albert', 'einstein',
location='princeton',
field='physics')


('albert', 'einstein')
{'location': 'princeton', 'field': 'physics'}

• As a tradition, we often use *args and **kw

• Example


def test(x,y=1,*a,**b):
print(x,y,a,b)


test(1)
test(1,2)
test(1,2,3)
test(1,2,3,4)
test(x=1,y=2)
test(1,a=2)
test(1,2,3,a=4)
test(1,2,3,y=4)

• Four ways of creating a list


def test1():
l = []
for i in range(100):
l = l + [i]

def test2():
l = []
for i in range(100):
l.append(i)

def test3():
l = list(range(100))

def test4():
l = [i for i in range(100)]


>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]


>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]


>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']


>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']


>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

• Generator


>>> L = [x * x for x in range(4)]
>>> L
[0, 1, 4, 9]
>>> g = (x * x for x in range(4)) #not tuple!
>>> g
at 0x1022ef630>

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
Traceback (most recent call last):
File "", line 1, in
StopIteration


g = (x * x for x in range(10))
for n in g:
print(n)


def yrange(n):
i = 0
while i < n:
print(i)
i += 1 # i=i+1

yrange(5)


def yrange(n):
i = 0
while i < n:
yield i
i += 1

o = yrange(5)

print(next(o))
# output: 0

print(next(o))
# output: 1

print(next(o))
# output: 2


def yrange(n):
i = 0
while i < n:
yield i
i += 1

for x in yrange(5):
print(x)

• map()


def f(x):
return x * x

y = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(type(y))
print(list(y))

map
[1, 4, 9, 16, 25, 36, 49, 64, 81]


print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
['1', '2', '3', '4', '5', '6', '7', '8', '9']


### Anonymous function: lambda


return x + y

lambda x, y: x + y

lambda x, y = 2: x + y
lambda *z: z

>>> a = lambda x, y: x + y
>>> a( 1, 3 )
4
>>> b = lambda x, y = 2: x + y
>>> b( 1 )
3
>>> b( 1, 3 )
4
>>> c = lambda *z: z
>>> c( 10, 'test')
(10, 'test')

• Sometimes the anonymous function is convenient.

• It has only one expression. (You do not have to use return)


sum = lambda arg1, arg2: arg1 + arg2

print ("The total is : ", sum( 10, 20 ))
print ("The total is : ", sum( 20, 20 ))


>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

def f(x):
return x * x


## 7.2 Functions (4)

Return a function, Recursion Intro

• Return a function

def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax


def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

f = lazy_sum(1, 3, 5, 7, 9)
print(f()) # print(f) causes error
print(type(f)) # delete lazy_sum, f still exists.

25
function


f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
print(f1==f2)

#output
False

• Defining a closure function

• When do we have a closure?

• We must have a nested function (function inside a function).

• The nested function must refer to a value defined in the enclosing function.

• The enclosing function must return the nested function.

• When to use closures?

• Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.

### Example


def print_msg():
msg = "zen of python"
def printer():
return msg
return printer

another = print_msg()
print(another())


def make_multiplier_of(n):
def multiplier(x):
return x * n
return multiplier

# Multiplier of 3
times3 = make_multiplier_of(3)

# Multiplier of 5
times5 = make_multiplier_of(5)

# Output: 27
print(times3(9))

# Output: 15
print(times5(3))

# Output: 30
print(times5(times3(2)))


### Recursion Brief Intro

• Calculate: Factorial $n!$


def fact(n):
if n==1:
return 1
else:
return n * fact(n - 1)


===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120


## Summary

• Functions
• Reading: Python for Everybody, Chapter 10.1-10.5, 10.7-10.8
• Reading: Python Crash Course, Chapter 8