1
\$\begingroup\$

I'm just messing around with class inheritance. I originally had a separate goal in mind but I ended up here, and my new goal was to have a parent that was completely ignorant of it's self, i.e. no default attributes. This would seem to make it easier to subclass, the MeleClass could set; for instance canRange to True during instantiation without modifying the parent's parameters.

Questions:

  • Are there any other ways to do this?
  • How could this be bad?
  • Are there more efficient ways?

class Character:
    def __init__(self, **kwargs):
        for i in kwargs:
            setattr(self, i, kwargs[i])

class MeleClass(Character):
    def __init__(self, name="default", atk=1, deff=1):
        self.canRange = False
        Character.__init__(self, name=name, attack=atk, defence=deff)

class SpellClass(Character):
    def __init__(self, name="default", atk=1, deff=1):
        self.canRange = True
        Character.__init__(self, name=name, attack=atk, defence=deff)


p = MeleClass(name='TheLazyScripter')
print p.name
print p.attack
print p.canRange
\$\endgroup\$
2
  • \$\begingroup\$ Trying to create p = SpellClass() leads to TypeError: unbound method __init__() must be called with Character instance as first argument (got nothing instead). Please correct that first. \$\endgroup\$ Commented Apr 15, 2016 at 7:24
  • \$\begingroup\$ Oops forget my self! Ty! \$\endgroup\$ Commented Apr 15, 2016 at 9:54

2 Answers 2

2
\$\begingroup\$

I would change your Character.__init__ to this:

def __init__(self, **kwargs):
    for key, value in kwargs.iteritems():
        setattr(self, key, value)

It isn't any shorter but it's more clear what's happening. For one thing, i is not a descriptive name by any means. For another thing, you might as well iterate through key-value pairs1 rather than just keys. You use both; why not iterate through both?

I would also change your other __init__ methods. If Character.__init__ sets attributes, why not take advantage of it? Instead of self.canRange = ..., add canRange=... to the arguments to Character.__init__.

That brings me to something else. PEP 8, the Python style guide, says this about instance variables (such as canRange):

Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.


1 You could also do self.__dict__.update(kwargs) but that isn't recommended and seems hackish to me anyway.

\$\endgroup\$
2
  • \$\begingroup\$ I completely agree! I don't Know Why I originally chose not to use Key, Value pairs! As for __dict__update(kwargs) it appears that it does the same exact thing as setattr(self, key, value). I guess my class is kind of hackish!! \$\endgroup\$ Commented Apr 15, 2016 at 10:45
  • 1
    \$\begingroup\$ @TheLazyScripter There is a difference, mainly involving the attribute decorator or other advanced usage. Using setattr you won't be able to override existing names, but using __dict__.update it's possible. See this gist to understand. Depending on your use cases, it may or may not be something you want. \$\endgroup\$ Commented Apr 15, 2016 at 12:29
1
\$\begingroup\$

Since you are using Python 2, you should use new-style classes by explicitly inheriting from object. This is important because, when doing inheritance, you most likely want to (implicitly at least) relly on the __mro__ attribute of these kind of classes. It then let you change Character.__init__(self, ...) into super(<NameOfTheClass>, self).__init__(...) which will help you when you’ll build upon these classes and create a more complex hierarchy.

Now on the topic of adding the attributes to the base class, you should iterate over both the keys and values of kwargs at the same time instead of the keys only and using it later to grab the value:

for attribute, value in kwargs.iteritems():
    setattr(self, attribute, value)

but you could take advantage that you are setting an entire dictionary as attributes of an object. And that the current attributes of said objects are stored in it's inner __dict__ dictionary:

def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

Lastly, instead of setting some attributes manually in your derived classes, you could relly on the mechanism you just wrote to delegate that to the base class. The code would look something like:

class Character(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

class MeleeClass(Character):
    def __init__(self, name="default", atk=1, deff=1):
        super(MeleeClass, self).__init__(name=name, attack=atk, defence=deff, canRange=False)

class SpellClass(Character):
    def __init__(self, name="default", atk=1, deff=1):
        super(SpellClass, self).__init__(name=name, attack=atk, defence=deff, canRange=True)

In the topic of using this particular structure for your code, I see little to no advantage over using plain attributes assignment. It won't be long before you create a class using an attribute that no other have in your hierarchy and things will break when you will try to blindly access that attribute on any Character.

\$\endgroup\$
2
  • \$\begingroup\$ Everything you've said makes total sense, however I question your final assessment. Character is a base class, this means that I will never explicitly try to access it. I will instead access the sub-class (i.e. Mele/Spell). By setting canRange within the sub-class I guarantee that the base has no reference to it (It needs none). The end result is to have a generic class that can be used for anything. Not just some arbitrary Characters! I hope this makes sense! \$\endgroup\$ Commented Apr 15, 2016 at 10:41
  • \$\begingroup\$ @TheLazyScripter And where do you think p.name will do a lookup in the end? Anyway, do I understand correctly if I say that you're after a generic AutoAttributeObject that you'll inherit from in your projects instead of object? In this case... Meh... Go ahead if you feel you'll save on typing. But somehow you're writing the same thing in your call to super().__init__. So you're not saving much and add obfuscation about what is going on exactly. \$\endgroup\$ Commented Apr 15, 2016 at 10:54

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.