Vous êtes sur la page 1sur 45

Python Programming: Lecture 3

Functions
Lili Dworkin
University of Pennsylvania

Functions as First-Class Objects

We can create a function from within another function

We can pass functions as arguments to other functions

We can create lists and dictionaries containing functions

... etc

Default and Named Arguments

Function arguments can (1) have default values, and (2) be


specified in any order by using named arguments.
def greet(name, greeting='Hello', punct='!'):
return greeting + ' ' + name + punct
I

name is a positional argument

greeting and punct are keyword arguments

We can mix positional and keyword arguments, but keyword


arguments must come last

Default and Named Arguments

def greet(name, greeting='Hello', punct='!'):


return greeting + ' ' + name + punct
>>> greet('Alice')
Hello Alice!
>>> greet('Alice', greeting='Hi', punct='.')
Hi Alice.
>>> greet('Alice', punct='.')
Hello Alice.
>>> greet(greeting='Hola', 'Alice')
SyntaxError: non-keyword arg after keyword arg

Args and Kwargs

def foo(a, b, c):


print a, b, c
>>> foo('apple', 'banana', 'carrot')
apple banana carrot
>>> l = ['apple', 'banana', 'carrot']
>>> foo(*l)
apple banana carrot

Args and Kwargs

def foo(a='apple', b='banana', c='carrot'):


print a, b, c
>>> foo(b='bean', a='apricot', c='cantaloupe')
apricot bean cantaloupe
>>> d = {'a': 'apricot', 'b': 'bean', 'c':
'cantaloupe'}
>>> foo(**d)
apricot bean cantaloupe

Args and Kwargs

def foo(fruit1, fruit2, vegetable='broccoli'):


print fruit1, fruit2, vegetable
>>> l = ['kiwi', 'kumquat']
>>> d = {'vegetable': 'cauliflower'}
>>> foo(*l, **d)
kiwi kumquat cauliflower

Args and Kwargs


Functions can take a variable number of arguments:
def foo(*args):
# args is a tuple of all positional arguments
print ' '.join([str(x) for x in args])
>>> foo(1, 2, 3)
1 2 3
>>> foo('a', 'b', 'c', 'd', 'e')
a b c d e
>>> foo(*l)
kiwi kumquat

Args and Kwargs

def foo(**kwargs):
# kwargs is a dict of all keyword arguments
print ' '.join(['%s=%s' % (k,v) for (k, v) in
kwargs.items()])
>>> foo(a=1, b=2)
a=1 b=2
>>> foo(a=1, b=2, c=3)
a=1 c=3 b=2
>>> foo(**d)
vegetable=cauliflower

Args and Kwargs

def foo(*args, **kwargs):


print ' '.join([str(x) for x in args])
print ' '.join(['%s=%s' % (k,v) for (k, v) in
kwargs.items()])
>>> foo(*l, **d)
kiwi kumquat
vegetable=cauliflower

Args and Kwargs: Zip

>>> a = [1,2,3]
>>> b = ['a','b','c']
>>> c = [4,5,6]
>>> zip(a,b)
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> zip(a,b,c)
[(1, 'a', 4), (2, 'b', 5), (3, 'c', 6)]
>>> l = [a, b, c]
>>> zip(*l)
[(1, 'a', 4), (2, 'b', 5), (3, 'c', 6)]

Args and Kwargs: Zip

>>> matrix = [[1, 0, 1],


...
[0, 0, 1],
...
[1, 1, 1]]
>>> zip(*matrix)
What does this code do?

Scope

>>>
...
...
...
...
>>>
>>>
5
3
>>>
5

def foo(x):
print x
x = 3
print x
x = 5
foo(x)

Scope

>>>
...
...
...
...
>>>
>>>
[]
[0]
>>>
[0]

def foo(l):
print l
l.append(0)
print l
l = []
foo(l)

Scope

>>> x = 5
>>> def foo():
... y = x + 1
... x = 0
... return y
...
>>> foo()
UnboundLocalError: local variable 'x' referenced
before assignment

Scope

>>> x = 5
>>> def foo():
... y = x + 1 # refers to the x below
... x = 0
... return y
I

If a variable is assigned to in a block, then all uses of the


variable in the block refer to that assignment

But we attempted to use the variable x before it was


assigned to

Scope

>>>
>>>
...
...
>>>
15
>>>
>>>
25

i = 10
def add(x):
return x + i
add(5)
i = 20
add(5)

Pythons nested scopes bind to variables, not object values, so the


function sees the most current value of the i variable.

Default Arguments

def foo(data=[]):
data.append(1)
return data
>>>
[1]
>>>
[1,
>>>
[1,

foo()
foo()
1]
foo()
1, 1]

Default Arguments

The function keeps using the same object, in each call.

Why? Default parameter values are evaluated when, and only


when, the def statement they belong to is executed.

Default Arguments

>>>
>>>
...
...
>>>
>>>
10

i = 10
def foo(data=i): # data now points to 10
print data
i = 20
foo()

Default Arguments

Workaround:
def foo(data=None):
if data is None:
data = [] # local variable
# modify value here

Default Arguments: Explicit Binding


But can also be useful!
>>> functions = []
>>> for i in range(10):
...
def add(x):
...
return x + i
...
functions.append(add)
...
>>> functions[0] # expect this to add 0
<function add at 0x1041cad70>
>>> functions[0](1)
10

