6

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.

Community
  • 1
  • 1
user1186742
  • 389
  • 2
  • 9
  • 14
  • 1
    Should a 'CastSpell' object really *be* a 'Wand' object? – Casey Kuball May 20 '12 at 01:23
  • I simply wanted to get the .wandtype attribute, which is why I've used that. It sounds a bit weird, I know. – user1186742 May 20 '12 at 01:24
  • 1
    Why not have a `Spell` class with a `cast` method, that simply takes the wand type as an argument? – Casey Kuball May 20 '12 at 01:27
  • That would make a lot of sense. :D Thank you for the suggestion. It does appear that I'm completely confused as far as inheritance goes, though, so the answers below are quite helpful. – user1186742 May 20 '12 at 01:29
  • Depending upon your needs, you could have a `PhoenixFeatherWand` class, a `Lumus` class, a `WingardiumLeviosa` class, etc. In a typical object-oriented language you might have these inherit from a `Wand` or `Spell` class, but with python being a duck-typed language, you can just have them define the same interface, such as a `cast` method, a `size` attribute, a `name` attribute, etc. – Casey Kuball May 20 '12 at 01:35
  • I'll give this a shot and report back if I have any further problems. Should be a good exercise. Thank you so much!! – user1186742 May 20 '12 at 01:44
  • What are you expecting the `cast_spell`'s `wandtype` to be, given that none of the parameters you create it with correspond to the wand type? – Karl Knechtel May 20 '12 at 04:21
  • Not sure if this answers your question, but: I thought that if I used class inheritance then the `wandtype` parameter that I passed to the my_wand instance `('Phoenix Feather')` would be inherited by the cast_spell instance. Guess not? – user1186742 May 20 '12 at 04:30

3 Answers3

8

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 CastSpells in it: instead of "is-a" (a CastSpell is a kind of Wand), try "has-a" (a Wand has Spells).

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"
TryPyPy
  • 6,214
  • 5
  • 35
  • 63
  • 1
    I am indeed confused, but this helps, so thanks. Is there any way to accomplish what I'm trying to do using class inheritance? – user1186742 May 20 '12 at 01:23
  • Sure, see edit. Now I'll show how you can make Wand have CastSpells :) – TryPyPy May 20 '12 at 01:30
  • Thank you! Is it accurate that I really wouldn't need to create a Wand class if I were to use your second example? It seems a bit redundant... – user1186742 May 20 '12 at 01:54
  • No, you'd still need Wand because it had "fulldesc". Of course, you can move that to CastSpell and get rid of Wand altogether, but imagine you want to have different wands with different spells: a wand class lets you model each wand, so it can be useful. If you only care about spells and casting them, Wand is superfluous indeed. – TryPyPy May 20 '12 at 01:59
  • First of all, you're amazing, and I'm full of gratitude for you taking the time to give me such indepth advice. Truly, thank you. I have a few questions about your example. `self.wandtype = "" # We'll set this when adding to a wand` - what do you mean by that comment? And what does `def __repr__(self): return self.name` do? – user1186742 May 20 '12 at 02:11
  • 1
    Sorry, I removed the code that did that: in `Wand.addspell`, I had a line like `spell.wandtype = self.wandtype`. So that made the spell know in which wand it was... but had a problem: you would only be able to have a spell in one wand that way! So now we pass the wandtype when `cast`ing. The `__repr__()` of a class tells you how to display that class (e.g.: `print my_wand.spells` or `print lumus`). You can customize it to show the exact information you want, `return self.name` makes it show the name. Remove the `__repr__` method and try `print my_wand.spells` to see the default display. – TryPyPy May 20 '12 at 02:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11477/discussion-between-user1186742-and-trypypy) – user1186742 May 20 '12 at 03:52
1

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.]
mpdaugherty
  • 1,118
  • 8
  • 19
  • Thanks for this answer. I get a NameError: global name 'wandtype' is not defined error when I try to implement the first solution. Very good point as far as creating a cast_spell method instead. Thanks. – user1186742 May 20 '12 at 01:31
1

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.

the paul
  • 8,972
  • 1
  • 36
  • 53
  • 1
    As the article points out, `super` is okay to use if you use it consistently, and use only keyword arguments. – Casey Kuball May 20 '12 at 01:26
  • He's not using keyword arguments. His method arguments have different meanings for the different `__init__` functions. This is utterly inappropriate for `super()`. – the paul May 20 '12 at 01:36
  • 2
    This is true. However, he clearly has to clear up something with the current design, and while inheritance probably isn't relevant to his needs, if he wanted to make the `CastSpell` method work, he could morph his initializers to use it by taking more arguments. IMO, it seems strange for `CastSpell.__init__` to pick default arguments for `Wand.__init__`, and not allow the user to customize this. – Casey Kuball May 20 '12 at 01:40
  • Thanks for the answer, @thepaul; and thank you for your insights as well, @Darthfett. Is it possible to not set a default value for wandtype and instead get whatever value the user enters when they initialize the class? – user1186742 May 20 '12 at 01:52
  • 1
    @thepaul wasn't me, but perhaps the downvoter thought your first sentence could be misinterpreted. Technically, `super` exactly solves the problem, though it does cause a different external interface. Upvote from me for the link, just because it explains good usage of super. :P @user1186742 It could easily be fixed by adding the arguments to the constructor, but then it acts just like `super`, in passing all the arguments to the super-class. – Casey Kuball May 20 '12 at 04:11