Reputation: 11
I'm having a minor, I hope, issue with theory and the proper way to deal with a problem. It's easier for me to show an example then to explain as I seem to fail with my vocabulary.
class Original_1:
def __init__(self):
pass
def meth1(self):
pass
def meth2(self):
pass
class Original_2(Original_1):
def __init__(self):
Original_1.__init__(self)
def meth3(self):
pass
class Mixin:
def __init__(self):
pass
def meth4(self):
...
meth1(self)
meth2(self)
class NewClass_1(Original_1, Mixin):
def __init__(self):
Original_1.__init__(self)
Mixin.__init__(self)
class NewClass_2(Original_2, Mixin):
def __init__(self):
Original_2.__init__(self)
Mixin.__init__(self)
Now the goal is to extend Original_1 or Original_2 with new methods in the Mixin, but I run into some questions if I use meth1(), meth2(), or meth3() in the mixin. 1. I'm not referencing Original_1 or Origninal_2 in the mixin. (At this point it runs but I don't like it.) 2. If I make Mixin a child of Original_1, it breaks. I could make two separate NewClass_X but then I'm duplicating all of that code.
Upvotes: 0
Views: 2923
Reputation: 155418
Since Mixin
is not a standalone class, you can just write it to assume that the necessary methods exist, and it will find them on self
assuming the self
in question provides, or derives from another class which provides, meth1
and meth2
.
If you want to ensure the methods exist, you can either document it in the Mixin
docstring, or for programmatic enforcement, use the abc
module to make Mixin
an ABC
and specify what methods must be defined; if a given class doesn't provide them (directly or via inheritance) then you'll get an error if you attempt to instantiate it (because the class is still abstract until those methods are defined):
from abc import ABCMeta, abstractmethod
class Mixin(metaclass=ABCMeta):
def __init__(self):
pass
@abstractmethod
def meth1(self): pass
@abstractmethod
def meth2(self): pass
def meth4(self):
...
self.meth1() # Method call on self will dispatch to other class's meth1 dynamically
self.meth2() # Method call on self will dispatch to other class's meth2 dynamically
Beyond that, you can simplify your code significantly by using super
appropriately, which would remove the need to explicitly call the __init__
s for each parent class; they'd be called automatically so long as all classes use super
appropriately (note: for safety, in cooperative inheritance like this, you usually accept the current class's recognized arguments plus varargs, passing the varargs you don't recognize up the call chain blindly):
class Original_1:
def __init__(self, orig1arg, *args, **kwargs):
self.orig1val = orig1arg # Use what you know
super().__init__(*args, **kwargs) # Pass what you don't
def meth1(self):
pass
def meth2(self):
pass
class Original_2(Original_1):
def __init__(self, orig2arg, *args, **kwargs):
self.orig2val = orig2arg # Use what you know
super().__init__(self, *args, **kwargs) # Pass what you don't
def meth3(self):
pass
class Mixin(metaclass=ABCMeta):
# If Mixin, or any class in your hierarchy, doesn't need to do anything to
# be initialized, just omit __init__ entirely, and the super from other
# classes will skip over it entirely
def __init__(self, mixinarg, *args, **kwargs):
self.mixinval = mixinarg # Use what you know
super().__init__(self, *args, **kwargs) # Pass what you don't
@abstractmethod
def meth1(self): pass
@abstractmethod
def meth2(self): pass
def meth4(self):
...
self.meth1() # Method call on self will dispatch to other class's meth1
self.meth2() # Method call on self will dispatch to other class's meth1
class NewClass_1(Original_1, Mixin):
def __init__(self, newarg1, *args, **kwargs):
self.newval1 = newarg1 # Use what you know
super().__init__(self, *args, **kwargs) # Pass what you don't
class NewClass_2(Original_2, Mixin):
def __init__(self, newarg2, *args, **kwargs):
self.newval2 = newarg2 # Use what you know
super().__init__(self, *args, **kwargs) # Pass what you don't
Note that using super
everywhere means you don't need to explicitly call each __init__
for your parents; it automatically linearizes the calls, so for example, in NewClass_2
, that single super().__init__
will delegate to the first parent (Original_2
), which then delegates to Original_1
, which then delegates to Mixin
(even though Original_1
knows nothing about Mixin
).
In more complicated multiple inheritance (say, you inherit from Mixin
through two different parent classes that both inherit from it), using super
is the only way to handle it reasonably; super
naturally linearizes and deduplicates the parent class tree, so even though two parents derive from it, Mixin.__init__
would still only be called once, preventing subtle errors from initializing Mixin
more than once.
Note: You didn't specify which version of Python you're using. Metaclasses and super
are both better and simpler in Python 3, so I've used Python 3 syntax. For Python 2, you'd need to set the metaclass a different way, and call super
providing the current class object and self
explicitly, which makes it less nice, but then, Python 2 is generally less nice at this point, so consider writing new code for Python 3?
Upvotes: 1
Reputation: 298196
Mixins are used to add functionality (usually methods) to classes by using multiple inheritance.
For example, let's say you want to make a class's __str__
method return everything in uppercase. There are two ways you can do this:
Manually change every single class's __str__
method:
class SomeClass(SomeBase):
def __str__(self):
return super(SomeClass, self).__str__().upper()
Create a mixin class that does only this and inherit from it:
class UpperStrMixin(object):
def __str__(self):
return super(UpperStrMixin, self).__str__().upper()
class SomeClass(SomeBase, UpperStrMixin):
...
In the second example, notice how UpperStrMixin
is completely useless as a standalone class. Its only purpose is to be used with multiple inheritance as a base class and to override your class's __str__
method.
In your particular case, the following will work:
class Mixin:
def __init__(self, option):
...
def meth4(self):
...
self.meth1()
self.meth2()
class NewClass_1(Original_1, Mixin):
def __init__(self, option):
Original_1.__init__(self)
Mixin.__init__(self, option)
...
class NewClass_2(Original_2, Mixin):
def __init__(self, option):
Original_2.__init__(self)
Mixin.__init__(self, option)
...
Even though Mixin.meth1
and Mixin.meth2
aren't defined, this isn't an issue because an instance of Mixin
is never created directly and it's only used indirectly through multiple inheritance.
Upvotes: 1