Fluent Python Notes Part 2

Chapter 7 - Functions as First Class Objects

This chapter covers functions as first class objects, which it first defines as what is a first class object. That a function can be called, it can be used in other functions, it can be assigned to variables, and it can be returned from other functions. It talks about treating a function like an object since you can call it and store attributes in a function. It talks about higher order functions, which either take a function as an argument or return a function. Anonymous functions are lambdas and give some structure around best practices and when to use them and when to not. It gives the nine flavors of callable objects and really only covers user-defined callable objects, which are functions, anonymous functions, and classes. It goes more into each of those. It covers more depth on positional and keyword-only parameters and how to better define those in user-defined callable objects. And the different tricks around using packing and unpacking and things like the slash command to only allow positional arguments. It gives packages for functional programs specifically, talking especially about the partial and partial methods functions in the functools package.

Chapter 8 - Type Hints in Functions

This chapter introduces gradual typing. It slowly introduces typing to a function, with examples of using MyPy in order to use MyPyError-driven development, more or less, of seeing what errors MyPy throws and adding more type hits in order to reduce errors and pass those tests. It talks about types usable in annotations and goes over all of the different types, which is a ton. There's the Any class, there's simple types and classes like the built-ins. There's optional and union types, which optional are like non-required parameters, union are a union of several different kinds, often something like string and none. There's generic collections, tuple types, generic mappings, abstract base classes, which I think is one of the more usable ones, along with iterable, with the general principle of being allowing in what you accept and specific in what you output. So, allowing mappings of particular types, where the mappings just have to support certain functions, which then goes into the use of static protocols, where you can define a protocol of what functions must be supported in order to be allowed. It goes into the example of creating a protocol where the less-than function must be implemented in order to return a top function. So, to create a function where you return the top n number of entries, you need a static protocol. So, those combined make some very powerful use cases. It talks about the additional ones that are allowed, which are parameterized generics and typevars, callables, and no-returns. Those seem more for pretty high-level packages, so not too specifically interested in those. It briefly talks about annotating positional-only and variadic parameters, and gives some perspective on imperfect typing and strong testing, which is that, while typing is great, you shouldn't type everything or require typing for everything, because you lose a lot of the expressiveness of Python. A lot of the purpose of doing that can be covered by having more strong tests instead.

Chapter 9 - Decorators and Closures

This chapter covers decorators, which requires also covering closures and the nonlocal variable definition in Python. It goes into decorators 101, which defines a bit about when you might use a decorator. In particular, using a decorator is the equivalent to returning the function it's decorating wrapped in the definition of the decorator function. That is, if the decorator is named func and the function is named target, then using that decorator will return func(target). This is used when you need to, for example, register uses of a function - I think this is probably the use of prefect in Python for orchestration - or when you need to persist state across function calls in the case of averaging values over the entire history of the invocation of averaging, where you can keep appending values to some values list within a decorator. It covers the idea of closures, which are functions defined within another function, which reference variables defined outside of its inner scope, i.e. variables defined in the external function. These externally defined variables are where the historical series are stored, for example. Python uses the nonlocal variable parameter to define these variables within the inner function.

The end of the chapter covers parameterized decorators, which are decorators that accept arguments. Essentially, it goes over how, in order to achieve this, you have to nest functions. So you have the wrapping function outside of the decorator function. The wrapper function sets the default parameters and accepts the parameters, and then passes those into the decorator function, which, of course, wraps the final function. But this is how decorators are able to accept parameters. It also covers that it may be the case for parameterized decorators, and especially for that complicated decorator that might be better implemented as a class. I gave this example of a decorator as a class, which is much easier to read than a nested function, but in my opinion, I would use that.

Chapter 10 - Design Patterns with First-Class Functions

Chapter 10 is a pretty short chapter, which covers implementing some of the classic design patterns with first-class functions. It's basically showing how first-class functions can be used in dynamic languages, kind of broadly, but specifically how first-class functions in Python and also decorators can be used to simplify design patterns. The strategy pattern, which is defining an interface that has underlying strategies. I used the example of an e-commerce company which has a promotion class with subclasses for each promotion. It points out that this pattern requires classes with a single function, which may be better served in a function format. So it refactors those. To do that, it has different promotions as functions, and it passes those functions into a higher-level promotion function. It also shows how to define, for example, a best promotion function, which looks at lots of different promotions, and returns the best one. With the tutors, it shows the usefulness of decorators, where the decorators register all the different promotions, so then you can iterate over the promotions registered by the decorator, instead of having to explicitly call the function names. That way, if a new promotion is added, you don't have to manually update some of it. It also covers the command pattern in functions, which goes into the literature around design patterns in Python, of which there's much less than other languages.