DekGenius.com
[ Team LiB ] Previous Section Next Section

B.4 Part IV, Functions

See Section 14.9 for the exercises.

  1. The basics. There's not much to this one, but notice that using print (and hence your function) is technically a polymorphic operation, which does the right thing for each type of object:

    % python
    >>> def func(x): print x
    ...
    >>> func("spam")
    spam
    >>> func(42)
    42
    >>> func([1, 2, 3])
    [1, 2, 3]
    >>> func({'food': 'spam'})
    {'food': 'spam'}
  2. Arguments. Here's a sample solution. Remember that you have to use print to see results in the test calls, because a file isn't the same as code typed interactively; Python doesn't normally echo the results of expression statements in files.

    def adder(x, y):
        return x + y
    
    print adder(2, 3)
    print adder('spam', 'eggs')
    print adder(['a', 'b'], ['c', 'd'])
    
    % python mod.py
    5
    spameggs
    ['a', 'b', 'c', 'd']
  3. varargs. Two alternative adder functions are shown in the following file, adders.py. The hard part here is figuring out how to initialize an accumulator to an empty value of whatever type is passed in. The first solution, uses manual type testing to look for an integer and an empty slice of the first argument (assumed to be a sequence) otherwise. The second solution, uses the first argument to initialize and scan items 2 and beyond, much like one of the min function variants shown in Chapter 13.

    The second solution is better. Both of these assume all arguments are the same type and neither works on dictionaries; as we saw in Part II, + doesn't work on mixed types or dictionaries. We could add a type test and special code to add dictionaries too, but that's extra credit.

    def adder1(*args):
        print 'adder1',
        if type(args[0]) == type(0):    # Integer?
             sum = 0                    # Init to zero.
        else:                           # else sequence:
             sum = args[0][:0]          # Use empty slice of arg1.
        for arg in args:
            sum = sum + arg
        return sum
    
    def adder2(*args):
        print 'adder2',
        sum = args[0]               # Init to arg1.
        for next in args[1:]:
            sum = sum + next        # Add items 2..N.
        return sum
    
    for func in (adder1, adder2):
        print func(2, 3, 4)
        print func('spam', 'eggs', 'toast')
        print func(['a', 'b'], ['c', 'd'], ['e', 'f'])
    
    % python adders.py
    adder1 9
    adder1 spameggstoast
    adder1 ['a', 'b', 'c', 'd', 'e', 'f']
    adder2 9
    adder2 spameggstoast
    adder2 ['a', 'b', 'c', 'd', 'e', 'f']
  4. Keywords. Here is our solution to the first part of this exercise (file mod.py). To iterate over keyword arguments, use a **args form in the function header and use a loop like: for x in args.keys( ): use args[x].

    def adder(good=1, bad=2, ugly=3):
        return good + bad + ugly
    
    print adder(  )
    print adder(5)
    print adder(5, 6)
    print adder(5, 6, 7)
    print adder(ugly=7, good=6, bad=5)
    
    % python mod.py
    6
    10
    14
    18
    18
  5. and 6. Here are our solutions to exercises 5 and 6 (file dicts.py). These are just coding exercises, though, because Python 1.5 added dictionary methods, to do things like copying and adding (merging) dictionaries: D.copy( ), and D1.update(D2). (See Python's library manual or the Python Pocket Reference for more details). X[:] doesn't work for dictionaries, since they're not sequences (see Chapter 6 for details). Also remember that if we assign (e = d) rather than copy, we generate a reference to a shared dictionary object; changing d changes e, too.

    def copyDict(old):
        new = {  }
        for key in old.keys(  ):
            new[key] = old[key]
        return new
    
    def addDict(d1, d2):
        new = {  }
        for key in d1.keys(  ):
            new[key] = d1[key]
        for key in d2.keys(  ):
            new[key] = d2[key]
        return new
    
    % python
    >>> from dicts import *
    >>> d = {1:1, 2:2}
    >>> e = copyDict(d)
    >>> d[2] = '?'
    >>> d
    {1: 1, 2: '?'}
    >>> e
    {1: 1, 2: 2}
    
    >>> x = {1:1}
    >>> y = {2:2}
    >>> z = addDict(x, y)
    >>> z
    {1: 1, 2: 2}
  1. More argument matching examples. Here is the sort of interaction you should get, along with comments that explain the matching that goes on:

    def f1(a, b): print a, b             # Normal args
    
    def f2(a, *b): print a, b            # Positional varargs
    
    def f3(a, **b): print a, b           # Keyword varargs
    
    def f4(a, *b, **c): print a, b, c    # Mixed modes
    
    def f5(a, b=2, c=3): print a, b, c   # Defaults
    
    def f6(a, b=2, *c): print a, b, c    # Defaults and positional varargs
    
    
    % python
    >>> f1(1, 2)                  # Matched by position (order matters)
    1 2
    >>> f1(b=2, a=1)              # Matched by name (order doesn't matter)
    1 2
    
    >>> f2(1, 2, 3)               # Extra positionals collected in a tuple
    1 (2, 3)
    
    >>> f3(1, x=2, y=3)           # Extra keywords collected in a dictionary
    1 {'x': 2, 'y': 3}
    
    >>> f4(1, 2, 3, x=2, y=3)     # Extra of both kinds
    1 (2, 3) {'x': 2, 'y': 3}
    
    >>> f5(1)                     # Both defaults kick in.
    1 2 3
    >>> f5(1, 4)                  # Only one default used
    1 4 3
    
    >>> f6(1)                     # One argument: matches "a"
    1 2 (  )
    >>> f6(1, 3, 4)               # Extra positional collected
    1 3 (4,)
  2. Primes revisited. Below is the primes example wrapped up in a function and module (file primes.py) so it can be run multiple times. We added an if test to trap negatives, 0, and 1. We also changed / to // to make this immune from the Python 3.0 / "true" division changes we studied in Chapter 4, and support floating-point numbers. The // operator works in both the current and future division scheme, but the future / operator fails (uncomment the from and change // to / to see the differences in 2.2 and 3.0).

    #from __future__ import division
    
    def prime(y):
        if y <= 1:                              # For some y > 1
            print y, 'not prime'
        else:
            x = y // 2                          # Future / fails
            while x > 1:
                if y % x == 0:                  # No remainder?
                    print y, 'has factor', x
                    break                       # Skip else.
                x -= 1
            else:
                print y, 'is prime'
    
    prime(13); prime(13.0)
    prime(15); prime(15.0)
    prime(3);  prime(2)
    prime(1);  prime(-3)

    Here is the module in action; the // operator allows it to works for floating-point numbers too, even though it perhaps should not:

    % python primes.py
    13 is prime
    13.0 is prime
    15 has factor 5
    15.0 has factor 5.0
    3 is prime
    2 is prime
    1 not prime
    -3 not prime

    This function still isn't very reusable yet—it could return values instead of printing—but it's enough to run experiments. It's also still not a strict mathematical prime (floating-points work), and is still inefficient. Improvements are left as exercises for more mathematically-minded readers. Hint: a for loop over range(y, 1, -1) may be a bit quicker than the while (in fact, it's roughly twice as fast in 2.2), but the algorithm is the real bottleneck here. To time alternatives, use the built-in time module, and coding patterns like those used in this general function-call timer (see the library manual for details):

    def timer(reps, func, *args):
        import time
        start = time.clock(  )
        for i in xrange(reps):
            apply(func, args)
        return time.clock(  ) - start
  3. List comprehensions. Here is the sort of code you should write; we may have a preference, but we're not telling:

    >>> values = [2, 4, 9, 16, 25]
    >>> import math
    
    >>> res = [  ]
    >>> for x in values: res.append(math.sqrt(x))
    ...
    >>> res
    [1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
    
    >>> map(math.sqrt, values)
    [1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
    
    >>> [math.sqrt(x) for x in values]
    [1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
    [ Team LiB ] Previous Section Next Section