Skip to content

Instantly share code, notes, and snippets.

@iamaziz
Last active May 6, 2020 05:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamaziz/4afbd9e33222b39d7bf8b07c510f07cb to your computer and use it in GitHub Desktop.
Save iamaziz/4afbd9e33222b39d7bf8b07c510f07cb to your computer and use it in GitHub Desktop.
Note on metaprogramming and metacalss in Python
Date: 4/27/2020

Why need metaprogramming? (maybe metaclass?)

Scenario:

  • I want to have a single Interface Class that has a Base Implementation Class with multiple instance implementations for other Inheriting Classes

For example (let's say a class to extract data from multiple data sources) as follows:

Interaface: Extractor BaseCalss: Base Extractor Instances: Extractor 1, Extractor 2, ... , Extractor n

From the above, I aways want to call Extractor only with specifying which class to use e.g.

a = Extractor()                   # --> by default choose the BaseClass
a = Extractor(name="Extractor1")  # --> chooses Extractor 1 class
a = Extractor(name="Extractor2")  # --> chooses Extractor 2 class
# ... and so on

Important thing to notice is that the instance classes build on top of the Base Class functionality

Idea:

Use a meta class to auto register the member classes.

For example, to build the above in code, it can be something like this:

from abc import ABCMeta, abstractmethod

_registered = {}

# -- Class Registrar
class ExtractorMeta(ABCMeta):
    def __init__(cls, clsname, bases, methods):
        super().__init__(clsname, bases, methods)
        print(f"registering {cls.__name__}")
        _registered[cls.__name__] = cls

# -- Interface Class
class Extractor(metaclass=ExtractorMeta):
    def __init__(self, name: str = "BaseExtractor"):
        if name not in _registered:
            raise NotImplementedError(f"Calss '{name}' is not registered!!")
        self.__class__ = _registered[name]

    @abstractmethod
    def get(self): raise NotImplementedError

# -- Base Class
class BaseExtractor(Extractor):
  def get(self): return "calling requests.get"

# -- Instance Classes
class TwitterExtractor(BaseExtractor):
    def get(self): return "getting twitters data"

class YouTubeExtractor(BaseExtractor):
    def get(self): return "getting YouTube data"
    

_registered
# {'Extractor': __main__.Extractor,
#  'BaseExtractor': __main__.BaseExtractor,
#  'TwitterExtractor': __main__.TwitterExtractor,
#  'YouTubeExtractor': __main__.YouTubeExtractor}

Then calling ....

BaseExtractor().get()                     # 'calling requests.get'
BaseExtractor("TwitterExtractor").get()   # 'getting twitters data'
BaseExtractor("YouTubeExtractor").get()   # 'getting YouTube data'
@iamaziz
Copy link
Author

iamaziz commented May 6, 2020

The highlighted quote below is very true; at least in my case.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment