Like many popular programming languages, Python tends to fall into several programming paradigms. From functional to object-oriented, Python features a little bit of everything. As a result, I recommend beginners treat Python like an imperative programming language.
Of course, if you’re truly a beginner, you know that everything I just said is a bunch of jargon. That’s why I’ll take a large portion of this article to outline some of the major programming paradigms before ultimately making the case for sticking to imperative programming. Don’t worry! We’ll see a little bit of everything as this series continues.
Table of Contents
Exploring Programming Paradigms
In general, there are only a handful of fundamentally different ways to approach programming problems. We call these approaches paradigms, and they guide the design of programming languages. For example, Lisp is considered a functional programming language while C is considered a procedural programming language. In the remainder of this section, we’ll look at a few of these paradigms in more detail.
For a programming language to be considered imperative, it has to meet two basic criteria:
- It must allow the user to define the order of instructions that will be executed (see: algorithmic thinking)
- It must allow the user to track state through the use of variables or other constructs
For example, the algorithm for long division would be considered imperative programming. After all, there’s a clear set of steps followed by the tracking of information at each step.
Seeing as this is the way a lot of folks think about algorithms, imperative programming tends to be a natural way to learn how to code. As a result, most of the popular languages today support imperative programming including all of the following:
Since this is such a common form of programming, a lot of folks have a hard time imagining other paradigms. Fortunately, we’ll take a look at declarative programming next.
In most definitions of declarative programming, it’s described as everything that imperative programming is not. For our purposes, I’ll define declarative programming as a paradigm in which the code describes what a program should do but not how it should do it.
A common example of declarative programming is HTML which is a language for creating the structure of a web page—and yes, despite what you might have heard, HTML is a programming language. As a result, this language doesn’t tell the browser how to render the web page but rather what the web page should look like. For example, here’s this paragraph in HTML:
<p>A common example of declarative programming is HTML which is a language for creating the structure of a web page. As a result, this language doesn’t tell the browser <strong>how</strong> to render the web page but rather <strong>what</strong> the web page should look like. For example, here’s this paragraph in HTML:</p>
Here, we can see that the paragraph is surrounded in `<p>`html tags. Likewise, the bold parts of the paragraph are surrounded in `<strong>`html tags. Otherwise, all the text is as it appears.
Nowhere in this code snippet is there a program to draw the text on screen. Likewise, there’s no code for handing bold text differently from standard text. Instead, the entire structure of the paragraph is just declared. It’s up to the browser to decide how to render it.
All that said, there are programming languages that still qualify as declarative despite being drastically different from HTML. That’s because one of the criteria for being a declarative language is being stateless (i.e. no mutable variables). This includes a plethora of languages that subscribe to the ideas of functional programming—more on that later.
At any rate, imperative and declarative programming tend to form the two core paradigms. Despite being opposites, many modern programming languages manage to include features of both. For example, here’s a list of a few popular languages that include declarative features—pay attention to how many are in both lists:
From here, every other paradigm falls into one of these buckets.
If we take this idea of imperative programming a step further, we can actually begin to organize our ideas into procedures—just like we did when we talked about algorithmic thinking.
Without procedures, we tend to have to write steps repeatedly. This can be a bit annoying without a way to jump around our list of steps (e.g. go back to step 2). Even with the ability to jump around, our list of steps quickly becomes a nightmare to maintain.
Instead, what we can do is group steps into procedures. For example, we might have an algorithm for making a PB&J sandwich. For whatever reason, this algorithm has dozens of steps from how to assemble a sandwich all the way down to where to buy peanut butter. Given enough detail, it might even tell us which hand should hold the knife. Rather than writing all these steps in order, we can break out the different pieces into procedures (e.g. open_jar, spread, assemble_sandwich, etc.).
Naturally, some procedures are pieces of other procedures. For example, `open_jar` is useful for both peanut butter and jelly. As a result, we’ll start to develop a hierarchy. Eventually, we’ll assemble an entire algorithm from a small set of top-level procedures.
In other words, the benefit of adding procedures to an imperative programming language is abstraction—a common theme in this series. Now, algorithms aren’t just a series of steps but rather a hierarchy of procedures.
Naturally, the majority of modern programming languages include some form of procedural programming. For instance, here’s a list of programming languages that let you create procedures:
Of course, as it turns out, procedural programming is only the tip of the iceberg in terms of imperative programming. In the next section, we’ll look at another subset of imperative programming called object-oriented programming.
Like procedural programming, object-oriented programming (OOP) stems from imperative programming. However, OOP takes the idea of procedures a step further by also linking them to their state.
Personally, I think this definition is a bit confusing, so I find it helpful to talk about the paradigm a bit more broadly. In other words, OOP allows the user to construct blueprints. For example, we might use OOP to model a real world object like a car or a person.
The benefit of this paradigm is that we can now construct several cars with slightly different parameters. For example, each car might have a different set of tires or a different paint job. Then, we can interact with these cars separately. For instance, some of the cars might be driving north while other cars are parked.
Another benefit of this paradigm is that we can now relate data through hierarchy. In other words, our car example above can be expanded so that we have several types of cars that all borrow from the main car object. For example, we might want a luxury car which is still a car, but it has certain feel to it.
Without the notion of objects, it can become really hard to abstract a larger system. After all, in a procedural language, the only mechanism for modeling a system would be a procedure. In other words, all data management would have to be made through procedures. It could be done, but I believe it would be harder to conceptualize.
Today, many popular programming languages contain elements of OOP including:
As it turns out, OOP is about as far as imperative programming has gone in terms of introducing abstractions. After all, procedures allow us to organize instructions while objects allow us to organize data. What else is there left to do? Well, we could abandon imperative programming altogether and pivot to an interesting paradigm known functional programming.
The very last programming paradigm I want to chat about today is functional programming (FP). I saved it for last because it’s very interesting, and I find it pretty difficult to grasp—especially for folks who are used to the imperative paradigm.
At it’s core, FP is a stateless programming paradigm. In other words, there are no statements, which are lines of code that modify state. Instead, everything is an expression, which is a piece of that code that returns a value. If you’re interested in learning more about the distinction between statements and expressions, I wrote an article about that awhile back.
At any rate, the advantage of this type of design is that it’s relatively straightforward to reason about. After all, expressions don’t care about context. They can be evaluated directly. There are no worries about expressions modifying that context. Everything is predictable. For example, the following expression has all the information I need to evaluate it: `(5 + (2 * 6))`. In other words, 5 doesn’t derive it’s value from some previous calculation or—heaven forbid—the weather.
On the flip side, in imperative programming, every statement depends on what came before it. As a result, it can be hard to reason about what a statement is doing without knowing the context. In some cases, the context is so complex that it can be difficult to anticipate errors. For example, I can’t just start in the fifth step of a recipe without knowing the context; I have to start from the beginning.
As always, there is a bit of a drawback with FP. First, it relies heavily on recursion which is a problem solving technique that relies on the ability to break problems down into smaller problems. As a result, it can sometimes be challenging to come up with a recursive solution where an iterative one would be more straightforward. That said, this issue is often mitigated by writing functions that abstract recursion.
That said, a lot of the aspects of FP are trendy again, so they’ve made their way into many popular programming languages including:
With that said, we’ve covered all of the major programming paradigms. In the next section, we’ll finally find out which paradigm Python falls into.
What Is Python’s Paradigm?
Now that we’ve taken the opportunity to explore several different programming paradigms, I’m sure there’s a question on your mind: what is Python’s paradigm?
Of course, there’s a good chance you already know the answer to this question. After all, Python managed to show up in every paradigm list—talk about an identity crisis!
To make matters worse, if you hop over to Wikipedia, you’ll see Python listed as “multi-paradigm”. For better or for worse, this is pretty common for popular programming languages. It seems that as trends change in development, languages adapt. As a result, you end up with programming languages that support some mix of the following:
- Higher-Order Functions
- And so on!
To a purist, this is disgusting. After all, they’d argue that programming languages should adhere to one and only one paradigm. Yet, practically all modern programming languages mix and match paradigms to their disdain: the real world is far more messy than they’d like.
In some respects, I’d agree with that. Having a language like Python that supports so many paradigms can be confusing and can result in some pretty messy code. Also, as an educator, I don’t find it very helpful to expose students to several paradigms at the same time. With languages like Python, this can be a difficult topic to avoid. So, what do we do?
Well, for the early portion of this series, we’re going to treat Python as an imperative programming language. In other words, we’re not going to mess with some of the procedural, object-oriented, or functional features of Python—at least for now. As a result, we’ll be able to map most of what we discussed in our algorithmic thinking article to what we’ll discuss next.
But first, let’s tease a little more code!
Imperative Programming in Python
Remember when we opened up IDLE to explore the tool a bit? Let’s do that again:
Personally, I think IDLE is a great tool for exploring imperative programming (e.g. statements). After all, every time we hit enter, IDLE interprets a statement (imperative) or evaluates an expression (functional).
For example, Python can interpret statements such as printing “Hello, World”:
>>> print("Hello, World") Hello, World
On the flip side, Python can also evaluate expressions such as numbers:
>>> 5 5
Of course, seeing five evaluate to itself isn’t all that interesting. Certainly, Python can also handle basic arithmetic expressions like addition:
>>> 5 + 4 9
If we were particularly daring, we might mix our statements and expressions by assigning the result of some arithmetic to a variable:
>>> x = 5 + 4
Notice how nothing prints to the console. Beyond calling `print()`python, statements won’t print to the console. Expressions, on the other hand, will. This isn’t a universal feature of Python, but it’s a nice distinction by IDLE. It’ll help us spot the difference between statements and expressions—as well as other constructs in the futures.
At any rate, now that we have a variable, we can evaluate it by calling it directly:
>>> x 9
Keep in mind that the value of `x` depends entirely on what led up to this point. In other words, there’s no way for us to know what value `x` stores without working backwards to see how `x` was created. In imperative programming, we have to follow the steps.
Now, I’d encourage you to take what little we’ve explored up to this point to experiment. For instance, try subtraction. See if you can figure out what operator performs division. Maybe play around with other mathematical operators like equals and greater than. The sky’s the limit.
Alternatively, if you want something more formal to guide you through number exploration, I have a lab you might enjoy. Just head on over to Patreon and hop on the For Honor! tier. Otherwise, next time we’ll expand on this idea of variables and take a deep dive into data types!
While you’re here, I’d appreciate it if you took some time to explore my list of ways to grow the site. There, you’ll find tons of fun stuff including links to my newsletter and YouTube channel.
Likewise, if you’re stuck waiting on the next article in the series (or just want to explore other content), take a look at these related articles:
In addition, here are some resources from the folks at Amazon (ad):
- Effective Python: 90 Specific Ways to Write Better Python
- Python Tricks: A Buffet of Awesome Python Features
- Python Programming: An Introduction to Computer Science
Beyond that, I don’t have much to plug. Just stay safe, and take care! Hopefully, I’ll see you back here soon.
Life has given me a bit of a beating, so I'm taking some time to recover. See y'all again soon.
Why Is Adding Two Random Numbers Not the Same as Generating One in the Same Range?
Generating random numbers might seem easy at first, but there are definitely some pitfalls.