Decorators are higher-order functions that let us modify the behavior of existing functions with extremely terse syntax, for instance, to print out parameters, or to cache output. In this video, learn how to write a function decorator, and how to look for decorators in the functools module. Also, learn about the use of map/filter/reduce and of Lambda functions in Python.
- [Instructor] Function decorators are a feature of Python that may seem mysterious. In fact, you have already learned all that you need to understand them. They're high order functions which take a function as import and return another, and they're applied to a function by way of a short hand. We start by importing the modules we need. Decorators are useful when we want to modify several functions in the same way. For instance, for debugging purposes, we may wish to print out the arguments whenever a function is called. We can certainly do that by adding a print statement to the function itself. But we can do it more economically by creating a decorator, which we will be able to reuse. We call it print arcs. It takes a single argument, a function, Input func. In the body, we defined the new function, output func that we will return. It prints out the name of the original function and the arguments, which it was called. It then calls the original function. This decorator could be useful to track the calls of a recursive function. For instance, a function that computes the Nth Fibonacci number. Here it is. I have even included a doc string to document it. The decorator is applied to the function by writing it just before the definition prefixed by an "at" character. What happens is that, Python processes the function definition as usual, then feeds it to the decorator and stores the result in decorated function within the same name. Let's see what happens when we call it. Indeed, we see the sequence of recursive calls that we may expect. In fact, we could have done a better job of this. When we wrap the function, we lost its name and its doc string. The Functools standard library module, offers an easy way to fix this. Amusingly, this requires yet another decorator which we apply to the definition of output Func, within the print arcs decorator. Intuitively, the Functools decorator wraps, lets us declare that output func wraps input func. We define Fibonacci again, and we see that the function now preserves name and doc string. Functools includes more useful decorators. We've seen that the recursive computation of Fibonacci numbers is wasteful because the function calls itself repeatedly with the same integer argument. We could introduce a caching mechanism that remembers the functions output for every integer argument, for which it's called. Functools has just the right thing. It's called LRU Cache, for least recently used. We apply it as a second decorator or on top of print arcs. Decorators are applied, starting from the bottom. We can specify a maximum size for the cache, but here we'll make it so it remembers everything. Let's see. Indeed, no wasted cause now. And even fewer, the second time around, since the cache has learned the value for an equal file. Let's bring back turtles and draw a spiral using the Fibonacci sequence. This is known as the golden spiral and it appears in nature. For instance, in the shell of the Nautilus Mollusc. We first need a new comment to draw a quarter of circle of a given radius. Which we can do by taking 90 small steps and turning by one degree after each. It's not really a circle, but we want to see the difference. We can be elegant and make a functional version QC of quarter circle that fixes their ages and can be applied to any turtle. The Fibonacci spiral is a sequence of quarter circles with radii given by the sequence of Fibonacci numbers. So let's run this comments on a turtle. Very pretty. Let's move on to a few other features of Functional Python. We've already talked about iterators, comprehensions, generator expressions and generators. Those are functional in nature. Anonymous functions, give us a way to define a function without a name. They're written with a Lambda notation, which comes straight from Lisp. A Lambda function that doubles the number, would be Lambda X colon two times X. We can assign it to a variable and then use it like any other function, or we could use it directly. Lambda functions, I use dolphin with the built-ins map and filter. As well with Functool reduce, which are the last features that I'd like to discuss here. Map, applies a function to all elements of a sequence. So let's apply twice to the first 10 Fibonacci numbers. The result is another iterator, which I can pass to List if I wish to see the elements. Filter, selects the elements of a sequence or iterator that satisfy a condition, expressed as a Boolean function. For instance, all the even Fibonacci numbers among the first 10. You see how you use the anonymous function to express the criteria that needs to be satisfied. Last, Functools reduce, applies an operation to the first two items in a sequence then to the resulting value and the third item and so on. This is indeed known as a reduction. We can use it with a two argument Lambda to compute the sum of the first 10 Fibonacci numbers. Functional techniques can be a very powerful tool to write efficient code. When you tackle a new problem, ask yourself if it becomes simpler when you think of it in terms of functions, rather than data.