【问题标题】:Python Class Inheritance AttributeError - why? how to fix?Python 类继承 AttributeError - 为什么?怎么修?
【发布时间】:2012-05-27 01:18:30
【问题描述】:

关于 SO 的类似问题包括:this onethis。我还阅读了所有我能找到的在线文档,但我仍然很困惑。非常感谢您的帮助。

我想在我的 CastSpell 类 lumus 方法中使用 Wand 类 .wandtype 属性。但我不断收到错误“AttributeError:'CastSpell'对象没有属性'wandtype'。”

此代码有效:

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() 

此代码尝试继承,但没有。

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() 

我尝试使用 super() 方法无济于事。我非常感谢您帮助理解 a)为什么类继承在这种情况下不起作用,b)如何让它工作。

【问题讨论】:

  • “CastSpell”对象真的应该“Wand”对象吗?
  • 我只是想获得 .wandtype 属性,这就是我使用它的原因。这听起来有点奇怪,我知道。
  • 为什么没有一个带有cast 方法的Spell 类,它只是将魔杖类型作为参数?
  • 这很有意义。 :D 谢谢你的建议。不过,就继承而言,我确实很困惑,所以下面的答案很有帮助。
  • 根据您的需要,您可以有一个PhoenixFeatherWand 类、一个Lumus 类、一个WingardiumLeviosa 类等。在典型的面向对象语言中,您可能有这些继承自WandSpell 类,但由于 python 是一种鸭式语言,您可以让它们定义相同的接口,例如 cast 方法、size 属性、name 属性等.

标签: python class inheritance


【解决方案1】:

你需要调用超类的 init 方法。否则,不会在当前 CastSpell 实例上设置 wandtype 和 length。

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

或者,您可以在 init 方法之外的对象上添加 wandtype 和 length 作为属性:

class Wand(object):
    wandtype = None
    length = None

然后,它们将始终可用(尽管在初始化之前它们的值为 None)。


但是,您确定 CastSpell 应该是 Wand 的子类吗? CastSpell 是一个动作,听起来更像是 Wand 的一种方法。

class Wand(object):
    [...]
    def cast_spell(self, spell, thing):
         [etc.]

【讨论】:

  • 感谢您的回答。当我尝试实施第一个解决方案时,我收到 NameError: global name 'wandtype' is not defined 错误。就创建 cast_spell 方法而言非常好。谢谢。
【解决方案2】:

简单地说,你在继承自它的类中覆盖Wand.__init__,所以CastSpell.wandtype永远不会在CastSpell中设置。除此之外,my_wand 不能将信息传递给cast_spell,所以你对继承的作用感到困惑。

不管你怎么做,你必须以某种方式将lengthwandtype 传递给CastSpell。一种方法是将它们直接包含到CastSpell.__init__

class CastSpell(Wand):
    def __init__(self, spell, thing, length, wandtype):
        self.spell = spell 
        self.thing = thing
        self.length = length
        self.wandtype = wandtype

另一种更通用的方法是将这两个传递给基类自己的__init__()

class CastSpell(Wand):
    def __init__(self, spell, thing, length, wandtype):
        self.spell = spell 
        self.thing = thing
        super(CastSpell, self).__init__(length, wandtype)

另一种方法是停止让CastSpellWand 继承(CastSpellWand 的一种?还是Wand 的某种东西?),而是让每个魔杖都能够拥有一些@ 987654338@s 在其中:代替“is-a”(CastSpellWand 的一种),尝试“has-a”(Wand 具有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"

【讨论】:

  • 我确实很困惑,但这有帮助,所以谢谢。有什么方法可以使用类继承来完成我想要做的事情?
  • 当然,请参阅编辑。现在我将展示如何让 Wand 拥有 CastSpells :)
  • 谢谢!如果我使用你的第二个例子,我真的不需要创建一个 Wand 类,这是否准确?好像有点多余……
  • 不,您仍然需要 Wand,因为它具有“fulldesc”。当然,您可以将其移至 CastSpell 并完全摆脱 Wand,但想象一下您希望拥有具有不同法术的不同魔杖:魔杖类可以让您对每根魔杖进行建模,因此它会很有用。如果你只关心法术和施法,魔杖确实是多余的。
  • 抱歉,我删除了执行此操作的代码:在 Wand.addspell 中,我有一条类似 spell.wandtype = self.wandtype 的行。所以这让咒语知道它是用哪根魔杖......但有一个问题:你只能在一根魔杖上使用这样的咒语!所以现在我们在casting 时传递 wandtype。类的__repr__() 告诉您如何显示该类(例如:print my_wand.spellsprint lumus)。您可以自定义它以显示您想要的确切信息,return self.name 使其显示名称。去掉__repr__方法,试试print my_wand.spells看默认显示。
【解决方案3】:

是的,super() 不是你想要的。请参阅this article 了解为什么不这样做的详细信息。

在 Python 中对超类的正常调用(不幸的是)是通过引用超类显式完成的。

如果我对您的问题的解释正确,您会想知道为什么.length.wandtype 属性没有出现在CastSpell 的实例中。这是因为没有调用 Wand.init() 方法。你应该这样做:

class CastSpell(Wand):
    def __init__(self, spell, thing):
        Wand.__init__(self, whateverdefaultvalue_youwantforwandtype, default_value_for_length)
        self.spell = spell
        etc.

也就是说,您似乎没有正确使用继承。 CastSpell 是一个“动作”,而 wand 是一个“东西”。这并不是一个对继承有意义的抽象。

【讨论】:

  • 正如文章所指出的,super 是可以使用的,如果你一直使用它,并且只使用关键字参数。
  • 他没有使用关键字参数。他的方法参数对于不同的__init__ 函数有不同的含义。这完全不适合super()
  • 这是真的。然而,他显然必须用当前设计清理一些东西,虽然继承可能与他的需求无关,但如果他想让CastSpell 方法工作,他可以​​通过接受更多参数来改变他的初始化程序以使用它. IMO,CastSpell.__init__Wand.__init__ 选择默认参数似乎很奇怪,并且不允许用户自定义它。
  • 感谢@thepaul 的回答;并感谢您的见解,@Darthfett。是否可以不为 wandtype 设置默认值,而是获取用户在初始化类时输入的任何值?
  • @thepaul 不是我,但可能反对者认为您的第一句话可能被误解了。从技术上讲,super 完全解决了这个问题,尽管它确实会导致不同的外部接口。我为链接投票,只是因为它解释了 super 的良好用法。 :P @user1186742 它可以很容易地通过向构造函数添加参数来修复,但是它就像super一样,将所有参数传递给超类。
猜你喜欢
  • 2017-11-03
  • 2011-04-30
  • 1970-01-01
  • 1970-01-01
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多