I have implemented a metaclass in Python 3 that, apart from the usual instance constructor (i.e. __init__
), enables you to define a class constructor (as a class method that I called __init_class__
). The metaclass extends abc.ABCMeta
so it supports abstract classes, i.e. the class constructor is not called if a class is abstract.
I've written up some tests for it, they pass, and the metaclass also works in practice for me, but I'd like to know:
- Is this even a good approach for adding a class constructor functionality?
- Are there any problematic corner-cases that I might run into with my code?
- Am I testing it correctly?
- Any comments about my code being (or not) pythonic?
Implementation:
import abc
import inspect
class MyType(abc.ABCMeta):
"""
Metaclass.
It adds extra functionality of an optional class constructor, which might by added
to the class by adding a class method __init_class__(cls) to the class definition.
Class constructor takes no arguments.
"""
def __init__(cls, name, bases, nmspc):
super().__init__(name, bases, nmspc)
cls.__has_init_class__ = hasattr(cls, '__init_class__')
if cls.__has_init_class__ and not inspect.isabstract(cls):
cls.__init_class__()
Tests:
import unittest
class MyTypeTests(unittest.TestCase):
""" Unit tests for MyType metaclass. """
def test_class_constructor(self):
""" Is the class constructor invoked? """
class WithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy class. """
initialized = False
class AbstractWithoutInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy abstract class. """
initialized = False
@abc.abstractmethod
def spam(self):
""" Dummy abstract method. """
pass
class WithInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy class with class constructor. """
initialized = False
@classmethod
def __init_class__(cls):
cls.initialized = True
class AbstractWithInit(metaclass=MyType): # pylint: disable=R0903
""" Dummy abstract class with class constructor. """
initialized = False
@abc.abstractmethod
def spam(self):
""" Dummy abstract method. """
pass
@classmethod
def __init_class__(cls):
cls.initialized = True
self.assertFalse(WithoutInit.initialized)
self.assertFalse(AbstractWithoutInit.initialized)
self.assertTrue(WithInit.initialized)
self.assertFalse(AbstractWithInit.initialized)