Chapter 11: Creating Dynamic Objects

In this chapter, we will look at the concept of dynamic objects in Python 3 and the process that can be followed to create any dynamic Python objects including classes, instances of classes, methods, and attributes.

As the name suggests, dynamic objects are objects that can be created at runtime or execution time rather than while coding, provided certain conditions are met.

Throughout this chapter, we will look at how to create classes, class instances, functions, methods, and attributes dynamically using our core example of ABC Megamart.

Why should we understand the creation of dynamic objects? In scenarios where we want to build applications that can generate code at runtime, the basic building blocks for Python code are the objects that are created at runtime. Dynamic creation of objects gives the flexibility and choice of creating an object only when it is required. Any object defined will occupy a certain amount of memory. When an object created during the coding time is not required by the rest of the code or application, it occupies memory that can otherwise be used more efficiently.

In this chapter, we will be taking a look at the following main topics:

  • Exploring type for dynamic objects
  • Creating multiple instances of a class dynamically
  • Creating dynamic classes
  • Creating dynamic attributes and methods

By the end of this chapter, you should have an understanding of how Python objects can be created at runtime and how they can be implemented in various applications.

Technical requirements

The code examples shared in this chapter are available on GitHub under the code for this chapter at https://github.com/PacktPublishing/Metaprogramming-with-Python/tree/main/Chapter11.

Exploring type for dynamic objects

In this section, let’s explore the function named type from the perspective of dynamic object creation. Why do we need to create an object dynamically? Let’s consider the scenarios where we want to change the attributes of the class only for specific instances/objects of the class and not for the original class itself. In such scenarios, we can create dynamic objects for the class and define the attributes of the class dynamically within the specific dynamic object and not for the whole class itself.

In multiple chapters throughout this book, we have looked at the various uses of the type function. In this chapter, we will look at how to use type to dynamically create Python objects.

Let’s look at the graphical representation of the signature of the type function in Python in the following screenshot:

Figure 11.1 – Signature of type

Figure 11.1 – Signature of type

The type function accepts a self-object followed by a tuple and a dictionary of arguments as input. When we provide an object as input to the type function, it returns the type of the object as in the following example:

type(object)

The output for the type of object is type itself:

type

From Figure 11.1, we can also see that the other variation of type accepts an object followed by bases and dict. The argument value for bases denotes the base classes and the argument value for dict denotes various attributes of the class.

To examine the type function for creating dynamic objects, let’s define a class named Branch:

class Branch:
    '''attributes...'''
    
    '''methods...'''
    pass

Let’s further create an object dynamically using the type function in the following code:

branchAlbany = type('Branch', (object,), {'branchID' : 123, 
                        'branchStreet' : '123 Main Street',
                        'branchCity' : 'Albany',
                        'branchState' : 'New York',
                        '  'branch'ip' : 12084})

In the preceding code, the branchAlbany variable is the object to be defined dynamically, the first argument is the class name for which an object needs to be created, the second argument is the tuple of base classes for the class argument, and the third argument is the list of attributes or methods to be added to the object.

We can look at the definition of the branchAlbany object in the following code and output:

branchAlbany

__main__.Branch

The following screenshot is a representation of the attributes added to branchAlbany once the preceding code is executed:

Figure 11.2 – Attributes of branchAlbany

Figure 11.2 – Attributes of branchAlbany

The method resolution order of the dynamic class instance is the same as the Branch class:

branchAlbany.mro

<function Branch.mro()>

All the dynamic attributes added to the class instance are now part of the branchAlbany class instance:

branchAlbany.branchID
123
branchAlbany.branchStreet
'123 Main Street'
branchAlbany.branchCity
'Albany'
branchAlbany.branchState
'New York'
branchAlbany.branchZip
12084

To understand this further, let’s look at the attributes of branchAlbany and compare them to the attributes of the Branch class for which the branchAlbany instance is created. A graphical representation of the comparison is shown in Figure 11.3:

Figure 11.3 – Attributes of Branch versus branchAlbany

Figure 11.3 – Attributes of Branch versus branchAlbany

The preceding figure clarifies that the attributes defined as part of the dynamic object creation for the Branch class did not get included in the Branch class itself. The definition of the Branch class remains intact and only the definition of the dynamic object changed in this scenario.

To explore further, we can create another dynamic instance of the Branch class with a different set of attributes:

branchNYC = type('Branch', (object,), {'branchID' : 202, 
                             'productId': 100001,
                           'productName': 'Refrigerator',
                             'productBrand': 'Whirlpool'})

The branchNYC instance now has its own set of dynamic attributes that are not part of either the Branch class or the branchAlbany instance. A comparison of the three is in Figure 11.4:

Figure 11.4 – Attributes of Branch, branchAlbany, and branchNYC

Figure 11.4 – Attributes of Branch, branchAlbany, and branchNYC

With this understanding, let’s look further at creating multiple instances or objects of a class dynamically.

