关于从子类中提取对象信息的问题

时间:2016-04-20 22:23:36

标签: python

如果对我有类似的问题,我道歉。我对Python很新,但仍习惯于OOP。话虽这么说,如果这个社区中众多善良的灵魂中的一个可以帮助我的具体情况,或者指出我所提出的类似问题的方向, - 虽然我想要注意谷歌搜索不是帮助和每个问题与类似的错误是如此广泛的不同,并涵盖了许多不同的项目,我很难掌握解决方案 - 非常感谢。

假设我有一个名为'objects.py'的文件来存储我正在开发的基于文本的游戏的大部分类对象,并且在该文件中我有一个名为'Occupation'的类。类'Occupation'包含用作'Player'类修饰符的变量,稍后将对其进行描述。

这个想法是在根文件中,即'root.py',玩家将能够从玩家职业列表中进行选择,例如'wizard'或'knight',并从各个Occupation类中获取信息,替换当前的Player变量信息。

'player.py'文件:

import objects

class Player(object):
    nextlevel = 30
    # 'nextlevel' is the gold required for a level up.
    def __init__(self):
        self.name = ''
        self.level = 1
        self.health = 1
        self.stamina = 0
        self.mana = 0
        self.skills = {'str': 0, 'def': 0, 'dex': 0, 'int': 0, 'chr': 0, 'lck': 0}
        self.spells = []
        self.inventory = [{'armor': [objects.PrisonerRobes()], 'weapons': [objects.IronDagger()], 'bows': [], 'potions': [], 'misc': []}, objects.Gold(0), objects.Arrow(0)]
        self.victory = False

'objects.py'文件:

class Occupation(object):
    # Base class for all occupations / classes, e.g. 'knight' or 'wizard'.
    classlist = ['warrior', 'knight', 'paladin', 'druid', 'warlock', 'wizard']
    def __init__(self, name, hp, sp, mp, strv, defv, dexv, intv, chrv, lckv):
        # The variable names of 'strv', 'defv', etc. stand for 'strength value', 'defence value', etc.
        self.name = name
        self.health = health
        self.stamina = stamina
        self.mana = mana
        self.strv = strv    #skill values
        self.defv = defv    
        self.dexv = dexv
        self.intv = intv
        self.chrv = chrv
        self.lckv = lckv

    def __str__(self):
        details = '{0}: HP: {1}, SP: {2}, MP: {3} // [STR: {4}, DEF: {5}, DEX: {6}, INT: {7}, CHR: {8}, LCK: {9}]'
        return details.format(self.name, self.health, self.stamina, self.mana, self.strv, self.defv, self.dexv, self.intv, self.chrv, self.lckv)

class Warrior(Occupation):
    def __init__(self):
        super(Warrior, self).__init__('Warrior', 75, 50, 25, 12, 8, 12, 5, 8, 5)

'root.py'文件:

from player import Player
import objects

def new_game():
    while True:
        print 'Only one class, and it is Warrior!'
        print objects.Warrior()
        classchoice = raw_input('Please type the full name of the class you wish to select.: ').lower()
        playerclass = classchoice.title()
        if classchoice != '':
            characterclass = getattr(objects, playerclass)
            player.health = characterclass.health
            player.stamina = characterclass.stamina
            player.mana = characterclass.mana
            player.skills['str'] = characterclass.strv
            player.skills['def'] = characterclass.defv
            player.skills['dex'] = characterclass.dexv
            player.skills['int'] = characterclass.intv
            player.skills['chr'] = characterclass.chrv
            player.skills['lck'] = characterclass.lckv
            print 'You have chosen {0} as your class.'.format(characterclass.name)
            confirm = raw_input('Is this correct? Type [Y] or [N] for yes/no respectively.: ').lower()
            if confirm == 'y':
                break
            elif confirm == 'n':
                continue
            else:
                print 'Not a valid keystroke!'
                continue

选择玩家类时输入'warrior'时出现问题:

line 48[in my original file]: player.health = characterclass.health
AttributeError: type object 'Warrior' has no attribute 'health'

在尝试更新预先存在的Player值时,似乎无法引用我想要的变量。据我所知,“勇士”课程的变量是“健康”,但我似乎无法访问它。我是否误解了类和子类在Python中的工作原理?还是别的什么?

感谢您的帮助。

3 个答案:

答案 0 :(得分:2)

== getattr()返回除对象本身之外的对象的named属性的值。 字符类不是characterclass = getattr(objects, playerclass)的实例,因此您无法引用属性Warrior

  

getattr(object,name [,default])   返回object的named属性的值。 name必须是一个字符串。如果字符串是对象属性之一的名称,则结果是该属性的值。例如,getattr(x,' foobar')等同于x.foobar。如果named属性不存在,则返回default(如果提供),否则引发AttributeError。

您需要将warrior实例创建为:

health

答案 1 :(得分:2)

