How is Python code organized?
Let's talk a little bit about how Python code is organized. In this section, we'll start going down the rabbit hole a little bit more and introduce more technical names and concepts.
Starting with the basics, how is Python code organized? Of course, you write your code into files. When you save a file with the extension .py
, that file is said to be a Python module.
Note
If you're on Windows or macOS that typically hide file extensions from the user, please make sure you change the configuration so that you can see the complete names of the files. This is not strictly a requirement, but a suggestion.
It would be impractical to save all the code that it is required for software to work within one single file. That solution works for scripts, which are usually not longer than a few hundred lines (and often they are quite shorter than that).
A complete Python application can be made of hundreds of thousands of lines of code, so you will have to scatter it through different modules, which is better, but not nearly good enough. It turns out that even like this, it would still be impractical to work with the code. So Python gives you another structure, called package, which allows you to group modules together. A package is nothing more than a folder, which must contain a special file, __init__.py
, that doesn't need to hold any code but whose presence is required to tell Python that the folder is not just some folder, but it's actually a package (note that as of Python 3.3, the __init__.py
module is not strictly required any more).
As always, an example will make all of this much clearer. I have created an example structure in my book project, and when I type in my console:
$ tree -v example
I get a tree representation of the contents of the ch1/example
folder, which holds the code for the examples of this chapter. Here's what the structure of a really simple application could look like:
example ├── core.py ├── run.py └── util ├── __init__.py ├── db.py ├── math.py └── network.py
You can see that within the root of this example, we have two modules, core.py
and run.py
, and one package: util
. Within core.py
, there may be the core logic of our application. On the other hand, within the run.py
module, we can probably find the logic to start the application. Within the util
package, I expect to find various utility tools, and in fact, we can guess that the modules there are named based on the types of tools they hold: db.py
would hold tools to work with databases, math.py
would, of course, hold mathematical tools (maybe our application deals with financial data), and network.py
would probably hold tools to send/receive data on networks.
As explained before, the __init__.py
file is there just to tell Python that util
is a package and not just a mere folder.
Had this software been organized within modules only, it would have been harder to infer its structure. I put a module only example under the ch1/files_only
folder; see it for yourself:
$ tree -v files_only
This shows us a completely different picture:
files_only/
├── core.py
├── db.py
├── math.py
├── network.py
└── run.py
It is a little harder to guess what each module does, right? Now, consider that this is just a simple example, so you can guess how much harder it would be to understand a real application if we couldn't organize the code in packages and modules.
How do we use modules and packages?
When a developer is writing an application, it is likely that they will need to apply the same piece of logic in different parts of it. For example, when writing a parser for the data that comes from a form that a user can fill in a web page, the application will have to validate whether a certain field is holding a number or not. Regardless of how the logic for this kind of validation is written, it's likely that it will be needed in more than one place.
For example, in a poll application, where the user is asked many questions, it's likely that several of them will require a numeric answer. For example:
- What is your age?
- How many pets do you own?
- How many children do you have?
- How many times have you been married?
It would be very bad practice to copy/paste (or, more properly said: duplicate) the validation logic in every place where we expect a numeric answer. This would violate the don't repeat yourself (DRY) principle, which states that you should never repeat the same piece of code more than once in your application. I feel the need to stress the importance of this principle: you should never repeat the same piece of code more than once in your application (pun intended).
There are several reasons why repeating the same piece of logic can be very bad, the most important ones being:
- There could be a bug in the logic, and therefore, you would have to correct it in every place that the logic is applied.
- You may want to amend the way you carry out the validation, and again you would have to change it in every place it is applied.
- You may forget to fix/amend a piece of logic because you missed it when searching for all its occurrences. This would leave wrong/inconsistent behavior in your application.
- Your code would be longer than needed, for no good reason.
Python is a wonderful language and provides you with all the tools you need to apply all the coding best practices. For this particular example, we need to be able to reuse a piece of code. To be able to reuse a piece of code, we need to have a construct that will hold the code for us so that we can call that construct every time we need to repeat the logic inside it. That construct exists, and it's called a function.
I'm not going too deep into the specifics here, so please just remember that a function is a block of organized, reusable code that is used to perform a task. Functions can assume many forms and names, according to what kind of environment they belong to, but for now this is not important. We'll see the details when we are able to appreciate them, later on, in the book. Functions are the building blocks of modularity in your application, and they are almost indispensable. Unless you're writing a super-simple script, you'll use functions all the time. We'll explore functions in Chapter 4, Functions, the Building Blocks of Code.
Python comes with a very extensive library, as I have already said a few pages ago. Now, maybe it's a good time to define what a library is: a library is a collection of functions and objects that provide functionalities that enrich the abilities of a language.
For example, within Python's math
library, we can find a plethora of functions, one of which is the factorial
function, which of course calculates the factorial of a number.
Note
In mathematics, the factorial of a non-negative integer number N, denoted as N!, is defined as the product of all positive integers less than or equal to N. For example, the factorial of 5
is calculated as:5! = 5 * 4 * 3 * 2 * 1 = 120
The factorial of 0
is 0! = 1
, to respect the convention for an empty product.
So, if you wanted to use this function in your code, all you would have to do is to import it and call it with the right input values. Don't worry too much if input values and the concept of calling is not very clear for now; please just concentrate on the import part. We use a library by importing what we need from it, and then we use it.
In Python, to calculate the factorial of number 5
, we just need the following code:
>>> from math import factorial
>>> factorial(5)
120
Note
Whatever we type in the shell, if it has a printable representation, will be printed on the console for us (in this case, the result of the function call: 120
).
So, let's go back to our example, the one with core.py
, run.py
, util
, and so on.
In our example, the package util
is our utility library. Our custom utility belt that holds all those reusable tools (that is, functions), which we need in our application. Some of them will deal with databases (db.py
), some with the network (network.py
), and some will perform mathematical calculations (math.py
) that are outside the scope of Python's standard math
library and, therefore, we have to code them for ourselves.
We will see in detail how to import functions and use them in their dedicated chapter. Let's now talk about another very important concept: Python's execution model.