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
2 Answers 2
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.
-
\$\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\$TheLazyScripter– TheLazyScripter2016年04月15日 10:45:43 +00:00Commented Apr 15, 2016 at 10:45
-
1\$\begingroup\$ @TheLazyScripter There is a difference, mainly involving the
attribute
decorator or other advanced usage. Usingsetattr
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\$301_Moved_Permanently– 301_Moved_Permanently2016年04月15日 12:29:23 +00:00Commented Apr 15, 2016 at 12:29
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
.
-
\$\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\$TheLazyScripter– TheLazyScripter2016年04月15日 10:41:59 +00:00Commented 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 genericAutoAttributeObject
that you'll inherit from in your projects instead ofobject
? 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 tosuper().__init__
. So you're not saving much and add obfuscation about what is going on exactly. \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2016年04月15日 10:54:32 +00:00Commented Apr 15, 2016 at 10:54
p = SpellClass()
leads toTypeError: unbound method __init__() must be called with Character instance as first argument (got nothing instead)
. Please correct that first. \$\endgroup\$