Is there any way to connect 2 classes (without merging them in 1) and thus avoiding repetition under statement if a: in class Z?
class A:
def __init__(self, a):
self.a = a
self.b = self.a + self.a
class Z:
def __init__(self, z, a=None):
self.z = z
if a: # this part seems like repetition
self.a = a.a
self.b = a.b
a = A('hello')
z = Z('world', a)
assert z.a == a.a # hello
assert z.b == a.b # hellohello
Wondering if python has some tools. I would prefer to avoid loop over instance variables and using setattr.
Something like inheriting from class A to class Z, Z(A) or such.
Conceptually, the standard techniques for associating an
Ainstance with aZinstance are:Using composition (and delegation)
"Composition" simply means that the
Ainstance itself is an attribute of theZinstance. We call this a "has-a" relationship: everyZhas anAthat's associated with it.In normal cases, we can simply pass the
Ainstance to theZconstructor, and have it assign an attribute in__init__. Thus:Notice that the attribute for the
ainstance is specially named to avoid conflicting with theAclass attribute names. This is to avoid ambiguity (calling it.amakes one wonder whethermy_z.ashould get the.aattribute from theAinstance, or the entire instance), and to mark it as an implementation detail (normally, outside code won't have a good reason to get the entireAinstance out of theZ; the entire point of delegation is to make it so that users ofZdon't have to worry aboutA's interface).One important limitation is that the composition relationship is one-way by nature:
self._a = agives theZclass access toAcontents, but not the other way around. (Of course, it's also possible to build the relationship in both directions, but this will require some planning ahead.)"Delegation" means that we use some scheme in the code, so that looking something up in a
Zinstance finds it in the composedAinstance when necessary. There are multiple ways to achieve this in Python, at least two of which are worth mentioning:Explicit delegation per attribute
We define a separate
propertyin theZclass, for each attribute we want to delegate. For example:For methods, using the same
propertyapproach should work, but it may be simpler to make a wrapper function and calling it:Delegation via
__getattr__The
__getattr__magic ("dunder") method allows us to provide fallback logic for looking up an attribute in a class, if it isn't found by the normal means. We can use this for theZclass, so that it will try looking within its_aif all else fails. This looks like:Here, we use the free function
getattrto look up the name dynamically within theAinstance.Using inheritance
This means that each
Zinstance will, conceptually, be a kind ofAinstance - classes represent types, and inheritingZfromAmeans that it will be a subtype ofA.We call this an "is-a" relationship: every
Zinstance is anAinstance. More precisely, aZinstance should be usable anywhere that anAinstance could be used, but alsoZmight contain additional data and/or use different implementations.This approach looks like:
The
superfunction is magic that finds theA.__init__function, and calls it as a method on theZinstance that's currently being initialized. (That is:selfwill be the same object for both__init__calls.)This is clearly more convenient than the delegation and composition approach. Our
Zinstance actually hasaandbattributes as well asz, and also actually has aactionmethod. Thus, code likemy_z.action()will use the method from theAclass, and accessing theaandbattributes of aZinstance works - because theZinstance actually directly contains that data.Note in this example that the code for
actionnow tries to useself.z. this won't work for anAinstance constructed directly, but it does work when we construct aZand callactionon it:We say that such an
Aclass, which doesn't properly function on its own, is abstract. (There are more tools we can use to prevent accidentally creating an unusable baseA; these are outside the scope of this answer.)This convenience comes with serious implications for design. It can be hard to reason about deep inheritance structures (where the
Aalso inherits fromB, which inherits fromC...) and especially about multiple inheritance (Zcan inherit fromBas well asA). Doing these things requires careful planning and design, and a more detailed understanding of howsuperworks - beyond the scope of this answer.Inheritance is also less flexible. For example, when the
Zinstance composes anAinstance, it's easy to swap thatAinstance out later for another one. Inheritance doesn't offer that option.Using mixins
Essentially, using a mixin means using inheritance (generally, multiple inheritance), even though we conceptually want a "has-a" relationship, because the convenient usage patterns are more important than the time spent designing it all up front. It's a complex, but powerful design pattern that essentially lets us build a new class from component parts.
Typically, mixins will be abstract (in the sense described in the previous section). Most examples of mixins also won't contain data attributes, but only methods, because they're generally designed specifically to implement some functionality. (In some programming languages, when using multiple inheritance, only one base class is allowed to contain data. However, this restriction is not necessary and would make no sense in Python, because of how objects are implemented.)
One specific technique common with mixins is that the first base class listed will be an actual "base", while everything else is treated as "just" an abstract mixin. To keep things organized while initializing all the mixins based on the original
Zconstructor arguments, we use keyword arguments for everything that will be passed to the mixins, and let each mixin use what it needs from the**kwargs.We can use this like:
The code in
AMixincan't stand on its own:but when the
Zinstance has bothZBaseandAMixinas bases, and is used to callfunc, thezattribute can be found - because nowselfis aZinstance, which has that attribute.The
superlogic here is a bit tricky. The details are beyond the scope of this post, but suffice to say that with mixin classes that are set up this way,superwill forward to the next, sibling base ofZ, as long as there is one. It will do this no matter what order the mixins appear in; theZinstance determines the order, andsupercalls whatever is "next in line". When all the bases have been consulted, next in line isRoot, which is just there to intercept thekwargs(since the last mixin doesn't "know" it's last, and passes them on). This is necessary because otherwise, next in line would beobject, andobject.__init__raises an exception if there are any arguments.For more details, see What is a mixin and why is it useful?.