Data Abstraction in Python – Simply Explained

A typical online search queries the meaning of data abstraction in Python. When I began learning Python, the answers I found caused more confusion than they solved.

Today I will describe data abstraction in a clear way that will assist you in better understanding the concept. We will then apply that concept in code to underscore understanding. If you haven’t come to grips with Python classes, encapsulation, and inheritance, you may wish to review those topics before entering the world of abstraction.

As you go through the article, you can also watch or listen to the explainer video:

What Does Abstract Mean?

The dictionary definition of the word abstract is “existing as an idea, feeling, or quality, not as a material object.”

For example, happiness or confidence are not material or concrete objects, yet we all know what the terms mean, and we recognize happiness or confidence when it occurs.

When we speak of the opposite of abstract, we use terms like ‘concrete’, referring to something that exists.

So What Is Abstraction in Object Orientation?

Let’s assume we have a tennis ball, a football, and a golf ball. They are all material objects that we can touch and use. However, if we use the term ‘ball’ on its own, that is an abstraction. If I speak of a ball, to what do I refer? A wrecking ball, a rugby ball, a disco ball (showing my age here)? Yet, when I speak to you of a ball, there are general concepts and rules that you will immediately understand. It’s either spherical or, in the case of a rugby ball, an elongated ellipsoid. It is generally used either for sport or to achieve some form of work. You have no idea of size, color, texture, or the material of manufacture. You don’t know its weight.

Therefore, the term ball is an abstraction, which gives you an understanding of the general concept but not a detailed understanding of a specific object. The term ball does not exist. It only exists once it becomes something tangible, such as a golf ball.

How Does This Work With Data?

When you begin coding, you will understand in a general sense what you wish to achieve, yet you often have no idea the variety of objects users of your code will create.

In my previous articles on Python classes, we used the example of a grocery store basic stock management program. Such an example nicely helps explain data abstraction.

If tasked with coding the stock management system for a small grocery store owned by a family member, you need first to think up the general rules and concepts that will apply to stock items. There are some things you know every stock item will need to possess. You will require a stock code, a description, a wholesale price, and the markup you wish to apply to an item.

In such a case described, you can code up a class that is an abstract of a stock item. A concept of those things you feel an item sitting on the shelf will possess. An abstract class in Python is a class that holds one or more abstract methods. An abstract method is a method that you declare but to which you add no functionality. All sub-classes of the abstract class must implement the abstract method. So let’s code an abstract class for our stock item.

from abc import ABC, abstractmethod

class Stock(ABC): # Abstract class

    def __init__(self, stock_code, description, buy_price, mark_up, volume, manuf):
        self.code = stock_code
        self.desc = description
        self.buy = buy_price
        self.margin = mark_up
        self.volume = volume
        self.manuf = manuf
        super().__init__()

    def sell_price(self):
        print('Retail price = $', round(self.buy * self.margin, 2))

    @abstractmethod
    def sale(self):
        pass

    @abstractmethod
    def expiry(self):
        pass

So in the code above, we imported methods 'ABC' and abstractmethod from the module 'abc'. These methods will allow us to create our abstract class. We then created the class Stock as you would typically create any class, although in this case, we call the method ABC in the parentheses.

We then define all the attributes we feel a generic stock item may need, using the __init__() function, and then create a standard method that will calculate the price of any stock item when called.

Then we use the decorator @abstractmethod to define two abstract methods, one called sale and the other called expiry. These methods bring us back to our original discussion on abstraction. We know that each sub-class we generate, and most of the objects we create from those sub-classes, will need expiry dates and sales methods. However, we don’t know which ones and what form they will take. Therefore we can create a generic or abstract concept to be completed upon instantiation of the object.

One important point to note. If we try to create an object using this abstract class, we will get an error message. This is because you can only create a sub-class from an abstract class, and it is from that subclass that we may instantiate an object.

Let me show you.

from abc import ABC, abstractmethod

class Stock(ABC): # Abstract class

    def __init__(self, stock_code, description, buy_price, mark_up, volume, manuf):
        self.code = stock_code
        self.desc = description
        self.buy = buy_price
        self.margin = mark_up
        self.volume = volume
        self.manuf = manuf
        super().__init__()

    def sell_price(self):
        print('Retail price = $', round(self.buy * self.margin, 2))

    @abstractmethod
    def sale(self):
        pass

    @abstractmethod
    def expiry(self):
        pass