我想不同意Charles Merriam的回答。我喜欢不同的课程。是的,一开始他们看起来都很相似,只是有不同的统计数据。但是稍后你可以扩展它们以保持多种功能。例如特殊修饰符,例如" rage"为战士或"冥想"对于巫师。

如果您将所有鸡蛋放在一个Character类中,并且只是将不同的类表示为Character类的字符串属性,那么您将无法在以后进行此扩展。事实上,当我发布此帖并看到Charles Merriam的回答时,我在最后添加了rage方法到Warrior。这些是我必须改变的唯一因素,因为我使用了你的继承模型。 (原来我只是想向你展示作文。)

的组合物

在我展示这种继承的东西可以回到我认为你做错了什么之前。我认为你已经为你的职业创建了一个类继承模型,但后来迷失了方向,并希望将它作为属性在Player类中全部推送。

更多动手:你定义了一个持有统计数据的职业类(hp,mana,stamina,dexterity ......)。然后你创建了一个Player对象并使其完全相同,除了一些额外的itemsvictory ...属性。如果您将所有这些努力编码统计数据作为职业类别的属性,您为什么要让玩家拥有特定的独立统计数据,就好像职业不存在一样?你不会。

但到底该怎么办?我认为职业不能拥有物品。我们没有像这样对职业进行编码,无论如何,对于一个拥有任何东西的教学和思想系统来说,这有点奇怪。所以我想避免不得不继承玩家的职业。即使玩家可以拥有职业,玩家也不是职业的延伸。因此,我们想要的是一种构图。

当您获取Occupation对象时,将其作为属性分配给Player对象。

class CharClass(object):
    #these stats have to be defined for EVERY occupation possible
    #This can be used as a base class for players without defined classes yet
    #by defining the default values of __init__
    def __init__(self, name="Unknown", health=30, stamina=10, mana=10,
                 strv=1, defv=1, dexv=1, intv=0, chrv=0, lckv=0):
        self.name = name
        self.health = health
        self.stamina = stamina
        self.mana = mana
        self.strv = strv    #skill values
        self.defv = defv    
        self.dexv = dexv
        self.intv = intv
        self.chrv = chrv
        self.lckv = lckv

    def __str__(self):
        details = '{0}: HP: {1}, SP: {2}, MP: {3} \n'+\
                  '[STR: {4}, DEF: {5}, DEX: {6}, INT: {7}, CHR: {8}, LCK: {9}]'
        return details.format(self.name, self.health, self.stamina, self.mana, self.strv, 
                              self.defv, self.dexv, self.intv, self.chrv, self.lckv)


class Warrior(CharClass):
    def __init__(self):
        super(Warrior, self).__init__(health=100, stamina=50, dexv=20)

    def __str__(self):
        parentstring = super(Warrior, self).__str__()
        return "You are a Warrior \n"+parentstring

class Player(object):
    def __init__(self, charclass=CharClass(), items=[], spells=[], victory=False):
        self.charclass = charclass #the generic class is the default one
        self.items = items
        self.spells = spells

现在我们使用了合成,你不必单独为玩家设置任何统计数据。这使您的代码更多更短,更易读。您可以通过Warrior对象访问它们,Warrior对象是您的玩家类的属性,如下所示:

newplayer = Player()
print newplayer
print newplayer.charclass

print

#graduating to a Warrior
newplayer.charclass = Warrior() 
print newplayer
print newplayer.charclass

print

#there's a snag though. Every time now if you want to print or change Players health
#(or some other attribute) you'd have to spell all of this:

print newplayer.charclass.health
newplayer.charclass.health += 100
print newplayer.charclass.health

#this is because Player object contains a Warrior object in it and what you really change is that
#Warrior object and not the Player.

print type(newplayer.charclass)

上述代码的输出为:

<__main__.Player object at 0x000000000427A550>
Unknown: HP: 30, SP: 10, MP: 10 
[STR: 1, DEF: 1, DEX: 1, INT: 0, CHR: 0, LCK: 0]

<__main__.Player object at 0x000000000427A550>
You are a Warrior 
Unknown: HP: 100, SP: 50, MP: 10 
[STR: 1, DEF: 1, DEX: 20, INT: 0, CHR: 0, LCK: 0]

100
200
<class '__main__.Warrior'>

无缝构图

围绕这个障碍有一个技巧/解决方法,尽管对于新手来说它有点困难。不要烦恼它,在世界变得更有意义的一个月内回顾它。

只要无法找到属性,就会调用特殊函数__getattr____setattr__。我们会覆盖这些函数来拦截特殊符号.(在self.attribute}和=中的作用。我们不是设置或获取Player类的属性,而是让它们有时设置/获取合成的self.charclass对象的属性!

