1
\$\begingroup\$

I'm a newbie python developer who accidentally studied the concept of Abstract classes and I found it pretty much helpful to implement due to the SOLID principles.

At the very moment of testing and learning the concept, I found out that using a abstract class also as an mix-in class would be a great Idea, so I decided to implement the __init__ method in the base abstract class.

As I am new to the concept, I don't know if this is a good implementation for making reusable software:

#!usr/bin/env python3
# -*- coding: utf-8 -*-

"""Dependency inversion learning file | By : KhodeNima |
2023/10/25
"""


from decimal import (
    Decimal
)

from abc import (
    ABC,
)

class BaseCurrency(ABC):
    def __init__(self, value_by_dollar : Decimal):
        """The base currency class

        Args:
            - value_by_dollar (Decimal): The value of the currency in Dollar.
        """

        self.__name = self.__class__.__name__
        self.value_by_dollar = value_by_dollar

    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, name : str):

        if name == "BaseCurrency":
            raise PermissionError("The BaseCurrency class can only be inherited | ( Not instantiated )")
        
        return name

    

class Dirham(BaseCurrency):
    ...


some_decimal_value = Decimal('10.0')
dirham_1 = Dirham(value_by_dollar=some_decimal_value)

\$\endgroup\$
3
  • 5
    \$\begingroup\$ Is there some code missing from Dirham? It would be helpful for reviewers to see this implementation (as it clarifies how you intend the base to be extended). Question size limit is plenty large enough for you to fill that in - thanks! \$\endgroup\$ Oct 25 at 17:31
  • 6
    \$\begingroup\$ Which dollars? USD? \$\endgroup\$
    – Reinderien
    Oct 25 at 18:45
  • 1
    \$\begingroup\$ dirham_1.name results in AttributeError: 'Dirham' object has no attribute '_name'. Did you mean: 'name'? And dirham_1.name = "Name" doesn't do anything. As such your code is just a long-winded way of storing only value_by_dollar. \$\endgroup\$
    – Peilonrayz
    Oct 26 at 13:45

1 Answer 1

5
\$\begingroup\$

Constructor

The idea behind Abstract Base Classes is that they can't be instantiated by themselves, only their subclasses can. However, we would still use __init__ to ensure that attributes of class instances are set properly. In your case, that would be both _name and value_by_dollar.

If that is all that is needed in a constructor, then subclasses don't even need to provide their own constructor. Calling __init__ in that case redirects to the base class' implementation. Otherwise, remember to call super().init() to explicitly call the superclass constructor.

Coupling the class name with the instance attribute __name self.__name = self.__class__.__name__ is a bad practice. There is no reason why they should be coupled - and you're defining a setter so that coupling can fall apart later.

A better idea would be to ask for the currency name in the constructor of the base class. Then, in subclasses, we would call super().__init__() by providing a string as the currency name. This enables you to change the _name attribute without changing the class names of the subclasses.

Also, a side note: there is a typo when setting _name (note the double underscore)!

Name Property Getter and Setter

Here, you provide a default for both implementations. Note that you haven't defined any of them as abc.abstractmethod (link), indicating that subclasses need not override them. Classes inheriting from ABC cannot be instantiated unless all abstract methods or properties are implemented. However, since you provided implementations for all methods, BaseCurrency can be instantiated.

In addition the name setter explicitly checks the _name attribute of the instance to avoid the setter to be called. This puts a cognitive load on you to change the condition if you ever change the BaseCurrency to be called something else.

Thankfully, there is an easy fix for both of these - declare the setter as an abstract method:

@name.setter
@abstractmethod
def name(self, name : str):
    pass

Since you have an abstract method now, BaseCurrency cannot be instantiated. Also, subclasses need to implement their own name setter which eliminates the need to check if the calling class is the base one.

This also means you actually need to provide a body to the Dirham class. Usually, empty subclasses of an abstract class tell you either you don't need an abstract class (everything can be done through instantiation) or you are doing too much in the abstract class that should be left to the subclasses. And an abstract class with no abstract methods (that is, without places that need to be filled in for the class to do something) is abstract only in name.

\$\endgroup\$
4
  • 3
    \$\begingroup\$ @SᴀᴍOnᴇᴌᴀ There is no code missing. In the Dirham class, there is an ellipse - the effect would be the same if there was just pass. Dirham is a proper subclass, but does not override anything so the body can be left empty. I couldn't comment on the top post yet. \$\endgroup\$ Oct 25 at 18:43
  • 1
    \$\begingroup\$ I apologize for missing that. I have removed my comment. \$\endgroup\$ Oct 25 at 20:32
  • \$\begingroup\$ Is ... a synonym for pass? Is that a fairly recent thing? I'd not heard of it before. \$\endgroup\$ Oct 26 at 7:17
  • 3
    \$\begingroup\$ @TobySpeight ... is a singleton value similar to None. It doesn't do anything by itself, but can be used for various practical purposes. It's been around since 3.5 if I'm not mistaken. On a line by itself it's just an expression that evaluates to itself and does nothing. It can be used in place of pass, but I've understood that there's a convention to use pass to mean "this intentionally does nothing" and ... to mean "not implemented". \$\endgroup\$
    – Dronir
    Oct 26 at 8:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.