2

I'm facing the following problem:

  • There's a base class Unit, which has a couple of attributes, e.g. id, type, name, skills, ...
  • There are different types of units, some of them have additional attributes like health, attack, or tribe, so naturally the relevant subclasses HealthUnit, AttackUnit, etc. exist as well.
  • There are a few units that have multiple of these attributes, e.g. HealthAttackUnit, or HealthAttackTribeUnit.

I want to avoid coding like this:

class Unit(object):
 def __init__(self, id, type, name, skills):
 self.id= id
 self.type= type
 self.name= name
 self.skills= skills
class HealthUnit(Unit):
 def __init__(self, id, type, name, skills, health):
 Unit.__init__(self, id, type, name, skills)
 self.health= health
class AttackUnit(Unit):
 def __init__(self, id, type, name, skills, attack):
 Unit.__init__(self, id, type, name, skills)
 self.attack= attack
class HealthAttackUnit(HealthUnit, AttackUnit):
 def __init__(self, id, type, name, skills, health, attack):
 HealthUnit.__init__(self, id, type, name, skills, health)
 AttackUnit.__init__(self, id, type, name, skills, attack)

for obvious reasons.


I tried to use dict unpacking as a workaround, kind of like this:

class HealthUnit(Unit):
 def __init__(self, health, **args):
 Unit.__init__(self, **args)
 self.health= health

but even this comes with lots of duplicate code:

class HealthAttackUnit(HealthUnit, AttackUnit):
 def __init__(self, health, attack, **args):
 HealhUnit.__init__(self, health=health, **args)
 AttackUnit.__init__(self, attack=attack, **args)
class HealthAttackTribeUnit(HealthUnit, AttackUnit, TribeUnit):
 def __init__(self, health, attack, tribe, **args):
 HealhUnit.__init__(self, health=health, **args)
 AttackUnit.__init__(self, attack=attack, **args)
 TribeUnit.__init__(self, tribe=tribe, **args)

Plus, this will call Unit.__init__ multiple times, which is less than ideal.

So, the question is: Is there a better, less copy/paste, way of doing this?

Update: The dict unpacking is nice and all, but it's still a little annoying having to call all constructors with keyword arguments. I'd prefer a solution without **kwargs, but I'm guessing there probably isn't one?

asked Nov 12, 2014 at 15:38
6
  • If you use super(), you won't have to call each inherited __init__ separately - see e.g. stackoverflow.com/q/576169/3001761. You will need to use *args, **kwargs, too. Commented Nov 12, 2014 at 15:42
  • Unless you instantiate objects of HealthUnit and AttackUnit, you might want to be interested in mixins instead. Commented Nov 12, 2014 at 15:45
  • @poke those are mixins, surely? Commented Nov 12, 2014 at 15:49
  • @DanielRoseman Since they inherit from the general Unit, instead of just providing health or attack stuff, I don’t think so. Commented Nov 12, 2014 at 15:51
  • @DanielRoseman: No, they aren't mixins, because need to instantiate HealthUnit and AttackUnit objects. Commented Nov 12, 2014 at 15:59

1 Answer 1

4

Yes, this is exactly why the super function exists.

Ensure all your __init__ files call super, and Python will work out the MRO for you and call the relevant classes in turn.

class HealthUnit(Unit):
 def __init__(self, **kwargs):
 self.health = kwargs.pop('health')
 super(HealthUnit, self).__init__(**kwargs)
class AttackUnit(Unit):
 def __init__(self, **kwargs):
 self.attack = kwargs.pop('attack')
 super(AttackUnit, self).__init__(**kwargs)
class TribeUnit(Unit):
 def __init__(self, **kwargs):
 self.tribe = kwargs.pop('tribe')
 super(TribeUnit, self).__init__(**kwargs) 
class HealthAttackTribeUnit(HealthUnit, AttackUnit, TribeUnit):
 pass

See also Python core contributor (and occasional SO poster) Raymond Hettinger's article Super considered super, but note the syntax in that post is for Python 3, there's a separate link to the version 2 code.

answered Nov 12, 2014 at 15:42
Sign up to request clarification or add additional context in comments.

5 Comments

I'm not sure I would actually remove e.g. kwargs['health'] before calling the superclass, I'd do e.g. kwargs.get('health'), which also allows setting a default. +1 otherwise.
The reason I do that is that higher up the hierarchy the classes may not be expecting those arguments: and if they're not set up to accept **kwargs it will cause a TypeError. Certainly that will happen if you get all the way to object. Best to remove the arguments when you deal with them, I reckon.
This is a pretty good solution, but it forces me to call all constructors with keyword arguments. That's not really a problem, but I still want to ask: Is there a way around the dict unpacking? P.S.: I finally understand what super does, thank you, and take my upvote, kind sir.
There's not reallly a good way of doing it with args rather than kwargs. The problem is that you'd need to guarantee the MRO, which depends on the inheritance graph. Raymond's article goes into detail about exactly how that is worked out if you're interested, but even he recommends using kwargs over args for something like this.
I see. Oh well. I'll just add it to the pile of things I don't like about python.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.