假设我有一个名为'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中的工作原理?还是别的什么?
感谢您的帮助。
答案 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对象并使其完全相同,除了一些额外的items
,victory
...属性。如果您将所有这些努力编码统计数据作为职业类别的属性,您为什么要让玩家拥有特定的独立统计数据,就好像职业不存在一样?你不会。
但到底该怎么办?我认为职业不能拥有物品。我们没有像这样对职业进行编码,无论如何,对于一个拥有任何东西的教学和思想系统来说,这有点奇怪。所以我想避免不得不继承玩家的职业。即使玩家可以拥有职业,玩家也不是职业的延伸。因此,我们想要的是一种构图。
当您获取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类添加方法,例如,让我们添加rageOn
和rageOff
方法:
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()
行。
继续编码。它变得更好。