I want to ensure that any class that is derived from my class overrides certain methods. If they are not overridden I want to raise a NotImplementedError as soon as possible after compiling begins, rather than when one of the methods are called.
I've found that I can do it with a metaclass like so:
class MetaBaseClass(type):
# list of method names which should be overridden
to_override = ['method_a', 'method_b']
def __init__(cls, name, bases, dct):
for methodName in cls.to_override:
if methodName not in dct:
raise NotImplementedError('{0} must override the {1} method'.format(name, methodName))
super(MetaBaseClass, cls).__init__(name, bases, dct)
class BaseClass(object):
__metaclass__ = MetaBaseClass
def method_a(self):
pass
def method_b(self):
pass
This will raise the error at class definition time if method_a or method_b aren't overridden by class derived from BaseClass.
Is there a better way to do this?
Why not use abstractmethod.
from abc import abstractmethod, ABCMeta
class BaseClass(object):
__metaclass__ = ABCMeta
#abstractmethod
def method_a(self):
pass
#abstractmethod
def method_b(self):
pass
class Inherit(BaseClass):
pass
You will get an error as soon as a user tries to instantiate the Inherit class.
i = Inherit()
TypeError: Can't instantiate abstract class Inherit with abstract methods method_a, method_b
Related
I know that inside a metaclass, I can do:
class MyMetaClass(type):
def __new__(cls, name, bases, attrs):
return type.__new__(cls,name,bases,attrs)
if I want to replace bases with my own base class: MyClass[Generic[TypeVar("T")]], ie, MyClass is a generic class.
if i just do:
my_class = MyClass[Generic[TypeVar("T")]]
return type.__new__(cls,name,(my_class,),attrs)
It gives me a
type() doesn't support MRO entry resolution; use types.new_class()
What does it mean? How do I specify my base class that inherits from Generic?
Why does base class order matter when overriding #abstractmethods in base classes?
Is it possible to override abstract methods in base classes without depending on base class order?
from abc import ABC, abstractmethod
class A(ABC):
#abstractmethod
def f(self):
raise NotImplementedError
class B:
def f(self):
pass
# class C(A,B): pass # fails
class C(B,A): pass # works
While C(B,A) works, C(A,B) results in
TypeError: Can't instantiate abstract class C with abstract methods f
I have a mixin class that is supposed to be used as a interface only when it is used with other classes, for example:
class Mother():
pass
class Child(Mother):
pass
class Mixin():
def __init__(self):
assert isinstance(self, Mother), 'Mixin can only be used with Mother implementations'
super().__init__()
class Implementation(Mixin, Child):
pass
Implementation()
The above works, but only when Implementation is being instantiated, can I somehow have the above assert be evaluated on code execution?
This is important so that the application won't run if someone implemented a class wrongfully.
(I'm not sure I worded the title correctly)
Actually, it won't work even if "when Implementation is being instantiated" - it'll find the relation to Mother class via Child class (Implementation inherits Child ---> Child inherits Mother),
thereby isinstance(self, Mother) treats Implementation as derived from Mother class due to inheritance chain (considered as mro (method resolution order))
use __init_subclass__ hook instead:
class Mother():
pass
class Child(Mother):
pass
class Mixin():
def __init_subclass__(cls, **kwargs):
assert isinstance(cls, Mother), 'Mixin can only be used with Mother'
super().__init_subclass__(**kwargs)
class Implementation(Mixin, Child):
pass
Implementation()
Throws:
Traceback (most recent call last):
File ..., in __init_subclass__
assert isinstance(cls, Mother), 'Mixin can only be used with Mother'
AssertionError: Mixin can only be used with Mother
But if you need to allow Mixin to be applied to Mother class and its subclasses - use issubclass call instead:
class Mixin():
def __init_subclass__(cls, **kwargs):
assert issubclass(cls, Mother), 'Mixin can only be used with Mother and its subclasses'
super().__init_subclass__(**kwargs)
The hook will be applied on class declaration phase (before potential instantiation)
You can also use metaclass, it is powerful and can help you understand python class.
class Mother():
pass
class Child(Mother):
pass
class Meta(type):
def __new__(meta_cls, name, bases, dct):
if name != "Mixin" and all([not issubclass(b, Mother) for b in bases]):
raise Exception('Mixin can only be used with Mother')
cls = super().__new__(meta_cls, name, bases, dct)
return cls
class Mixin(metaclass=Meta):
pass
class Implementation(Mixin, Child):
pass
I have an abstract class with a 'mandatory' property:
class PMixin(ABC):
#property
#abstractmethod
def progressbar_step(self):
raise NotImplementedError
It is possible to have a mandatory property only in specific cases, and not for all sub-clases.
I use multiple inheritance, so if class A
class A(PMixin, B)
inherits also from B not to be mandatory, otherwise to be mandatory.
A inherits all from PMixin if is not a subclass of B.
If it inherits also from B, progressbar_step is not necessary.
I can declare: progressbar_step=None, in the PMixin
and overwrite only if is not inheriting from B, and solve the issue, like
class PMixin(ABC):
progressbar_step = None
class A(PMixin)
progressbar_step = 5
class A2(PMixin, B)
....
but I want to get a warning, where is the case to have a value, in example A;
It is just a coding 'safety' mechanism
Here's a solution using __init__subclass__, made available in Python 3.6. This is called whenever PMixin is subclassed, and we can use it to remove the abstractmethod from subclasses that meet our criteria.
from abc import ABC, abstractmethod
class B:
pass
class PMixin(ABC):
#property
#abstractmethod
def progressbar_step(self):
raise NotImplementedError
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if issubclass(cls, B):
print('is subclass')
print(cls.progressbar_step)
cls.progressbar_step = None
class A(PMixin): # Will raise error
pass
class C(PMixin, B): # No error
pass
If you want the warning when you subclass PMixin, rather than when you try to instantiate an object of that subclass, you can check cls.progressbar_step.__isabstractmethod__ in __init_subclass__ and raise warnings appropriately.
for python versions that are < 3.6 you can define a custom meta class to PMixin:
class PMixinMeta(type):
def __new__(mcs, names, bases, body):
klass = super().__new__(mcs, names, bases, body)
if issubclass(klass, B): # 'if B in bases' is also ok
klass.progressbar_step = None
return klass
however since ABC also uses a custom meta class, you will get a metaclass conflict if you define PMixinMeta as the meta of PMixin and inherit from ABC.
so you need to create an intermediate metaclass to resolve the conflict.
from abc import ABCMeta
class PMixinAbcMeta(ABCMeta, PMixinMeta):
pass
and then define PMixinAbcMeta as the metaclass of PMixin
from abc import abstractmethod
class PMixin():
__metaclass__ = PMixinAbcMeta
#property
#abstractmethod
def progressbar_step(self):
raise NotImplementedError
Note: you will only get an exception once you initiate (use __init__) an instance of a subclass of PMixin.
If you want to receive an exception during build time, PMixinMeta should look like this:
class PMixinMeta(type):
def __new__(mcs, names, bases, body):
klass = super().__new__(mcs, names, bases, body)
if issubclass(klass, B):
klass.progressbar_step = None
else:
if 'progressbar_step' not in body:
raise ValueError('must have a progressbar_step method')
return klass
meaning that now this:
class A(PMixin):
pass
will raise an exception, and not only this:
A()
This question already has answers here:
What are metaclasses in Python?
(25 answers)
Closed 9 years ago.
Is there a way to trigger code when my class is subclassed?
class SuperClass:
def triggered_routine(subclass):
print("was subclassed by " + subclass.__name__)
magically_register_triggered_routine()
print("foo")
class SubClass0(SuperClass):
pass
print("bar")
class SubClass1(SuperClass):
print("test")
Should output
foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1
Classes (by default) are instances of type.
Just as an instance of a class Foo is created by foo = Foo(...),
an instance of type (i.e. a class) is created by myclass = type(name, bases, clsdict).
If you want something special to happen at the moment of class-creation, then you have to modify the thing creating the class -- i.e. type. The way to do that is to define a subclass of type -- i.e. a metaclass.
A metaclass is to its class as a class is to its instance.
In Python2 you would define the metaclass of a class with
class SuperClass:
__metaclass__ = Watcher
where Watcher is a subclass of type.
In Python3 the syntax has been changed to
class SuperClass(metaclass=Watcher)
Both are equivalent to
Superclass = Watcher(name, bases, clsdict)
where in this case, name equals the string 'Superclass', and bases is the tuple (object, ). The clsdict is a dictionary of the class attributes defined in the body of the class definition.
Note the similarity to myclass = type(name, bases, clsdict).
So, just as you would use a class's __init__ to control events at the moment of a instance's creation, you can control events at the moment of a class's creation with a metaclass's __init__:
class Watcher(type):
def __init__(cls, name, bases, clsdict):
if len(cls.mro()) > 2:
print("was subclassed by " + name)
super(Watcher, cls).__init__(name, bases, clsdict)
class SuperClass:
__metaclass__ = Watcher
print("foo")
class SubClass0(SuperClass):
pass
print("bar")
class SubClass1(SuperClass):
print("test")
prints
foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1
Edit: My old post actually didn't work. Subclassing from classmethod doesn't work as expected.
First, we would like to have some way to tell the metaclass that this particular method is supposed to have the special called on subclass behavior, we'll just set an attribute on the function we'd like to call. As a convenience, we'll even turn the function into a classmethod so that the real baseclass it was found in can be discovered, too. We'll return the classmethod so that it can be used as a decorator, which is most convenient.
import types
import inspect
def subclass_hook(func):
func.is_subclass_hook = True
return classmethod(func)
We're also going to want a convenient way to see that the subclass_hook decorator was used. We know that classmethod has been used, so we'll check for that, and only then look for the is_subclass_hook attribute.
def test_subclass_hook(thing):
x = (isinstance(thing, types.MethodType) and
getattr(thing.im_func, 'is_subclass_hook', False))
return x
Finally, we need a metaclass that acts on the information: For most cases, the most interesting thing to do here is just check each of the supplied bases for hooks. In that way, super works in the least surprising way.
class MyMetaclass(type):
def __init__(cls, name, bases, attrs):
super(MyMetaclass, cls).__init__(name, bases, attrs)
for base in bases:
if base is object:
continue
for name, hook in inspect.getmembers(base, test_subclass_hook):
hook(cls)
and that should do it.
>>> class SuperClass:
... __metaclass__ = MyMetaclass
... #subclass_hook
... def triggered_routine(cls, subclass):
... print(cls.__name__ + " was subclassed by " + subclass.__name__)
>>> class SubClass0(SuperClass):
... pass
SuperClass was subclassed by SubClass0
>>> class SubClass1(SuperClass):
... print("test")
test
SuperClass was subclassed by SubClass1