Creating multiple instances of a class dynamically

In this section, let’s look at creating more than one instance of a class dynamically. For this example, we will be making use of a built-in Python function named globals to create dynamic object names, along with the type function that we use to create dynamic objects. Refer to the following steps:

  1. Let’s create a new class named Product without any attributes or methods. Instead of defining the attributes within the class and creating an instance of the class, let’s create multiple instances with their own attributes:

    class Product():

        '''attributes...'''

        '''methods...'''

        pass

  2. Next, we will be creating three dictionary items in a list named details:

    details = [{'branchID' : 202,

                'ProductID' : 100002,

                'ProductName' : 'Washing Machine',

                'ProductBrand' : 'Whirlpool',

                'PurchasePrice' : 450,

                'ProfitMargin' : 0.19},

               {

                'productID' : 100902,

                'productName' : 'Iphone X',

                'productCategory' : 'Electronics',

                'unitPrice' : 700

               },

               {

                'branchID' : 2021,

                'branchStreet' : '40097 5th Main Street',

                'branchBorough' : 'Manhattan',

                'branchCity' : 'New York City',

                'Product ID': 100003,

                'Product Name': 'Washing Machine',

                'Product Brand': 'Samsung',

                'Purchase Price': 430,

                'Profit Margin': 0.18

               },

              ]

  3. These dictionary items are going to be provided as attributes for multiple instances of objects that we are going to create using globals and type:

    for obj,var in zip(['product1','product2','product3'],details):

        globals()[obj] = type('Product', (object,), var)

  4. In the preceding code, we have created three objects product1, product2, and product3 with variables defined in the details list. Each object is created dynamically and will have its own set of attributes.

The Product class has its default set of attributes since we did not define any custom attributes in the class. These are presented in Figure 11.5:

Figure 11.5 – Attributes of Product

Figure 11.5 – Attributes of Product

  1. The attributes of the three objects we created in this example have their own set of attributes defined dynamically. The dynamic attributes of the dynamic objects are in the following figure:
Figure 11.6 – Attributes of product1, product2, and product3

Figure 11.6 – Attributes of product1, product2, and product3

In this section, we learned how to create multiple instances of a class dynamically with each instance having its own dynamic set of attributes. With this understanding, let’s further look at creating multiple classes dynamically.

Creating dynamic classes

In this section, let’s look at how to create classes dynamically with different names and different attributes by making use of the built-in functions of type and globals. To explore this concept further, we will first create one dynamic class using the type function:

Product = type('Product', (object,), {'branchID' : 202, 
                             'productId': 100001,
                             'productName': 'Refrigerator',
                             'productBrand': 'Whirlpool'})

In the preceding code, we created a class named Product and provided the class name, followed by the base classes and their corresponding attributes.

Let’s test the created class with the following code:

Product
__main__.Product
type(Product)
type
Product.branchID
202

With this understanding, let’s now take it further and create multiple dynamic classes.

Creating multiple dynamic classes

In this section, we will be creating multiple dynamic classes using type and globals:

  1. Let’s define three functions to be added as dynamic methods while creating multiple dynamic classes as follows:

    def setBranch(branch):

            return branch

    def setSales(sales):

            return sales

        

    def setProduct(product):

            return product

  2. Next, let’s create a dictionary of attributes:

    details = [{'branch': 202,

                'setBranch' : setBranch

      },

    {'purchasePrice': 430,

      'setSales' : setSales

      },

    {'product': 100902,

      'setProduct' : setProduct

      }]

  3. In the next step, we will be creating multiple classes dynamically using type and globals in a loop:

    for cls,var in zip(['productcls1','productcls2','productcls3'],details):

        globals()[cls] = type(cls, (object,), var)

  4. The preceding code creates three classes named productcls1, productcls2, and productcls3, and also creates dynamic variables and methods that can be further reviewed for their usage in the following code and their corresponding output:

    productcls1.setBranch(productcls1.branch)

    202

    productcls2.setSales(productcls2.purchasePrice)

    430

    productcls3.setProduct(productcls3.product)

    100902

In the preceding code, we have successfully executed the methods created within the dynamic classes.

In this section, we have learned how to create multiple classes dynamically. With this understanding, let’s proceed further by creating dynamic methods in classes.

Creating dynamic attributes and methods

In this section, let’s explore how to create dynamic methods within classes. A dynamic method is a method created for a class during runtime, unlike the regular class methods that we create while coding within the class definition itself.

Dynamic methods are created to avoid modifying the structure or the original class definition once it is defined. Instead of modifying the class definition, we can define and call a runtime template method that will in turn create a dynamic method for the class.

Let’s start by creating a simple class definition for managing the coupons of ABC Megamart named SimpleCoupon:

