Modular and reusable code is easier to maintain and scale. It allows you to reuse code across different parts of your application, reducing the amount of code you need to write. To write modular code, break your application into smaller, more manageable components. This is especially important in large projects with a lot of moving parts.

A good grasp of object oriented programming can be highly beneficial to writing clean code. The following paragraphs elucidate some basic concepts of writing modular code.

Functions

Functions are chunks of code that are called repeatedly. They normally comprise of a function name, and take in several parameters that is used within its own scope.

Classes and Objects

Classes in python are a step ahead of functions. Classes can have their own data members (variables) that can be accessed only within the class or through an instance of the class. Let us understand classes through an example.

class Robot:
	dt = 0.01
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def update(self, vx, vy):
		self.x += vx * self.dt
		self.y += vy * self.dt

if __name__ == "__main__":
	robot = Robot(0, 0)
	for i in range(100):
		robot.update(0, 10)

In the above example, we defined a class called Robot Before the class can be used, we must first create an instance of the class. This is done by calling the <classname>(**args) .(Robot(0,0)). Doing this calls the __init__ function of the class for the variable robot with the arguments that we provided. The __init__ function can be used to define certain variables when the object is first initialized.

Notice that a function that is defined within the class always has self as the first argument. This is because, whenever we call robot.update or any other function of the class, the instance of the object (robot) is passed as the first parameter. This allows the function to access data members and functions of the class. Also note that self is just a convention. It can be named anything! It just has to be the first argument of a function definition.

Let us now progress to some more concepts used:

Inheritance

class Robot:
	dt = 0.01
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def update(self, vx, vy):
		self.x += vx * self.dt
		self.y += vy * self.dt
	def reset(self):
		self.x = 0
		self.y = 0

class Drone(Robot):
	def __init__(self, setpoint):
		super().__init__()
		self.setpoint = setpoint
		self.z = 10

	def update(self, vx, vy, vz):
		super().update(vx, vy)
		self.z += vz * self.dt

if __name__ == "__main__":
	drone = Drone(10)
	for i in range(100):
		drone.update(0, 10, 0)
	drone.reset()

Inheritance is a concept used when you want to have multiple different types of objects with the same core functionality. In the above code snippet, the class Robot is the base class, and the class Drone inherits from the Robot class. Thus, when we initialize the drone as its own object, it can both access functions and inherit data members from its base class (also called super class). Here, when we instantiate the drone, it calls the init function of the Robot using super().__init__().

You can also see that we have overridden the update method of the base Robot class. When drone.update() is called, we call the update function of the Drone class which in turn calls the update function of the Robot class, and then performs some computation.

Decorators

class MyClass:
    @staticmethod
    def my_static_method():
        print("This is a static method")
if __name__ == "__main__":
	MyClass.my_static_method()