class Player(object):
    def __init__(self, charclass=CharClass(), items=[], spells=[], victory=False):
        self.charclass = charclass
        self.items = items
        self.spells = spells

    def __getattr__(self, attr):
            return getattr(self.charclass, attr)

    def __setattr__(self, attr, val):
        if "charclass" in vars(self) and attr in vars(self.charclass):
            self.charclass.__dict__[attr] = val     
        else:
            self.__dict__[attr] = val

有了这个,我们可以再次这样做(无需在任何地方写charclass

print newplayer.health
newplayer.health -= 50
print newplayer.health

输出是:

200
150

很难与__getattr____setattr__一起使用而不会出错。它们不容易被覆盖。

使用__getattr__会更容易,只要Player对象中没有相同的名称,它就会自动被调用。因此,您必须小心的是不在Player中添加具有相同名称的新属性。

但是当玩家对象尚未拥有任何属性时,__setattr__函数也会调用__init__。如果我们不检查,我们会收到错误。这是if "charclass" in vars(self)的作用;检查__init__是否为我们的对象添加了charclass属性。

但是如果我们只检查charclass属性,当我们尝试添加另一个属性时会发生什么?即行中的第二个:items?会调用__setattr__,它会检查charclass中的self,它会看到它在那里,然后它会将项目添加到self.charclass而不是self !实际上,它会向Warrior对象添加属性,而不是Player对象。

我们如何向Player对象添加新属性呢?答案是我们只设置已存在的Warrior对象属性。 Warrior(self.charclass)中不存在的所有属性都设置为Player实例(self.__dict__[attr] = val)的属性。这就是and attr in vars(self.charclass)的含义。

继承和组合有助于稍后在

上扩展代码

跳回到顶部,为什么我不同意Charles Merriam的回答。要立即扩展和更改代码的功能,您还需要做的就是向Warrior类添加方法,例如,让我们添加rageOnrageOff方法:

class Warrior(CharClass):
    #this class basically just defines different default values than CharClass
    def __init__(self):
        super(Warrior, self).__init__(health=100, stamina=50, dexv=20)

    def __str__(self):
        parentstring = super(Warrior, self).__str__()
        return "You are a Warrior \n"+parentstring

    def rageON(self):
        print "You are in a blood-rage +30 attack modifier"
        self.stamina += 30

    def rageOFF(self):
        print "You have calmed down."
        self.stamina -= 30

他们将立即在您的播放器类中提供,而无需更改播放器。

newplayer.rageON()
print newplayer.charclass
print "----------------------------------------------------------------------------------------"
newplayer.rageOFF()
print newplayer.charclass

输出:

You are in a blood-rage +30 attack modifier
You are a Warrior 
Unknown: HP: 150, SP: 80, MP: 10 
[STR: 1, DEF: 1, DEX: 20, INT: 0, CHR: 0, LCK: 0]
----------------------------------------------------------------------------------------
You have calmed down.
You are a Warrior 
Unknown: HP: 150, SP: 50, MP: 10 
[STR: 1, DEF: 1, DEX: 20, INT: 0, CHR: 0, LCK: 0]

如果你在查尔斯所建议的玩家或角色中完成了所有这些操作;你必须向Player类添加方法rage。在其中你必须检查你是否真的&#34;战士&#34;或不,然后才能执行它。

除非你意识到每个课程的每一个特殊效果,你都要编写这样的方法,这听起来并不那么重要。并且它们都必须位于定义Player类的单个文件中。毕竟听起来确实很糟糕。我不知道为什么我写了这一切,但现在投入太多而不发布。遗憾。

答案 2 :(得分:0)

面向对象的类结构有点令人困惑。

第一个选项,忽略子类。在您的示例中,“战士”和“向导”之间的唯一区别是数据。只需要一个Character类并像初始化其他数据一样设置数据:

 grak = Player()
 grak.occupation = Occupation('Warrior', 75, 50, ....)

或者,向Occupation类添加一个有用的构造函数:

@classmethod
def new_warrior(cls):
     return Occupation('Warrior', 75, 50, ....)

# and use this way
grak.occupation = Occupation.new_warrior()

其次,在需要时创建子类的实例,如self.occupation = Warrior()。要获得初始实例, 您可以使用几种不同的方法来制作工厂方法。最容易理解的是这样的函数:

  def new_occupation(occupation_string):
      if occupation == "Warrior":
          return Warrior()
      if occupation == "Wizard":
          return Wizard()
      ...

然后您可以输入my_occupation = new_occupation(playerclass)my_occupation.strength = 3。删除getattr()调用。

在为每个实例设计代码时,您希望使用子类 将具有不同的数据,不同的类将具有不同的行为,并且所有子类都承诺实现单个合同。例如,您的职业可能会承诺实施attack()方法。向导会对满月造成双重伤害,因此其attack()方法具有 不同的代码。当您调用myPlayer.occupation.attack()时,将调用正确的attack()方法,这意味着该实例实际属于的子类。如果您以后添加“魔术师”课程,则不会更改myPlayer.occupation.attack()行。

继续编码。它变得更好。