Functions as objects

Dr Andy Evans

[Fullscreen]

Functions

  • Functions being objects means functions can be passed into other functions:
    sort (list, key=str.upper)

Builtins

  • Sorted sorts stuff...
    sorted(iterable, key=None, reverse=False)
  • As we've seen, returns a copy of iterable, sorted.
  • The optional arguments must be kwargs.
  • "reverse" will sort in reverse if set to True.
  • "key" should be a function within the data of one argument that generates a value for comparing during sorting. e.g.
    key = str.lower
  • Will sort as if all strings are lowercase (otherwise all capitals are more important than all lowercase) .
  • Basically, it calls the method and passes in each item, and sorts the returned values.

Builtins: lists

  • Map function:
    map(function, iterable, ...)
  • Applies function to iterable as an iterator:
    # ord returns ACSII numbers
    for value in map(ord, ["A","B","C"]):
        print(value)

Lambdas

  • "headless" or "anonymous" functions: i.e. short functions without names. Can only contain expressions, not statements (e.g. variable assignments).
    for square in map(lambda x: x**2, [1,2,3,4,5]):
        print(str(square)) # 1, 4, 9, 16, 25

    for added in map(lambda x,y: x+y, [1,2,3,4,5],[1,2,3,4,5]):
        print(str(added)) # 2, 4, 6, 8, 10

    b = "fire walk with me".split(" ")
    for a in map(lambda x: x[::-1], b):
        print(a) # erif klaw htiw em

Reduce

  • FuncTools contains aggregators:
    functools.reduce(function, iterable)
  • Applies function cumulatively, so:
    def power(a, b):
        return a ** b

    import functools
    c = [2,3,4,5]
    d = functools.reduce(power, c) # (((2**3)**4)**5)
    print(d)
  • or
    reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) # ((((1+2)+3)+4)+5)
  • x is accumulator, y is each subsequent value.

Filter

Decorators

  • Enhance functions by wrapping them in code.
  • e.g. Least Recently Used cache:
    @lru_cache(maxsize=32)
    def get_lat_long(location):
        return lookup(location)

    for c in "Leeds", "Manchester", "Sheffield", "Liverpool", "Leeds":
        print(get_lat_long(c))


  • Saves the last 32 runs of this function for quick repeat; "memoizing"
  • For more, see: https://wiki.python.org/moin/PythonDecoratorLibrary

List comprehensions

  • Essentially slightly easier and more flexible lambdas for setting up lists:
    listA = [1,2,3,4]
    listB = [2*x for x in listA]
    print(listB) # [2, 4, 6, 8]

    listB = [2* x for x in range(1:5)]
    print(listB) # [2, 4, 6, 8]

    listB = [2*x for x in listA if x % 2 == 0] # Even numbers only
    print(listB) # [4, 8]

    Can also be used to apply a function to everything in a list. listB = [ord(x) for x in listA]

Combinatorics

  • List comprehensions can be used to generate combinations of values:
    listA = [(x, y) for x in [1,2,3] for y in ["a","b","c"]]
    print(listA)

    [(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
  • Essentially like developing tuples from nested for-each loops.
  • For a tuple, you have to use a parenthesis here or you get a syntax error.

Generator expressions

  • Generator expressions: exactly like list comprehensions, but generate an item at a time, so more memory efficient:
    listA = [1,2,3,4]
    notListB = (2* x for x in range(1,5))

    for i in notListB :
        print(i) # Works
    print(notListB) # Doesn't print contents as don't exist.

Scope

  • The scope of variables created inside lambdas, list comprehensions, and generator expresions is the function outlined, which some people like, as it doesn't create free variables slooping around the code afterwards.
  • You can still use variables created earlier within these constructions, though obviously that would be bad practice if one wanted to isolate this code from side effects.

Generator Function

  • New keyword "yield":
    def count():
        a = 0
        while True:
            yield a # Returns control and waits next call.
            a = a + 1
    b = count()
    print (next(b))
    print (next(b))
  • Where the condition is just set to True, they will generate infinite sequences. Must be attached to a variable so they are called from the same instance each time and the method level variables aren't wiped; this doesn't work:
    print (next(count()))
  • Can also be used:
    for i in count():
        print(i)