Similar questions on SO include: this one and this. I've also read through all the online documentation I can find, but I'm still quite confused. I'd be grateful for your help.
I want to use the Wand class .wandtype attribute in my CastSpell class lumus method. But I keep getting the error "AttributeError: 'CastSpell' object has no attribute 'wandtype'."
This code works:
class Wand(object):
def __init__(self, wandtype, length):
self.length = length
self.wandtype = wandtype
def fulldesc(self):
print "This is a %s wand and it is a %s long" % (self.wandtype, self.length)
class CastSpell(object):
def __init__(self, spell, thing):
self.spell = spell
self.thing = thing
def lumus(self):
print "You cast the spell %s with your wand at %s" %(self.spell, self.thing)
def wingardium_leviosa(self):
print "You cast the levitation spell."
my_wand = Wand('Phoenix-feather', '12 inches')
cast_spell = CastSpell('lumus', 'door')
my_wand.fulldesc()
cast_spell.lumus()
This code, with attempted inheritance, doesn't.
class Wand(object):
def __init__(self, wandtype, length):
self.length = length
self.wandtype = wandtype
def fulldesc(self):
print "This is a %s wand and it is a %s long" % (self.wandtype, self.length)
class CastSpell(Wand):
def __init__(self, spell, thing):
self.spell = spell
self.thing = thing
def lumus(self):
print "You cast the spell %s with your %s wand at %s" %(self.spell, self.wandtype, self.thing) #This line causes the AttributeError!
print "The room lights up."
def wingardium_leviosa(self):
print "You cast the levitation spell."
my_wand = Wand('Phoenix-feather', '12 inches')
cast_spell = CastSpell('lumus', 'door')
my_wand.fulldesc()
cast_spell.lumus()
I've tried using the super() method to no avail. I'd really appreciate your help understanding a) why class inheritance isn't working in this case, b) how to get it to work.
To put it simply, you override Wand.__init__
in the class that inherits from it, so CastSpell.wandtype
is never set in CastSpell
. Besides that, my_wand
can't pass information into cast_spell
, so you're confused about the role of inheritance.
Regardless of how you do it, you have to somehow pass length
and wandtype
to CastSpell
. One way would be to include them directly into CastSpell.__init__
:
class CastSpell(Wand):
def __init__(self, spell, thing, length, wandtype):
self.spell = spell
self.thing = thing
self.length = length
self.wandtype = wandtype
Another, more generic way would be to pass these two to the base class' own __init__()
:
class CastSpell(Wand):
def __init__(self, spell, thing, length, wandtype):
self.spell = spell
self.thing = thing
super(CastSpell, self).__init__(length, wandtype)
Another way would be to stop making CastSpell
inherit from Wand
(is CastSpell
a kind of Wand
? or something a Wand
does?) and instead make each Wand be able to have some CastSpell
s in it: instead of "is-a" (a CastSpell
is a kind of Wand
), try "has-a" (a Wand
has Spell
s).
Here's a simple, not so great way to have a Wand store spells:
class Wand(object):
def __init__(self, wandtype, length):
self.length = length
self.wandtype = wandtype
self.spells = {} # Our container for spells.
# You can add directly too: my_wand.spells['accio'] = Spell("aguamenti", "fire")
def fulldesc(self):
print "This is a %s wand and it is a %s long" % (self.wandtype, self.length)
def addspell(self, spell):
self.spells[spell.name] = spell
def cast(self, spellname):
"""Check if requested spell exists, then call its "cast" method if it does."""
if spellname in self.spells: # Check existence by name
spell = self.spells[spellname] # Retrieve spell that was added before, name it "spell"
spell.cast(self.wandtype) # Call that spell's cast method, passing wandtype as argument
else:
print "This wand doesn't have the %s spell." % spellname
print "Available spells:"
print "\n".join(sorted(self.spells.keys()))
class Spell(object):
def __init__(self, name, target):
self.name = name
self.target = target
def cast(self, wandtype=""):
print "You cast the spell %s with your %s wand at %s." % (
self.name, wandtype, self.target)
if self.name == "lumus":
print "The room lights up."
elif self.name == "wingardium leviosa":
print "You cast the levitation spell.",
print "The %s starts to float!" % self.target
def __repr__(self):
return self.name
my_wand = Wand('Phoenix-feather', '12 inches')
lumus = Spell('lumus', 'door')
wingardium = Spell("wingardium leviosa", "enemy")
my_wand.fulldesc()
lumus.cast() # Not from a Wand! I.e., we're calling Spell.cast directly
print "\n\n"
my_wand.addspell(lumus) # Same as my_wand.spells["lumus"] = lumus
my_wand.addspell(wingardium)
print "\n\n"
my_wand.cast("lumus") # Same as my_wand.spells["lumus"].cast(my_wand.wandtype)
print "\n\n"
my_wand.cast("wingardium leviosa")
print "\n\n"
my_wand.cast("avada kadavra") # The check in Wand.cast fails, print spell list instead
print "\n\n"
Yeah, super()
is not what you want. Refer to this article for details on why not.
Normal calls to the superclass in Python are (unfortunately) done explicitly by referring to the superclass.
If I'm interpreting your question right, you're wondering why the .length
and .wandtype
attributes aren't showing up in instances of CastSpell
. This is because the Wand.init() method isn't being called. You ought to do it like this:
class CastSpell(Wand):
def __init__(self, spell, thing):
Wand.__init__(self, whateverdefaultvalue_youwantforwandtype, default_value_for_length)
self.spell = spell
etc.
That said, you don't seem to be using inheritance right. CastSpell is an "action" while wand is a "thing". That isn't really an abstraction that makes good sense for inheritance.
You need to call the superclass's init method. Otherwise, wandtype and length never get set on the current CastSpell instance.
class CastSpell(Wand):
def __init__(self, spell, thing):
super(CastSpell, self).__init__(A, B) # A, B are your values for wandtype and length
self.spell = spell
self.thing = thing
Alternatively, you could add wandtype and length as attributes on the object outside of the init method:
class Wand(object):
wandtype = None
length = None
Then, they will always be available (though they will have a value of None until they've been initialized).
However, are you sure that CastSpell should be a subclass of Wand? CastSpell is an action, which sounds more like it should be a method of Wand.
class Wand(object):
[...]
def cast_spell(self, spell, thing):
[etc.]