Computer Science 15-112, Spring 2013
Class Notes:  CA-authored review sheet on "Functions Redux"
 


 

1. Argument Lists


Summary: The asterisk “packs” or “unpacks” arguments in a function call. When calling a function, you can unpack arguments of a sequence into the function call: Python converts f(*[1, 2, 3]) to f(1, 2, 3). This can be useful for passing a variable-length sequence of arguments to a function. In the same way, when defining a function, the asterisk indicates that the arguments are packed in a tuple, and this means that your function can accept an arbitrary number of arguments:

def f(*a):

    print a

f(1) # prints (1,)

f(1, 2) # prints (1, 2)

f([1, 2]) # prints ([1, 2],)

f(*[1, 2]) # prints (1, 2) # why?

Practice:

(a) Write a function mean(*args) that finds the arithmetic mean of a sequence of numbers. Like the builtin max and sum,your function should work for either a single argument, which itself is a sequence, or multiple arguments, which together compose a sequence. In other words, if args has only one element, you should return the mean of that element, and otherwise you should return the mean of args.  For example:

>>> mean([1, 2, 3])

2

>>> mean(1, 2, 3)

2

>>> mean([1])

1

>>> mean(1)

TypeError: 'int' object is not iterable

(b) Complete practice problem (c) of section 2 below.

2. Functions as Arguments


Summary: Functions can be passed as arguments in Python, just like lists, dictionaries, objects, etc. In fact, functions are objects; everything in Python is an object. Just like other objects, you can find all their attributes and methods with the built-in dir function. However, some of that information is not important to you right now. There are three important things you need to know about functions as variables:

(1) You can call them

def f(x): return 3

def g(a): return a(1) # a is a variable that points to a function, f

print g(f) # we are passing f to g as an argument

# prints: 3

(2) You can find their name

def f(x): return 3

def g(a): return a.__name__

print g(f) # we are passing f to g as an argument

# prints: f

(3) You can access their documentation

def f(x):

    """Returns 3, a very important number"""

    return 3

def g(a): return a.__doc__.upper()

print g(f)

# prints: RETURNS 3, A VERY IMPORTANT NUMBER

Practice:

(a) Indicate what the following code prints:

def f(x):

    def g(y):

        def h(x):

            print 'h', (x, y, i),

            return x+y

        print 'g?',

        return [h(i*2) for i in xrange(x)]

        print 'g!'

    print 'f?',

    return g(x/2)

    print 'f!'

   

print f(4)

# print f(f(4)[1]) # bonus

(b)         Write a function nthNonNegativeWithProperty(n, property), which takes a nonnegative int n and a function property (which should return True or False for all integers), and returns the nth nonnegative integer, i, such that property(i) returns True. For example, for all values n, nthNonNegativeWithProperty(n, isSmithNumber)should return the same result as nthSmithNumber(n).

(c) Complete the following function:

def tester(function, testCases):

    """

    tester takes a function and a list of test cases.

    Each element of testCases should be a tuple of (arguments, result).

    'arguments' should be a tuple of arguments to pass to function,

    and result should be the expected result.

    tester prints the number of successful test cases, which test

    cases failed, and which test cases raised an exception.

   

    Example usage:

    >>> def integerDivision(x,y): return int(x/y)

    ...

    >>> testCases = [((3.0, 3.0), 1), # 3/3 should be 1

                     ((4.0, 3.0), 1), # 4/3 should be 1

                     ((4.0, -3.0), -2) # 4/(-3) should be -2

                     ((5.0, 0.0), False) # it's a test case, don't question it

                    ]

    >>> print tester(integerDivision, testCases)

    Error: integerDivision(4.0, -3.0) returned -1 instead of -2

    Exception: integerDivision(5.0, 0.0) raised an exception: float division

    integerDivision passed 2 of 4 test cases!

    None

    """

    pass

(d) Write a function hasWellFormattedDocstring(f), which takes a function f and returns True if no line of the docstring of f exceeds 80 characters, and False otherwise. This is a very basic format checker - feel free to extend it with extra features, like checking for consistent indentation width, and making sure that tabs and spaces aren’t mixed in indentation.

3. Closures


Functions can really be thought of as boxes, that takes in an input (x1, x2..xn), and gives an output y. We can write a function that takes in any input and produces almost any  output that you’d want, construct any kind of box. Since Y can be anything, we can even return other boxes as a possible value for Y. Consider the below example:

def makeAdder(n):
        def adder(x):

                return x+n

        return adder

The above function makes a box that takes a number x, and returns the result x+n.

Now, the above application was a very trivial example of the applications of closures. You can also use closures to do a powerful technique called currying. Consider the following example:


Lets say we have a function of the following format:

def runAndLog(foo):

        …

        …

        result = foo()

        …

        …

and

def bar(n):
        pass

If we wanted to call runAndLog(bar), we will get an error of wrong number of arguments. How will we accomplish this? Well consider the following function

def baz(n):
        def f():
                return bar(n)

        return f

We can then call runAndLog(baz(n)), which will do as we wanted. This is in fact, how we use the closure in canvas.after

Practice Problems: See next section

4. Anonymous functions


Recall in the last section that functions can be modelled by boxes, taking in some input and giving some output. Well, sometimes, if the functions are really simple, we don’t want to waste a name for the function (especially if it is poorly chosen like f). Lambda’s are a way to get around this. A lambda is a shorthand notation of the following format

f ---1---: ---EXP---, where --1-- is filled in by the argument variables, separated by commas, and the EXP is the  expression that you want the lambda to evaluate to. As an example, an addition function might be defined as  addition = lambda x,y: x+y.

Thus, the adder example can be defined as:

def makeAdder(n):

        return lambda x: x+n

and the baz example as:

def baz(n):

        return lambda :  bar(n)    #blank here means no arguments

Practice problems:


What will the following print?

def f1(n):
    f = lambda x: x%2
    def g(n):
        if (n > 0):
            return (f(n), lambda: g(n/2))
        else:
            return None
    curr = g(n)
    while (curr != None):    
        print curr[0],
        curr = curr[1]()
f1(5)
f1(10)
f1(1024)
f1(1023)

def f2( f ):

def g(x,f):

return f(x) == x
for i in xrange(100):

                if g(i,f) and f(i):

print i

g = lambda x : int(round(x**(1.0/2)))

f2(g)

        

def f3(a):
        def bar(n):

                return lambda i: i+n

        def baz(a,foo):

                return foo(a + foo(a))

        for i in xrange(a):

print baz(i,bar(i))

f3(5)