Python Class Inheritance AttributeError - why? how

2020-07-11 05:57发布

问题:

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.

回答1:

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"


回答2:

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.



回答3:

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.]