class SimpleCoupon:
    '''attributes''''''
    
 ''''''methods''''''
    pass

We did not define any attributes or methods for this class, but we will define them more clearly in the following sections.

Defining attributes dynamically

Let’s now define a set of coupon attributes for the SimpleCoupon class during runtime using Python’s built-in setattr function. This function accepts a Python object, the name of the attribute, and its corresponding value:

setattr(SimpleCoupon,'couponDetails',
[["Honey Mustard Sauce","Condiments","ABCBrand3","Pasadena Store","10/1/2021",2],
["Potato Chips","Snacks","ABCBrand1","Manhattan Store","10/1/2021",2],
["Strawberry Ice Cream","Desserts","ABCBrand3","ABC Manufacturer","10/1/2021",2]])

In the preceding code, we have provided the class name SimpleCoupon as the input object, followed by the attribute name as couponDetails, and its corresponding values as three lists of product details, one for each type of coupon: Condiments, Snacks, and Desserts.

Now that we have dynamically created the attribute, let’s check whether it has been added to the SimpleCoupon class and is available for use by looking at the list of attributes and methods available in the class as represented in Figure 11.7:

Figure 11.7 – couponDetails added to SimpleCoupon

Figure 11.7 – couponDetails added to SimpleCoupon

With this understanding, let’s further dynamically create methods in the SimpleCoupon class.

Defining methods dynamically

In this section, let’s create a new function that acts as a template function to dynamically generate methods within the SimpleCoupon class. We will now create a function named createCoupon that accepts a class object, method name, and the coupon details as input.

Within the function definition, let’s also define a generateCoupon function that will be generated as a dynamic method in the class:

def createCoupon(classname,methodname,couponDetails):
    def generateCoupon(couponDetails):
        import random
        couponId =  random.sample(range(
      100000000000,900000000000),1)
        for i in couponId:
            print('***********------------------
           **************')
            print('Product:', couponDetails[0])
            print('Product Category:', couponDetails[1])
            print('Coupon ID:', i)
            print('Brand:', couponDetails[2])
            print('Source:', couponDetails[3])
            print('Expiry Date:', couponDetails[4])
            print('Quantity:', couponDetails[5])
            print('***********------------------
           **************')
    setattr(classname,methodname,generateCoupon)

In the preceding code, we call the setattr function to define the method dynamically in the class object provided as input to setattr.

In the next step, let’s generate three generateCoupon methods dynamically using the same method definition but with three different names and test them with three different sets of attributes.

for method,var in zip(['generateCondimentsCoupon','generateSnacksCoupon','generateDessertsCoupon'],SimpleCoupon.couponDetails):
    createCoupon(SimpleCoupon, method,var) 

Now, the SimpleCoupon class has three different methods added to it with the names generateCondimentsCoupon, generateSnacksCoupon, and generateDessertsCoupon respectively. The dynamics methods added to the SimpleCoupon class are shown in the following figure:

Figure 11.8 – Dynamic methods added to SimpleCoupon

Figure 11.8 – Dynamic methods added to SimpleCoupon

Let’s run each method by calling them from the SimpleCoupon class. The generateCondimentsCoupon method is called in the following code:

SimpleCoupon.generateCondimentsCoupon(SimpleCoupon.couponDetails[0])

The output is generated as follows:

***********------------------**************
Product: Honey Mustard Sauce
Product Category: Condiments
Coupon ID: 666849488635
Brand: ABCBrand3
Source: Pasadena Store
Expiry Date: 10/1/2021
Quantity: 2
***********------------------**************
generateSnacksCoupon is called in the following code:
SimpleCoupon.generateSnacksCoupon(SimpleCoupon.couponDetails[1])

The output for this is as follows:

***********------------------**************
Product: Potato Chips
Product Category: Snacks
Coupon ID: 394693383743
Brand: ABCBrand1
Source: Manhattan Store
Expiry Date: 10/1/2021
Quantity: 2
***********------------------**************

The generateDessertsCoupon method is called in the following code:

SimpleCoupon.generateDessertsCoupon(SimpleCoupon.couponDetails[2])

The output is generated as follows:

***********------------------**************
Product: Strawberry Ice Cream
Product Category: Desserts
Coupon ID: 813638596228
Brand: ABCBrand3
Source: ABC Manufacturer
Expiry Date: 10/1/2021
Quantity: 2
***********------------------**************

In this section, we have understood the concept of generating methods dynamically in a Python class along with examples. This concept will help while designing applications with automated code generation capabilities.

Summary

In this chapter, we have learned the concept of dynamic objects by exploring methods of creating various dynamic objects in Python 3. We also covered the concepts of creating multiple instances of a class dynamically. We looked at the concept of creating dynamic classes. We also looked at the concepts of creating attributes and methods dynamically within classes.

Similar to other chapters covered in this book, while this chapter explained dynamic objects, it also provided some focus on metaprogramming and its impact on Python code.

In the next chapter, we will be looking at the concept of design patterns with some other interesting examples.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.186.92