Reputation: 5877
One of the answers to a previous question I asked suggests the use of a metaclass.
class DogType(type):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
class Sheperd(Dog):
habits = ["herds sheep"]
class GermanSheperd(Sheperd):
habits = ["bites people"]
class Poodle(Dog):
habits = ["barks stupidly"]
class StBernard(Dog):
pass
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
dog = cls(name)
print(dog)
However this throws an error:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I like this solution, however I also really need class Dog
to behave like a metaclass such that I can define abstract methods in Dog
that will need to propagate in all subclasses. This could be a method like def bark()
which all sub-dogs would need to implement...
How do I get Dog
to be both a metaclass implementing the functionality in DogType
, but also an Abstract class of its own accord which restricts how subclasses are instantiated and run?
Upvotes: 1
Views: 61
Reputation: 5877
@MisterMiyagi's comments have provided some food for thought and also allowed me to better understand how ABC's themselves are implemented. It seems the "Abstract to Concrete" order is something like this:
type --> ABCMeta --> ABC --> regular class --> regular subclass --> object
This being said, if DogType
inherits from ABCMeta
as opposed to from type
, it can still act as a metaclass, all the while allowing its subclasses to act as abstract base classes, since ABCMeta
is the metaclass for any ABC
. This allows us to do the following:
class DogType(ABCMeta):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
@abstractmethod
def print_habits(self):
for habit in self.habits:
print(habit)
class Sheperd(Dog):
habits = ["herds sheep"]
def print_habits(self):
for habit in self.habits:
print(habit)
class GermanSheperd(Sheperd):
habits = ["bites people"]
def print_habits(self):
for habit in self.habits:
print(habit)
class Poodle(Dog):
habits = ["barks stupidly"]
def print_habits(self):
for habit in self.habits:
print(habit)
class StBernard(Dog):
def print_habits(self):
for habit in self.habits:
print(habit)
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
print('\n', name)
print(cls)
dog = cls(name)
dog.print_habits()
I am aware that it is unclear in the above code why I am defining print_habits
as an abstractmethod
and reimplementing it in subclasses. I could simply define it once in Dog
and it would be just fine, but in my use case there are methods that need to be enforced in all Dog
subclasses, and some that don't.
Upvotes: 0
Reputation: 2676
If you look at the source code for the ABC
class, you'll find that it's a simple instance of the ABCMeta
class, which is why your example gave a metaclass conflict Thus, from your example, you can achieve it by
from abc import ABCMeta, abstractmethod
class Meta(ABCMeta):
pass
class BaseClass(metaclass=Meta):
@abstractmethod
def something(self):
pass
class DerivedClass(BaseClass):
def something(self):
return 1
try:
BaseClass()
except TypeError:
pass
else:
raise Exception('Meta class failed')
DerivedClass()
And you can see that this program runs just fine.
Upvotes: 1