Default Arguments: Explicit Binding

Workaround: use explicit binding


>>> functions = []
>>> for i in range(10):
...
def add(x, i=i):
...
return x + i
...
functions.append(add)
...
>>> functions[0](1)
1

Default Arguments: Memoization

def fib(n):
if n <= 1:
return n
else:
return fib(n-1) + fib(n-2)
We might be evaluating the same function over and over again ...

Default Arguments: Memoization

fibs = {}
def fib(n):
if n in fibs:
return fibs[n]
if n <= 1:
fibs[n] = n
else:
fibs[n] = fib(n-1) + fib(n-2)
return fibs[n]

Default Arguments: Memoization

But we dont want to use a global variable ...


def fib(n, fibs={}):
....
fibs is evaluated once, at function definition, and every successive
call to fib will update it.

Nested Functions

def custom_add(i):
def add(x):
return x + i
return add
>>>
>>>
6
>>>
>>>
11

add_five = custom_add(5)
add_five(1)
add_ten = custom_add(10)
add_ten(1)

Nested Functions

def custom_add(i):
def add(x):
return x + i
return add
>>>
>>>
>>>
15
>>>
>>>
15

i = 10
add_ten = custom_add(i)
add_ten(5)
i = 20
add_ten(5)

Nested Functions

This is another way to fix the problem we saw earlier:


>>>
>>>
...
...
...
>>>
1
>>>
2

functions = []
for i in range(10):
fun = custom_add(i)
functions.append(fun)
functions[0](1)
functions[1](1)

Nested Functions
A function like zip that always has list l as its first argument:
def custom_zip(l):
def my_zip(*args):
# remember that args is a tuple
zip_args = [l] + list(args)
# zip_args is a list of lists
return zip(*zip_args)
return my_zip
>>> z = custom_zip([1, 2, 3])
>>> z(['a', 'b', 'c'])
[(1, 'a'), (2, 'b'), (3, 'c')]

Nested Functions

def make_counter():
count = 0
def counter():
count = count + 1
return counter
>>> c = make_counter()
>>> c()
UnboundLocalError: local variable 'count' referenced
before assignment

Nested Functions

def make_counter():
count = 0
def counter():
count = count + 1
return counter
I

count = tries to define a new local variable

But it wont work, because we use count in its own definition

Nested Functions

def make_counter():
count = [0]
def counter():
count[0] = count[0] + 1
print count[0]
return counter
>>> c = make_counter()
>>> c()
1
>>> c()
2

Nested Functions

def make_counter():
count = [0]
def counter():
count[0] = count[0] + 1
print count[0]
return counter
I

We look up the outer variable count and then mutate it

Just referencing the variable, not assigning to it

Passing to Other Functions

>>> def compose(f, g, x):


... return f(g(x))
...
>>> compose(str, sum, [1,2,3])
'6'

Map and Filter

>>>
[1,
>>>
...
...
>>>
[2,

map(int, ['1', '2'])


2]
def is_even(x):
return x % 2 == 0
filter(is_even, [1, 2, 3, 4, 5])
4]

Lambda Functions

A way to define one-line nameless functions

Basic form: lambda a1, a2 ...

Cant have assignments or blocks

expr(a1, a2 ...)

Lambda Functions

>>>
>>>
2
>>>
[1,
>>>
[0,

f = lambda x: x*2
f(1)
map(lambda x: x**2, [1, 2, 3])
4, 9]
filter(lambda x: x==0, [0, 1, 0, 2])
0]

Sorting

>>> l = ['aaa', 'bb', 'c']


>>> sorted(l)
['aaa', 'bb', 'c']
What if we want to sort by increasing length?

Sorting

>>> sorted(['A', 'b', 'C'])


['A', 'C', 'b']
>>> sorted(['A', 'b', 'C'], key=str.lower)
['A', 'b', 'C']
I

key is a function of one argument that is called on each list


element prior to making comparisons

But it doesnt actually change the list

Sorting

>>> l = ['Anne Smith', 'John Cohen']


>>> sorted(l)
['Anne Smith', 'John Cohen']
>>> last_name = lambda name: name.split()[1]
>>> sorted(l, key=last_name)
['John Cohen', 'Anne Smith']

Sorting

>>> sorted(l, key=lambda x: len(x))


['c', 'bb', 'aaa']
>>> sorted(l, key=lambda x: len(x), reverse=True)
['aaa', 'bb', 'c']

Parsing
Goal:
>>> parse('2+3')
5
>>> parse('5-2')
3
def parse(s):
# assumes s is the form <int><op><int>
op = s[1]
if op == '+':
return int(s[0]) + int(s[2])
elif op == '-':
return int(s[0]) - int(s[2])
...

Parsing

>>>
>>>
>>>
def

add = lambda x,y: x + y


subtract = lambda x,y: x - y
op = {'+': add, '-': subtract}
parse(s):
f = op[s[1]]
return f(int(s[0]), int(s[2]))

Counting Characters

Lets make a dictionary count char where keys are characters of


the alphabet, and values are functions that take a string as input
and count the number of times that character appears.
>>> count_char['p']('apple')
2
>>> count_char['a']('banana')
3

Vous aimerez peut-être aussi