Why Structure Your Code?
As programming projects get more complicated, they become more challenging to understand. As you add more classes, libraries, and functions to your code, it becomes harder to find the problems and which parts of your code are doing what.
When you’re just starting as a developer, this is less of an issue, but the moment you expect someone else to use a project, or you’re working on a team, you need to start coming up with solutions for making your project more comprehensible. You’ll be surprised how hard it is to come back to a project after a couple of months of not working on it!
Structuring your code is essential because it reduces the effort required to maintain and extend it. It ensures that new developers can quickly get up to speed with a project and its internals.
Structuring your code is, ultimately, about making you more efficient.
Here are a few principles to consider when writing your code:
Discoverability
Discoverability is about how easy or hard it is to find out where particular functionality lies. It can also refer to the difficulty in understanding code itself - is it clear what the code does just by looking at it? Or do you have to look elsewhere to find out?
You can ensure discoverability by dividing our code logically, using files and folders to group code with similar functions.
Simplicity
You can also make sure that each section of code has as few responsibilities as possible - preferably only one! Functions and methods that only do one thing are easier to understand and test.
Style
Well-structured code has a coherent style. There should be a style guide in your workplace or open source project. Following it will make sure that your code and the code of your colleagues and collaborators are similar.
Structuring Your Code
The most useful code structuring tools are simply files and folders. Dividing your programs into (appropriately named) files and folders helps with discoverability.
Generally speaking, you want each class in a separate file, and that file to be named the same as the class. If that isn’t feasible or desirable, put a group of logically connected classes in the same file with an appropriate name.
It is also important that classes, functions/methods, and variables are all named logically. The names should tell a story. Consider this function:
def calculation(a, b):
return a * (1-(b/100))
What does it do? Well…
def subtract_discount(price, discount):
return price * (1-(discount/100))
With these names, it's obvious that this subtracts a certain percentage discount from a price.
Class names should be similarly logical. Remember ContactSystem and its subclasses earlier?
![The ContactSystem class and its behavior](https://user.oc-static.com/upload/2020/09/28/16012800067465_P2C1_Why%20Inheritance_static.png)
The names clearly indicated what they were doing and that they were related. You should be using the conceptual understanding you used when designing your classes in the decisions on how to structure your code!
Python Modules and Packages
You should’ve used external libraries in your programs by now, using the import
statement to draw in code from outside your program.
A module is the executable code and the class/function definitions contained within a single Python file. At the same time, a package is a collection of modules that are logically grouped in a directory that share a configuration file (_init__.py
).
This __init__.py can contain any initialization code, and usually contains an __all__
definition that defines all of the modules in the package, like this:
__all__ = [“ContactSystem”, “TextContactSystem”, “EmailContactSystem”]
Putting modules in a package lets you have more logical import statements - particularly useful when you’re providing your code as a library to someone. For example, if the above “all” statement is in a folder called contact
, you could do this:
from contact import *
text = TextContactSystem(“01234 567890”)
from … import *
lets you import everything in a package. You could also import just the text system using:
import contact.TextContactSystem
By structuring your code logically using modules and packages, you can make it easier to navigate and use.
When you import a module, you execute all of the code in it. If all that is in a file is function and class definition, nothing happens, but other code - most usefully import
statements - will run.
A common pattern in Python programs is:
if __name__ == “__main__”:
do_something()
print(“Hello, World”)
This code checks whether or not it has been run directly (if it’s the “main program”). If it instead has been imported, the code doesn’t run! Which means you don’t get random output or unnecessary function calls.
It’s particularly worth noting that you have very little control over the order of code in different imported packages, so you shouldn’t rely on it for anything other than importing packages and configuration.
Your Turn: Structure an Existing Piece of Software
Context:
In the main.py
file you will see that it is full of classes, functions, and other code, all of which are jumbled together into one file.
Instructions:
Split the program into modules and packages, adding __init__.py
files and import statements where needed. Make sure that the program can be run by running a main.py
in the project’s root directory.
Let’s Recap!
A Python file, containing Python code, is known as a module.
A package is a directory of Python modules that contains a __init__.py file.
You can use import statements to include classes and functions from other Python modules and packages.
Now you know the mechanisms by which you structure code, in the next chapter we’ll be talking about the thought processes and design decisions involved in breaking down a problem.