In this chapter, we will work on implementing a case study by applying the metaprogramming concepts that we have learned so far. For this case study, we will be using the Automobile. (1987). UCI Machine Learning Repository dataset.
In this chapter, we will be looking at the following main topics:
By the end of this chapter, you should have an understanding of how to use the existing methods of the ast library in Python to enable your application to generate its own code.
The code examples shared in this chapter are available on GitHub at: https://github.com/PacktPublishing/Metaprogramming-with-Python/tree/main/Chapter15.
In this section, we will be looking at the details of the case study before we start implementing it. Let’s consider a car agency, ABC Car Agency, that focuses on sales of new and used cars from multiple brands. This agency would like to build an application that produces customized catalogs for each car displaying the various specifications and features of the car.
We will look at the details available to develop and build the application by applying the concepts that we have learned throughout this book. There are 205 different cars that need to be cataloged and the data used to build this case study is taken from the following dataset: Automobile. (1987). UCI Machine Learning Repository.
There are many ways to develop an application that can solve this problem. We are going to look at how to develop a reusable application that uses metaprogramming.
A high-level view of the automobile data is as follows:
Figure 15.1 – The Automobile. (1987). UCI Machine Learning Repository dataset
For this case study, we are not going to perform any detailed data processing using the automobile dataset. Instead, we will be using the data available in this dataset to create various components for the application’s development. The flow of design for this example will start with developing a code generator library, followed by creating a code generator framework. We will then generate the ABC Car Agency library and, finally, create an execution framework. All of these processes will be explained in detail in this section.
The Python scripts that will be developed for this case study will be as follows:
Figure 15.2 – Python scripts for the ABC Car Agency case study
The car sales application will be developed by defining the following classes:
Each of these classes will be explained in this section.
The overall structure of classes for this application is going to look as follows:
Figure 15.3 – Overview of the car sales application
With this understood, we will look further at the base classes for the application.
We will now start building the code required for the case study.
Let’s start by developing a metaclass named CarSpecs. This class will have the following structure:
Let’s now look at the definition of CarSpecs:
from abc import ABC, abstractmethod
class CarSpecs(type):
def __new__(classitself, classname, baseclasses, attributes):
newattributes = {}
for attribute, value in attributes.items():
if attribute.startswith("__"):
newattributes[attribute] = value
elif type(value)==int or type(value)==float:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = str(value)
newattributes[attribute]['type'] = 'NUMERIC'
elif type(value)==str:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = value.title()
newattributes[attribute]['type'] = 'VARCHAR'
elif type(value)==bool:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = value.title()
newattributes[attribute]['type'] = 'BOOLEAN'
else:
newattributes[attribute] = value
return type.__new__(classitself, classname, baseclasses, newattributes)
class CarCatalogue(metaclass = CarSpecs):
@abstractmethod
def define_color(self):
pass
@abstractmethod
def print_catalogue(self):
pass
class CarMake(metaclass = CarSpecs):
@abstractmethod
def define_spec(self):
pass
class BodyStyle(metaclass = CarSpecs):
@abstractmethod
def body_style_features(self):
pass
class SaleType(metaclass = CarSpecs):
@abstractmethod
def calculate_price(self):
pass
class New(SaleType, CarCatalogue, metaclass = CarSpecs):
def calculate_price(self, classname):
car = classname()
price = float(car.price['info'])
return price
class Resale(SaleType, CarCatalogue, metaclass = CarSpecs):
def calculate_price(self, classname, years):
car = classname()
depreciation = years * 0.15
price = float(car.price['info']) * (1 - depreciation)
return price
These are the main classes for which we will be creating templates that will be used to generate code in the next section.
In this section, let’s look at developing a code generator that will be used to generate code for all the base classes – CarSpecs, CarMake, CarCatalogue, BodyStyle, and SaleType. The detailed steps are as follows:
class CodeGenerator:
def generate_meta(self):
ast = __import__('ast')
meta_template = '''
from abc import ABC, abstractmethod, ABCMeta
class CarSpecs(type, metaclass = ABCMeta):
def __new__(classitself, classname, baseclasses, attributes):
newattributes = {}
for attribute, value in attributes.items():
if attribute.startswith("__"):
newattributes[attribute] = value
elif type(value)==int or type(value)==float:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = str(value)
newattributes[attribute]['type'] = 'NUMERIC'
elif type(value)==str:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = value.title()
newattributes[attribute]['type'] = 'VARCHAR'
elif type(value)==bool:
newattributes[attribute] = {}
newattributes[attribute]['feature'] = attribute.title().replace('_', ' ')
newattributes[attribute]['info'] = value.title()
newattributes[attribute]['type'] = 'BOOLEAN'
else:
newattributes[attribute] = value
return type.__new__(classitself, classname, baseclasses, newattributes)
'''
meta_tree = ast.parse(meta_template)
print(ast.unparse(meta_tree))
print(' ')
def generate_car_catalogue(self):
ast = __import__('ast')
catalogue_template = '''
class CarCatalogue(metaclass = CarSpecs):
@abstractmethod
def define_color(self):
pass
@abstractmethod
def print_catalogue(self):
pass
'''
catalogue_tree = ast.parse(catalogue_template)
print(ast.unparse(catalogue_tree))
print(' ')
def generate_carmake_code(self):
ast = __import__('ast')
carmake_template = '''
class CarMake(metaclass = CarSpecs):
@abstractmethod
def define_spec(self):
pass
'''
carmake_tree = ast.parse(carmake_template)
print(ast.unparse(carmake_tree))
print(' ')
def generate_bodystyle_parent(self):
ast = __import__('ast')
bodystyle_parent_template = '''
class BodyStyle(metaclass = CarSpecs):
@abstractmethod
def body_style_features(self):
pass
'''
bodystyle_parent_tree = ast.parse(bodystyle_parent_template)
print(ast.unparse(bodystyle_parent_tree))
print(' ')
def generate_salestype_code(self):
ast = __import__('ast')
saletype_template = '''
class SaleType(metaclass = CarSpecs):
@abstractmethod
def calculate_price(self):
pass
'''
salestype_tree = ast.parse(saletype_template)
print(ast.unparse(salestype_tree))
print(' ')
def generate_newsale_code(self):
ast = __import__('ast')
newsale_template = '''
class New(SaleType, CarCatalogue, metaclass = CarSpecs):
def calculate_price(self, classname):
car = classname()
price = float(car.price['info'])
return price
'''
newsale_tree = ast.parse(newsale_template)
print(ast.unparse(newsale_tree))
print(' ')
def generate_resale_code(self):
ast = __import__('ast')
resale_template = '''
class Resale(SaleType, CarCatalogue, metaclass = CarSpecs):
def calculate_price(self, classname, years):
car = classname()
depreciation = years * 0.15
price = float(car.price['info']) * (1 - depreciation)
return price
'''
resale_tree = ast.parse(resale_template)
print(ast.unparse(resale_tree))
print(' ')
def generate_car_code(self, classname, carspecs):
self.classname = classname
self.carspecs = carspecs
ast = __import__('ast')
car_template = '''
class '''+self.classname+'''(CarMake, CarCatalogue, metaclass = CarSpecs):
fuel_type = '''+"'"+self.carspecs['fuel_type']+"'"+'''
aspiration = '''+"'"+self.carspecs['aspiration']+"'"+'''
num_of_door = '''+"'"+self.carspecs['num_of_door']+"'"+'''
drive_wheels = '''+"'"+self.carspecs['drive_wheels']+"'"+'''
wheel_base = '''+"'"+self.carspecs['wheel_base']+"'"+'''
length = '''+"'"+self.carspecs['length']+"'"+'''
width = '''+"'"+self.carspecs['width']+"'"+'''
height = '''+"'"+self.carspecs['height']+"'"+'''
curb_weight = '''+"'"+self.carspecs['curb_weight']+"'"+'''
fuel_system = '''+"'"+self.carspecs['fuel_system']+"'"+'''
city_mpg = '''+"'"+self.carspecs['city_mpg']+"'"+'''
highway_mpg = '''+"'"+self.carspecs['highway_mpg']+"'"+'''
price = '''+"'"+self.carspecs['price']+"'"+'''
def define_color(self):
BOLD = '33[5m'
BLUE = ' 33[94m'
return BOLD + BLUE
def define_spec(self):
specs = [self.fuel_type, self.aspiration, self.num_of_door, self.drive_wheels,
self.wheel_base, self.length, self.width, self.height, self.curb_weight,
self.fuel_system, self.city_mpg, self.highway_mpg]
return specs
def print_catalogue(self):
for i in self.define_spec():
print(self.define_color() + i['feature'], ": ", self.define_color() + i['info'])
'''
car_tree = ast.parse(car_template)
print(ast.unparse(car_tree))
print(' ')
def generate_bodystyle_code(self, classname, carfeatures):
self.classname = classname
self.carfeatures = carfeatures
ast = __import__('ast')
bodystyle_template = '''
class '''+self.classname+'''(BodyStyle, CarCatalogue, metaclass = CarSpecs):
engine_location = '''+"'"+self.carfeatures['engine_location']+"'"+'''
engine_type = '''+"'"+self.carfeatures['engine_type']+"'"+'''
num_of_cylinders = '''+"'"+self.carfeatures['num_of_cylinders']+"'"+'''
engine_size = '''+"'"+self.carfeatures['engine_size']+"'"+'''
bore = '''+"'"+self.carfeatures['bore']+"'"+'''
stroke = '''+"'"+self.carfeatures['stroke']+"'"+'''
compression_ratio = '''+"'"+self.carfeatures['compression_ratio']+"'"+'''
horse_power = '''+"'"+self.carfeatures['horse_power']+"'"+'''
peak_rpm = '''+"'"+self.carfeatures['peak_rpm']+"'"+'''
def body_style_features(self):
features = [self.engine_location, self.engine_type, self.num_of_cylinders, self.engine_size,
self.bore, self.stroke, self.compression_ratio, self.horse_power, self.peak_rpm]
return features
def define_color(self):
BOLD = '33[5m'
RED = ' 33[31m'
return BOLD + RED
def print_catalogue(self):
for i in self.body_style_features():
print(self.define_color() + i['feature'], ": ", self.define_color() + i['info'])
'''
bodystyle_tree = ast.parse(bodystyle_template)
print(ast.unparse(bodystyle_tree))
print(' ')
With these methods, we are all set to generate the code required for the ABC Car Agency’s catalog.
Now, let’s proceed further to develop a code generation framework that generates the hundreds of classes required for our application.
In this section, we are going to make use of codegenerator.py to generate the base classes and its corresponding subclasses, which maintain and print various catalogs for the ABC Car Agency, as follows:
import pandas as pd
auto = pd.read_csv("automobile.csv")
auto_truncated = auto.copy(deep=True)
auto_truncated.drop_duplicates(subset = ['make','body-style'], inplace = True)
auto_truncated.reset_index(inplace = True, drop = True)
auto_truncated['make'] = auto_truncated['make'].apply(lambda x: x.title().replace('-',''))
auto_truncated.reset_index(inplace = True)
auto_truncated['index'] = auto_truncated['index'].astype('str')
auto_truncated['make'] = auto_truncated['make'] + auto_truncated['index']
auto_truncated['body-style'] = auto_truncated['body-style'].apply(lambda x: x.title().replace('-',''))
auto_truncated['body-style'] = auto_truncated['body-style'] + auto_truncated['index']
Once the basic data has been processed, let’s create two DataFrames that will be used to generate multiple classes using the code generator:
auto_specs = auto_truncated[['make', 'fuel-type', 'aspiration', 'num-of-doors', 'drive-wheels', 'wheel-base', 'length', 'width', 'height', 'curb-weight', 'fuel-system', 'city-mpg', 'highway-mpg', 'price']].copy(deep = True)
auto_specs.columns = ['classname', 'fuel_type', 'aspiration', 'num_of_door', 'drive_wheels', 'wheel_base', 'length', 'width', 'height', 'curb_weight', 'fuel_system', 'city_mpg', 'highway_mpg', 'price' ]
for col in auto_specs.columns:
auto_specs[col] = auto_specs[col].astype('str')
auto_features = auto_truncated[['body-style', 'engine-location', 'engine-type', 'num-of-cylinders', 'engine-size', 'bore', 'stroke', 'compression-ratio', 'horsepower', 'peak-rpm']].copy(deep = True)
auto_features.columns = ['classname', 'engine_location', 'engine_type', 'num_of_cylinders', 'engine_size', 'bore', 'stroke', 'compression_ratio', 'horse_power', 'peak_rpm']
for col in auto_features.columns:
auto_features[col] = auto_features[col].astype('str')
Figure 15.4 – Sample specifications
Figure 15.5 – Sample features
from codegenerator import CodeGenerator
codegen = CodeGenerator()
def generatelib():
codegen.generate_meta()
codegen.generate_car_catalogue()
codegen.generate_carmake_code()
codegen.generate_bodystyle_parent()
codegen.generate_salestype_code()
codegen.generate_newsale_code()
codegen.generate_resale_code()
for index, row in auto_specs.iterrows():
carspecs = dict(row)
classname = carspecs['classname']
del carspecs['classname']
codegen.generate_car_code(classname = classname, carspecs = carspecs)
for index, row in auto_features.iterrows():
carfeatures = dict(row)
classname = carfeatures['classname']
del carfeatures['classname']
codegen.generate_bodystyle_code(classname = classname, carfeatures = carfeatures)
from contextlib import redirect_stdout
with open('abccaragencylib.py', 'w') as code:
with redirect_stdout(code):
generatelib()
code.close()
Figure 15.6 – An autogenerated class code for a car brand
We have not autogenerated the code required for this example. We will now look at designing an execution framework.
In this section, let’s look at the last process of designing the ABC Car Agency application where we will actually run the code generated throughout this case study:
import abccaragencylib as carsales
class Queue:
def __init__(self, makeclass, styleclass, age):
self.makeclass = makeclass
self.styleclass = styleclass
self.make = self.makeclass()
self.style = self.styleclass()
self.new = carsales.New()
self.resale = carsales.Resale()
self.age = age
def pipeline(self):
print('*********ABC Car Agency - Catalogue***********')
self.make.print_catalogue()
print(' ')
self.style.print_catalogue()
print(' ')
print('New Car Price : ' + str(self.new.calculate_price(self.makeclass)))
print('Resale Price : ' + str(self.resale.calculate_price(self.makeclass, self.age)))
def run_facade(makeclass, styleclass, age):
queue = Queue(makeclass, styleclass, age)
queue.pipeline()
run_facade(carsales.AlfaRomero1, carsales.Hatchback28, 3)
The output is as follows:
*********ABC Car Agency - Catalogue***********
Fuel Type : Gas
Aspiration : Std
Num Of Door : Two
Drive Wheels : Rwd
Wheel Base : 94.5
Length : 171.2
Width : 65.5
Height : 52.4
Curb Weight : 2823
Fuel System : Mpfi
City Mpg : 19
Highway Mpg : 26
Engine Location : Front
Engine Type : Ohc
Num Of Cylinders : Four
Engine Size : 97
Bore : 3.15
Stroke : 3.29
Compression Ratio : 9.4
Horse Power : 69
Peak Rpm : 5200
New Car Price : 16500.0
Resale Price : 9075.0
There are 56 unique subclasses generated for CarMake and 56 unique subclasses generated for BodyStyle. We can use various combinations of CarMake and BodyStyle to print catalogs for this application.
The output generated is as follows:
*********ABC Car Agency - Catalogue***********
Fuel Type : Gas
Aspiration : Std
Num Of Door : Two
Drive Wheels : Fwd
Wheel Base : 93.7
Length : 157.3
Width : 64.4
Height : 50.8
Curb Weight : 1918
Fuel System : 2Bbl
City Mpg : 37
Highway Mpg : 41
Engine Location : Front
Engine Type : Dohc
Num Of Cylinders : Six
Engine Size : 258
Bore : 3.63
Stroke : 4.17
Compression Ratio : 8.1
Horse Power : 176
Peak Rpm : 4750
New Car Price : 5389.0
Resale Price : 1347.25
This is the step-by-step process of developing an application by applying metaprogramming methodologies in Python.
In this chapter, we have learned how to develop an application by applying various techniques of metaprogramming. We started by explaining the case study, and we defined the base classes required for this case study.
We also learned how to develop a code generator and how to generate code using it. We also designed a framework that could be used to execute or test the code generated for the application in this case study.
In the next chapter, we will be looking at some of the best practices that can be followed while designing an application with Python and metaprogramming.
3.142.119.114