In [1]:
# A function can accept a function as an argument, and can return a function
# This is useful if you want to reuse code that calls a function,
# and don't want to specify in advance which function it calls
In [2]:
# first, a little syntax
# if you want a function to take any number of arguments,
# use "*" before the parameter name and the parameter's value
# will be a tuple of arguments passed to the function
def multiple(*args):
return args
multiple('blah', 1)
Out[2]:
In [3]:
multiple('one', 2, 'tres', 4.0)
Out[3]:
In [4]:
# you can also use "*" before a parameter name you're passing to a function,
# and it will convert the value of the parameter (which must be iterable)
# to the arguments of the function being called, in order
def print_msg(text, number):
print '%s %g' % (text, number)
args = ('blah', 1)
print_msg(*args)
In [5]:
args = ('one', 2, 'tres', 4.0)
print_msg(*args)
In [1]:
# more syntax: you can define a function inside another function. its name is only usable
# in the function that it is defined in. it can reference variables in the enclosing function
def hello(who='world'):
def make_greeting():
return 'Hello, %s.' % who
print make_greeting()
hello()
In [5]:
# but be very very careful in the inner function not to try to assign a value to a variable
# from the enclosing function, as the assignment will create a new variable with the same name
# only visible within the inner function.
# this example does not work:
def avg(numbers):
sm = 0
ct = 0
def add(n):
sm = sm + n
ct = ct + 1
for n in numbers:
add(n)
return sm / ct
avg(range(10))
In [4]:
# because local variables are usually desirable in a function,
# this behavior is correct and by design, but not useful in this case.
# there is no clean way to solve this problem in Python 2.7.
# here is a workaround:
def avg(numbers):
state = {
'sum': 0.,
'count': 0.
}
def add(n):
state['sum'] += n
state['count'] += 1
for n in numbers:
add(n)
return state['sum'] / state['count']
# this works because "state" is never assigned in the inner function. modifying its
# contents doesn't assign a new value to the variable called "state"; its value is
# still the same mutable dict object that was assigned to it in the outer function.
avg(range(10))
Out[4]:
In [55]:
# back to decorators.
# lets define a function that does something. this converts its argument
# to a string and returns a list of the characters in the string
str(3.1415926535)
Out[55]:
In [60]:
list(str(3.1415926353))
Out[60]:
In [61]:
def charlist(value):
return list(str(value))
charlist(78)
Out[61]:
In [62]:
d = {
'x': 3
}
d
Out[62]:
In [63]:
charlist(d)
Out[63]:
In [64]:
# here's another simple function that just computes the sum of all
# integers up to n non-inclusive
def sum_to(n):
return sum(range(n))
sum_to(10)
Out[64]:
In [54]:
# as_charlist, given a function fn, returns a funtion inner
# that calls fn with its arguments and passes the result to
# charlist before returning it
def as_charlist(fn):
def inner(*args):
return charlist(fn(*args))
return inner
s = as_charlist(sum_to)
s
Out[54]:
In [50]:
s(10)
Out[50]:
In [52]:
# what if we made this the new definition of the function
# we want to treat this way?
def sum_to(n):
return sum(range(n))
sum_to = as_charlist(sum_to)
sum_to(13)
Out[52]:
In [53]:
# that's precisely what the decorator notation does
@as_charlist
def sum_to(n):
return sum(range(n))
sum_to(20)
Out[53]:
In [68]:
# decorators can be stacked
def backwards(fn):
def inner(*args):
return list(reversed(fn(*args)))
return inner
@backwards
@as_charlist
def sum_to(n):
return sum(range(n))
sum_to(20)
Out[68]:
In [86]:
# decorators can take arguments, but that requires an additional level of "wrapping"
def as_charlist(upcase=False):
def outer(fn):
def inner(*args):
s = str(fn(*args))
if upcase:
s = s.upper()
return list(s)
return inner
return outer
In [90]:
@as_charlist(True)
def greeting(who='world'):
return 'Hi, %s.' % who
greeting('everyone')
Out[90]: