my apologies if there are similar questions in regards to mine. I'm rather new to Python, and still getting used to OOP. That being said, if one of the many kind souls in this community could help in regards to my specific situation, or point me in the direction of similar questions that have been asked, -- though I'd like to note that Googling was no help and each question w/ similar errors were so widely different, and spanned so many different projects, I had a hard time grasping the solutions -- it would be greatly appreciated.
Let's say I have a file named 'objects.py' to store most of my class objects for a text based game I'm developing, and in that file I have a class named 'Occupation'. Class 'Occupation' holds variables that are used as modifiers for the 'Player' class that will be described a little more later on.
The idea is that in the root file, namely 'root.py', the player will be able to choose from a list of player occupations, e.g. 'wizard' or 'knight', and have the information from the respective Occupation classes replace the current Player variable information.
'player.py' file:
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' file:
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' file:
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
The problem appears when typing 'warrior' when choosing a player class.:
line 48[in my original file]: player.health = characterclass.health
AttributeError: type object 'Warrior' has no attribute 'health'
It seems that I can't reference the variables that I want to, when trying to update pre-existing Player values. The 'Warrior' class, as far as I can tell, has the variable 'health', but I can't seem to access it. Am I misunderstanding how classes and subclasses work in Python? Or is it something else?
Thanks for any help.
I would like to disagree with Charles Merriam's answer. I fancy the different classes. Yes in the beginning they all look alike and just have different stats. But later on you can extend them to hold multiple functionalities. For example special modifiers such as "rage" for Warrior or "meditation" for Wizard.
If you put all your eggs in a single Character class and just represent different classes as a string attribute of Character class, then you won't be able to do this extending later on. In fact I added the rage method to Warrior at the very end when I came to post this and saw Charles Merriam's answer. And those are the only lines I had to change because I used this inheritance model of yours. (Originally I only wanted to show you composition.)
Before I showcase what this inheritance thing can do back to what I think you're doing wrong. I think You've created a class inheritance model for your Occupations, but then lost your way and wanted to push it all in the Player class as attributes.
To be more hands on: You defined an Occupation class that holds stats (hp, mana, stamina, dexterity ...). Then you created a Player object and made it exactly the same, except for some extra items, victory... attributes. If you've spent all this effort coding stats as attributes of Occupation classes why would you want for the Player to have specific, independent stats as if Occupations don't exist? You wouldn't.
But what to do exactly? I figure an Occupation can't own items. We haven't coded Occupation like that and it's a bit weird for a system of teachings and ideas to own anything anyhow. So I want to avoid having to inherit Occupations with the Player. A Player is not an extension of Occupation even if a Player can have an Occupation. Therefore, what we want is a composition.
That's when you take an Occupation object, and assign it as an attribute to a Player object.
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
Now that we used composition, you don't have to set any of the stats for player individually. That makes your code a lot shorter and more readable. You can access those through a Warrior object, which is an attribute of your player class like this:
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)
Output of the above code would be:
<__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'>
There's a trick/workaround around the snag, albeit it's a bit harder for someone new to get it. Don't fret it, look it back up in a month when the world makes more sense.
Special functions __getattr__ and __setattr__ are called whenever the attribute couldn't be found where it was expected. We override those functions to intercept what the special signs . (in i.e. self.attribute) and = do. Instead of those operators setting or getting the attributes of Player class we make them sometimes set/get the attributes of the composited self.charclass object instead!
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
With this in place we can do this again (without having to write charclass everywhere:
print newplayer.health
newplayer.health -= 50
print newplayer.health
Output is:
200
150
It's hard to work with __getattr__ and __setattr__ without getting errors. They're not easy functions to overwrite.
It's easier with __getattr__, it will automatically get called whenever there's nothing with the same name in the Player object. So all you have to be careful of is not adding new attributes with the same name in Player.
But __setattr__ will also get called in the __init__ function when Player object doesn't have any attributes yet. If we don't check for that, we will get an error. That's what if "charclass" in vars(self) does; checks if __init__ added charclass attribute to our object.
But if we just check for charclass attribute, what happens when we try to add another? i.e. 2nd one in the row: items? __setattr__ would be called, it would check for charclass in self, it would see it is there and then it would add items to self.charclass and not self! In effect it would add attributes to the Warrior object and not Player object.
How do we add a new attribute to a Player object then? Answer is that we only set already existing Warrior object attributes. All attributes that don't exist in Warrior (self.charclass) are set as attributes of the Player instance (self.__dict__[attr] = val). That's what and attr in vars(self.charclass) means.
To jump back to top and to why I disagree with Charles Merriam's answer. To exntend and change the functionality of your code now, all you have to do additionally is to add methods to Warrior class, for example let's add rageOn and rageOff methods:
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
And they will be immediately available in your Player class without you having to change the Player at all.
newplayer.rageON()
print newplayer.charclass
print "----------------------------------------------------------------------------------------"
newplayer.rageOFF()
print newplayer.charclass
Output:
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]
If you did it all in Player, or Character, class as suggested by Charles; you would have to add a method rage to the Player class. In it you would have to check if you're actually "Warrior" or not and could only then execute it.
This doesn't sound like that much work until you realize that for every special effect of every class you want to have, you would have to write such a method. And they would all have to be in a single file where the Player class is defined. That does sound bad after all. I don't know why I wrote all this but it's too much invested now to not post. Sorry.
characterclass = getattr(objects, playerclass)
getattr() returns the value of named attribute of an object other than the object itself.
characterclass is not an instance of Warrior, therefore you cannot refer to attribute health
getattr(object, name[, default]) Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
You need to create warrior instance as:
warrior = objects.Warrior()
warrior.health
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With