Search code examples
pythonpython-3.xoopdesign-patterns

Check unique value when define concrete class for abstract variable in python


Suppose that I have this architecture for my classes:

# abstracts.py
import abc
class AbstractReader(metaclass=abc.ABCMeta):
  
    @classmethod
    def get_reader_name(cl):
        return cls._READER_NAME

    @classmethod
    @property
    @abc.abstractmethod
    def _READER_NAME(cls):
        raise NotImplementedError

# concretes.py
from .abstracts import AbstractReader
class ReaderConcreteNumber1(AbstractReader):
    _READER_NAME = "NAME1"

class ReaderConcreteNumber2(AbstractReader):
    _READER_NAME = "NAME2"

Also I have a manager classes that find concrete classes by _READER_NAME variable. So I need to define unique _READER_NAME for each of my concrete classes.

how do I check that NAME1 and NAME2 are unique when concrete classes are going to define?


Solution

  • You can create a metaclass with a constructor that uses a set to keep track of the name of each instantiating class and raises an exception if a given name already exists in the set:

    class UniqueName(type):
        names = set()
    
        def __new__(metacls, cls, bases, classdict):
            name = classdict['_READER_NAME']
            if name in metacls.names:
                raise ValueError(f"Class with name '{name}' already exists.")
            metacls.names.add(name)
            return super().__new__(metacls, cls, bases, classdict)
    

    And make it the metaclass of your AbstractReader class. since Python does not allow a class to have multiple metaclasses, you would need to make AbstractReader inherit from abc.ABCMeta instead of having it as a metaclass:

    class AbstractReader(abc.ABCMeta, metaclass=UniqueName):
        ... # your original code here
    

    Or if you want to use ABCMeta as metaclass in your AbstractReader, just override ABCMeta class and set child ABC as metaclass in AbstractReader:

    class BaseABCMeta(abc.ABCMeta):
        """
        Check unique name for _READER_NAME variable
        """
        _readers_name = set()
    
        def __new__(mcls, name, bases, namespace, **kwargs):
            reader_name = namespace['_READER_NAME']
            if reader_name in mcls._readers_name:
                raise ValueError(f"Class with name '{reader_name}' already exists. ")
            mcls._readers_name.add(reader_name)
    
            return super().__new__(mcls, name, bases, namespace, **kwargs)
    
    class AbstractReader(metaclass=BaseABCMeta):
        # Your codes ...
    

    So that:

    class ReaderConcreteNumber1(AbstractReader):
        _READER_NAME = "NAME1"
    
    class ReaderConcreteNumber2(AbstractReader):
        _READER_NAME = "NAME1"
    

    would produce:

    ValueError: Class with name 'NAME1' already exists.
    

    Demo: https://replit.com/@blhsing/MerryEveryInternet