Understanding Code Reuse and Modularity in Python 3



What is Object Oriented Programming (OOP)?

OOP — it is a programming paradigm based on the concept of “objects” that can contain data in the form of fields, often called attributes; and code in the form of procedures, often called methods. Find out more here or simply Google “OOP”.

Objects have characteristics and features, known as attributes, and can do various things with their methods. The biggest feature of OOP — it is how well objects can interact and even shape in the future, making them very developer friendly, scalable, altered over time, tested, and more.

What is modularity?

Modularity refers to the concept of creating multiple modules and then combining them and combining them into a single system (i.e. the degree to which a software / web application can be split into smaller modules is called modularity). Modularity ensures reusability and minimizes duplication.

Article flow

Purpose: to learn object-oriented programming — modularity. How can we turn some parts of our code into a library so that it can be used by someone else for further use. Creating modular code will ensure reusability and minimize duplication.

Dependencies: pygame

Summary: we`re going to make a little game ( not really a game), but just the environment and some objects in it. We will try to make the environment static and the objects (in our case, blobs) modular. We will use PyGame as it gives us an easy way to actually render what we are doing and building so that we can see our objects in action. What we`re going to do is build a Blob World, which is made up of actors known as blobs. Different blobs have different properties, and otherwise blobs must function in their Blob World environment. With this example, we can illustrate modularity. 
We divide our learning process into two phases.

  1. Creating environments and blobs
  2. Understanding modularity

Repository (Github): source

BLOB WORLD (Python code)

import pygame

import random

 

STARTING_BLUE_BLOBS = 10

STARTING_RED_BLOBS = 3

 

WIDTH = 800 

HEIGHT = 600

WHITE = ( 255 , 255 , 255 )

BLUE = ( 0 , 0 , 255 )

RED = ( 255 , 0 , 0 )

 

 game_display = pygame.display.set_mode ((WIDTH, HEIGHT))

pygame.display.set_caption ( "Blob World" )

clock = pygame.time.Clock ()

 

class Blob:

 

def __ init __ ( self , color):

self . x = random.randrange ( 0 , WIDTH)

  self . y = random.randrange ( 0 , HEIGHT)

self . size = random.randrange ( 4 , 8 )

self . color = color

 

def move ( self ):

  self . move_x = random.randrange ( - 1 , 2 )

self . move_y = random.randrange ( - 1 , 2 )

self . x + = self . move_x

self . y + = se lf . move_y

 

if self . x & lt;  0 : self .x = 0

elif self . x & gt; WIDTH: self . X = WIDTH

 

if self . y & lt;  0 : self .y = 0

elif self . y & gt; HEIGHT: self . Y = HEIGHT

 

 

def draw_environment (blob_list):

game_display.fill (WHITE)

  

for blob_dict in blob_list:

for blob_id in blob_dict:

blob = blob_dict [blob_id]

pygame.draw.circle (game_display, blob.color, [blob.x, blob.y ], blob.size)

blob.move ()

 

pygame.display.update ()

 

 

def main ():

blue_blobs = dict ( enumerate ([Blob (BLUE) for i in range (STARTING_BLUE_BLOBS)]) )

  red_blobs = dict ( enumerate ([Blob (RED) for i in range (STARTING_RED_BLOBS)]))

while True :

for event in pygame.event.get ():

if event. type = = pygame.QUIT:

pygame.quit ()

quit ()

draw_environment ([blue_blobs, red_blobs])

  clock.tick ( 60 )

 

if __ name__ = = `__main__` :

  main ()

Exit:


PART (1/2 ): Blob World In this part we are creating a simple game environment and some objects in it, because visualizing what we have created is an exceptional way to learn programming. An explanation of creating a blob world (i.e. its environment and objects) using Pygame is explained by here . All we need to understand is how to make our code modular.

PART (2/2): Modularity In this second part, we are going to understand the essential feature of object-oriented programming, that is, modularity. So far we have not provided anything that would do this ( code BLOB WORLD) too difficult to maintain or scale over time, at least in terms of what we can do with PyGame. How about making it modular? There is a very simple test for this, let`s try to import it! 
For this we will have two files. Let`s copy the Blob and random class and create a new file: blob.py

 import random class Blob: def __init __ (self, color): self.x = random.randrange (0, WIDTH) self.y = random.randrange (0, HEIGHT) self.size = random.randrange (4,8) self.color = color def move (self): self.move_x = random.randrange (-1,2) self.move_y = random.randrange (-1,2) self.x + = self.move_x self.y + = self.move_y if self.x WIDTH: self.x = WIDTH if self.y HEIGHT: self.y = HEIGHT 