CannedPeas = Stock('C234', 'Canned Peas', 0.65, 1.457, '400mls', 'Edgells')

# Result
'''
Traceback (most recent call last):
  File "C:\Users\David\Desktop\abstraction.py", line 28, in <module>
    CannedPeas = Stock('C234', 'Canned Peas', 0.65, 1.457, '400mls', 'Edgells')
TypeError: Can't instantiate abstract class Stock with abstract methods expiry, sale
'''

Yet when we create a sub-class or several sub-classes, everything works for us. Following is a code example where I’ve created two sub-classes. One is for canned items, another for fruit. Each uses the abstract class attributes, but given the differences possessed by each stock item, we use methods of the same name but with different actions.

An item to note, we must have the abstract class methods ‘expiry‘ and ‘sale‘ in our sub-classes, or Python will give us an error. However, we don’t need an expiry for the canned goods, so we use the ‘pass‘ syntax to allow Python to operate without action. Similarly, we don’t have a manufacturer for apples so when we pass in the attributes, we use a blank string in that position.

from abc import ABC, abstractmethod
from datetime import datetime, timedelta

class Stock(ABC): # Abstract class

    def __init__(self, stock_code, description, buy_price, mark_up, volume, manuf):
        self.code = stock_code
        self.desc = description
        self.buy = buy_price
        self.margin = mark_up
        self.volume = volume
        self.manuf = manuf
        super().__init__()

    def sell_price(self):
        print('Retail price = $', round(self.buy * self.margin, 2))

    @abstractmethod
    def sale(self):
        pass

    @abstractmethod
    def expiry(self):
        pass

class Canned(Stock): # Concrete class
    category = 'Cans'

    def sale(self, num):
        print('Buy', num, 'cans of', self.desc, 'for the price of: $', round(self.buy * self.margin, 2), '\n')

    def expiry(self):
        pass

class Fruit(Stock):
    category = 'produce'

    def sale(self, discount):
        print('Buy 2', self.volume, 'of', self.desc, 'at the discounted price of $', round((self.buy * self.margin)*(1-discount),2))
        print('Normal retail price $', round(self.buy * self.margin, 2), '\n')

    def expiry(self, days):
        expirydate = datetime.today() + timedelta(days=days)
        print('Use by:', expirydate.day, expirydate.month, expirydate.year)

# Instantiate two objects - one from each sub-class
C465 = Canned('C465', 'Chicken Soup', 0.65, 1.468, '400mls', 'Campbells')

P987 = Fruit('P987', 'Golden Delicious Apples', 1.57, 1.58, 'bags', '')

C465.sale(3)

C465.sell_price()

print()

P987.sale(.25)

P987.expiry(14)

# Result
'''
Buy 3 cans of Chicken Soup for the price of: $ 0.95 

Retail price = $ 0.95

Buy 2 bags of Golden Delicious Apples at the discounted price of $ 1.86
Normal retail price $ 2.48 

Use by: 9 7 2021
'''

In the above code, I’ve created or instantiated two concrete objects, one from each class. I’ve then called the methods ‘sale‘ and ‘expiry‘ where applicable and I’ve also accessed the abstract class’s normal method of ‘sell_price‘. Note that I needed to import the datetime module to carry out the calculation used in the fruit expiry method.

Summary

Today we learned about data abstraction in Python. We know that the term abstract means ‘existing as an idea, feeling, or quality, not as a material object.’ We then talked about abstraction being something that gives you broad concepts without all the small specific details.

In our case of data abstraction, we showed how we could use the Python module 'abc' to create an abstract class of Stock for our grocery stock management program. This abstract class contains all those generic attributes and methods we believe form the basis of a grocery stock item without knowing the specifics of what items will be sold.

We then created two concrete classes for two very different stock items, fruit and canned goods. From those, we created two objects that each used the specific methods from the concrete classes in addition to the standard method accessible in the abstract class.

We also learned that you cannot instantiate an object directly from an abstract class and that if the abstract class has abstract methods, those methods must be replicated in the sub-classes; otherwise, an error will result.

I trust this article has been useful in your understanding. Thank you for reading.