Best Practices for Using Functional Programming in Python
Introduction
Python is a very versatile, high-level programming language. It has a generous standard library, support for multiple programming paradigms, and a lot of internal transparency. If you choose, you can peek into lower layers of Python and modify them – and even modify the runtime on the fly as the program executes.
I’ve recently noticed an evolution in the way Python programmers use the language as they gain more experience. Like many new Python programmers, I appreciated the simplicity and user friendliness of the the basic looping, function, and class definition syntax when I was first learning. As I mastered basic syntax, I became curious about intermediate and advanced features like inheritance, generators, and metaprogramming. However, I wasn’t quite sure when to use them, and would often jump at opportunities to practice that weren’t a great fit. For a while, my code became more complex and harder to read. Then, as I kept iterating – especially if I kept working on the same codebase – I gradually reverted back to mostly using functions, loops, and singleton classes.
With that being said, the other features exist for a reason, and they’re important tools to understand. “How to write good code” is obviously an expansive topic – and there’s no single right answer! Instead, my goal with this blog post is to zero in on a specific aspect: functional programming as applied to Python. I’ll dig into what it is, how it can be used in Python, and how – according to my experience – it’s used best.
What is functional programming?
Functional programming, or FP, is a coding paradigm in which the building blocks are immutable values and “pure functions” that share no state with other functions. Every time a pure function has a given input, it will return the same output – without mutating data or causing side effects. In this sense, pure functions are often compared to mathematical operations. For example, 3 plus 4 will always equal 7, regardless of what other mathematical operations are being done, or how many times you’ve added things together before.
With the building blocks of pure functions and immutable values, programmers can create logical structures. Iteration can be replaced with recursion, because it is the functional way to cause the same action to occur multiple times. The function calls itself, with new inputs, until the parameters meet a termination condition. In addition, there are higher-order functions, which take in other functions as input and/or return them as output. I’ll describe some of these later on.
Although functional programming has existed since the 1950s, and is implemented by a long lineage of languages, it doesn’t fully describe a programming language. Clojure, Common Lisp, Haskell, and OCaml are all functional-first languages with different stances on other programming language concepts, like the type system and strict or lazy evaluation. Most of them also support side effects such as writing to and reading from files in some way or another – usually all very carefully marked as impure.
Functional programming can have a reputation for being abstruse, and for favoring elegance or concision over practicality. Large companies rarely rely on functional-first languages at scale, or at least do so on a smaller level than other languages like C++, Java, or Python. FP, however, is really just a framework for thinking about logical flows, with its upsides and downsides, and it is composable with other paradigms.
What does Python support?
Though Python is not primarily a functional language, it is able to support functional programming relatively easily because everything in Python is an object. That means that function definitions can be assigned to variables and passed around.
def add(a, b):
return a + b
plus = add
plus(3, 4) # returns 7