Back to our original file, let`s remove the Blob class and then import Blob from blob.py.

 import pygame import random from blob import Blob STARTING_BLUE_BLOBS = 10 ... 

Immediately we got an error in the blob.py file regarding our Blob class, where we have several undefined belt. This is definitely a problem with writing classes, we should try to avoid using constants or variables outside of the class. Let`s add these values ​​to the __init__ method and then change all the parts where we used constants. 
So here`s our new Blob class file: blob.py
Next, in our source file, when we call the Blob class, it expects some values ​​for these arguments, so you add them to the main function:

 def main (): blue_blobs = dict (enumerate ( [Blob (BLUE, WIDTH, HEIGHT) for i in range (STARTING_BLUE_BLOBS)])) red_blobs = dict (enumerate ([Blob (RED, WIDTH, HEIGHT) for i in range (STARTING_RED_BLOBS)])) while True: ... 

Great, now our Blob class can at least be imported, so it`s already modular! Another good idea — try to give the developer using your code as much power as possible and make your class as generic as possible. At least one example where we can definitely give more to a programmer using this class is in blob sizing:

 self.size = random.randrange (4,8) 

Is there any reason why we wouldn`t want to give the programmer an easy way to change this? I do not think so. However, unlike x_boundary and y_boundary, we don`t * need * a programmer to provide us with a size value, since we can at least use a reasonable initial default. So we can do something like:

 class Blob: def __init __ (self, color, x_boundary, y_boundary, size_range = (4,8)): self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange (0, self.x_boundary) self.y = random.randrange (0, self.y_boundary) self.size = random.randrange (size_range [0], size_range [1]) self.color = color 

Now if the programmer wants to change the size, he can, otherwise he shouldn`t. We can also let the programmer change the blob speed if they want:

 import random class Blob: def __init __ (self, color, x_boundary, y_boundary, size_range = (4,8), movement_range = (- 1,2)): self.size = random.randrange (size_range [0], size_range [1]) self.color = color self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random.randrange (0, self .x_boundary) self.y = random.randrange (0, self.y_boundary) self.movement_range = movement_range def move (self): self.move_x = random.randrange (self.movement_range [0], self.movement_range [1]) self.move_y = random.randrange (self.movement_range [0], self.movement_range [1]) self.x + = self.move_x self.y + = self.move_y if self.x self.x_boundary: self.x = self.x_boundary if self.y self.y_boundary: self.y = self.y_boundary 

Now we have opened the class a bit. Anything else popping up with us? Yes, the line is where we force the blob to stay within the bounds. Could there be examples where we would like the droplets to be able to move freely out of sight? Sure! Is this restrictive code helpful? Is it possible that programmers will want to use this quite often? Sure! however, it makes sense to either have no code at all or provide it with its own method, like this:

 import random class Blob: def __init __ (self, color, x_boundary, y_boundary, size_range = (4.8 ), movement_range = (- 1,2)): self.size = random.randrange (size_range [0], size_range [1]) self.color = color self.x_boundary = x_boundary self.y_boundary = y_boundary self.x = random .randrange (0, self.x_boundary) self.y = random.randrange (0, self.y_boundary) self.movement_range = movement_range def move (self): self.move_x = random.randrange (self.movement_range [0], self .movement_range [1]) self.move_y = random.randrange (self.movement_range [0], self.movement_range [1]) self.x + = self.move_x self.y + = self.move_y def check_bounds (self): if self.x self.x_boundary: self.x = self.x_boundary if self.y self.y_boundary: self.y = self.y_boundary 

Now the programmer can decide whether to use it or not. You can also give some argument in the move method, where if True then the borders will be applied. 
This gives us an idea of ​​how we can make our Python code modular.

Resources :

This article courtesy of Amartya Ranjan Saikia . If you are as Python.Engineering and would like to contribute, you can also write an article using contribute.python.engineering or by posting an article contribute @ python.engineering. See my article appearing on the Python.Engineering homepage and help other geeks.

Please post comments if you find anything wrong or if you would like to share more information on the topic discussed above.