Questions about pulling object information from subclasses - python

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.)
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'>
Seamless Composition
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.
Inheritance and composition help extending code later on
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

Object oriented class structures are a bit confusing.
First option, ignore subclasses. The only difference between a 'Warrior' and a 'Wizard' in your example is data. Just have a Character class and set the data as you would initialize other data:
grak = Player()
grak.occupation = Occupation('Warrior', 75, 50, ....)
Alternately, add a helpful constructor to the Occupation class:
#classmethod
def new_warrior(cls):
return Occupation('Warrior', 75, 50, ....)
# and use this way
grak.occupation = Occupation.new_warrior()
Second, create an instance of a subclass when you want one, like self.occupation = Warrior(). To get the initial instance,
you could use a couple different ways to make a factory method. The easiest to understand is a function like this:
def new_occupation(occupation_string):
if occupation == "Warrior":
return Warrior()
if occupation == "Wizard":
return Wizard()
...
Then you can type my_occupation = new_occupation(playerclass) and my_occupation.strength = 3. Drop the getattr() call.
You want to use subclasses when you are designing code where each instance
will have different data, different classes will have different behaviors, and all subclasses promise to fulfill a single contract. For example, your occupation might promise to implement an attack() method. The Wizard does double damage on a full moon, so its attack() method has
different code. When you call myPlayer.occupation.attack(), the correct attack() method is called, meaning the one for subclass that the instance actually belongs to. If you later add an 'Illusionist' class, you won't change the myPlayer.occupation.attack() line.
Keep coding. It gets better.

Related

Inheriting class attributes without declaring attributes or better OOP

I am attempting to construct classes to play out a game of MTG (A card game). I have three relevant classes: MTGGame(...), MTGCard(...), and AbilityList(). An object of MTGGame has several attributes about the player (turn, mana,..., deck).
A player must have a deck of cards to play, so I create a list of MTGCard objects for each player that is a deck, and create an MTGGame object for each from the respective decks. The cards have abilities, and when creating the cards I store abilities as functions/params into each MTGCard. However, I need the abilities to inherit and access methods/attributes from MTGGame and update them, but if I use super().__init__, then I will need to call my deck as a parameter for AbilityList when making MTGCards, which I wouldn't have yet.
Can this be achieved? If not, any suggestions improving my OOP logic to achieve this task?
I am aware that I can do something like this:
class MTGGame():
def __init__(self, deck, turn = 0, mana = 0, lifeTotal = 20, cavalcadeCount = 0, hand = [], board = []):
self.turn = turn
self.mana = mana
self.lifeTotal = lifeTotal
...
def gainLife(self, lifeGained):
self.lifeTotal = self.lifeTotal +lifeGained
def combatPhase(self):
for card in self.board:
card.attackingAbility()
class MTGCard():
def __init__(self, name, CMC, cardType, power, toughness, castedAbility, attackingAbility, activatedAbility, canAttack = False):
....
self.attackingAbility = attackingAbility
Class abilityList():
def healersHawkAbility(self, lifeAmt):
MTGGame.gainLife(lifeAmt)
But this would affect all instances of MTGGame, not the specific MTGGame object this would've been called from. I'd like it to simply update the specific object in question. I'd like to do something like this but I don't know how abilityList methods could access MTGGame attributes/methods ('AbilityList' object has no attribute 'gainLife'):
Class abilityList():
def healersHawkAbility(self, lifeAmt):
#How do I access methods/attributes in MTGGame from here? self?
self.gainLife(lifeAmt)
aL = abilityList()
#One example card:
card1 = MTGCard("Healers Hawk",1,'Creature',1,1, aL.nullAbility(), aL.healersHawkAbility, aL.nullAbility())
whiteDeck = [list of constructed MTGCard() objects, card1, card2,...,cardLast]
player1 = MTGGame(whiteDeck)
...
#Call the ability in a method contained in MTGGame:
player1.combatPhase()
#Would call something like this inside
card.attackingAbility()
#Which may refer to card.healersHawkAbility() since we stored healersHawkAbility() as an attribute for that MTGCard,
#and would declare gainLife(), which refers to self.lifeTotal or player1.lifeTotal in this case.
This is an excellent start and clearly you have already thought a lot of this through. However, you haven't thought through the relationship between the classes.
First thing to note:
MTGGame.gainLife(lifeAmt) is a method call accessed via the class rather than an instance. This means that the self paramter is not actually filled in i.e. you will get an error becuase your method expects 2 arguments but only receive one.
What you perhaps meant to do is the following:
class MTGGame:
lifeTotal = 20 # Notice this is declared as a class variable
def __init__(self, ...):
...
#classmethod
def healersHawkAbility(cls, lifeGained):
cls.lifeTotal = cls.lifeTotal + lifeGained
However, this requires class variables which here defeats the point of having an instance.
Your naming throughout the program should suggest that your classes are a little off.
For instance player1 = MTGGame(). Is player a game? No, of course not. So actually you might want to rename your class MTGGame to Player to make it clear it refers to the player, not the game. A seperate class called MTGGame will probably need to be created to manage the interactions between the players e.g. whose turn it is, the stack holding the cards whilst resolving.
The main focus of your question: how to deal with the cards accessing the game/player object.
Cards should be able to access instances of the player and game classes, and if the player has a is_playing attribute, the card should not have this. The rule of thumb for inheritance is 'is a'. Since card 'is not a' player, it should not inherit from it or MTGGame. Instead, card should be like this for example:
game = RevisedMTGGame()
player1 = Player()
player2 = Player()
class Card:
def __init__(self, name, text, cost):
self.name = name
self.text = text
self.cost = cost
self.owner = None
self.game = None
class Creature(Card):
def __init__(self, name, text, cost, power, toughness):
super().__init__(self, name, text, cost)
self.power = power
self.toughness = toughness
def lifelink(self):
self.owner.heal(self.power) # NOTE: this is NOT how I would implement lifelink, it is an example of how to access the owner
healersHawk = Creature("Healer's Hawk", "Flying, Lifelink", 1, 1, 1)
healersHawk.game = game
healersHawk.owner = player1
You can see from this incomplete example how you can set up your cards easily, even with complex mechanics, and as the base classes have been defined you can avoid repitition of code. You might want to look into the event model in order to implement the lifelink mechanic, as an example. I wish you luck in continuing your game!

How to print actual name of variable class type in function?

I'm trying to return variable name, but i keep getting this:
<classes.man.man object at (some numbers (as example:0x03BDCA50))>
Below is my code:
from classes.man import man
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
bob = man(172, 'green')
bib = man(190, 'brown')
print(competition(bob , bib ))
Epilogue
If anyone want to, explain please what I can write instead of __class__ in example below to get variable name.
def __repr__(self):
return self.__class__.__name__
Anyway, thank you for all of your support
There are different ways to approach your problem.
The simplest I can fathom is if you can change the class man, make it accept an optional name in its __init__ and store it in the instance. This should look like this:
class man:
def __init__(number, color, name="John Doe"):
self.name = name
# rest of your code here
That way in your function you could just do with:
return guy1.name
Additionnally, if you want to go an extra step, you could define a __str__ method in your class man so that when you pass it to str() or print(), it shows the name instead:
# Inside class man
def __str__(self):
return self.name
That way your function could just do:
return guy1
And when you print the return value of your function it actually prints the name.
If you cannot alter class man, here is an extremely convoluted and costly suggestion, that could probably break depending on context:
import inspect
def competition(guy1, guy2, counter1=0, counter2=0):
guy1_name = ""
guy2_name = ""
for name, value in inspect.stack()[-1].frame.f_locals.items():
if value is guy1:
guy1_name = name
elif value is guy2:
guy2_name = name
if counter1 > counter2:
return guy1_name
elif counter2 > counter2:
return guy1_name
else:
return "Noone"
Valentin's answer - the first part of it at least (adding a name attribute to man) - is of course the proper, obvious solution.
Now wrt/ the second part (the inspect.stack hack), it's brittle at best - the "variables names" we're interested in might not necessarily be defined in the first parent frame, and FWIW they could as well just come from a dict etc...
Also, it's definitly not the competition() function's responsability to care about this (don't mix domain layer with presentation layer, thanks), and it's totally useless since the caller code can easily solve this part by itself:
def competition(guy1, guy2, counter1=0, counter2=0):
.......................
some *ok* manipulations
.......................
if counter1>counter2:
return guy1
def main():
bob = man(172, 'green')
bib = man(190, 'brown')
winner = competition(bob, bib)
if winner is bob:
print("bob wins")
elif winner is bib:
print("bib wins")
else:
print("tie!")
Python prints the location of class objects in memory if they are passed to the print() function as default. If you want a prettier output for a class you need to define the __repr__(self) function for that class which should return a string that is printed if an object is passed to print(). Then you can just return guy1
__repr__ is the method that defines the name in your case.
By default it gives you the object type information. If you want to print more apt name then you should override the __repr__ method
Check below code for instance
class class_with_overrided_repr:
def __repr__(self):
return "class_with_overrided_repr"
class class_without_overrided_repr:
pass
x = class_with_overrided_repr()
print x # class_with_overrided_repr
x = class_without_overrided_repr()
print x # <__main__.class_without_overrided_repr instance at 0x7f06002aa368>
Let me know if this what you want?

Is there a way to fix Name Error due to scope?

I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.

I want to create a basic attribute system with Python (classes and def statements)

I'm a fledgling coder starting out with Python, and am trying to create a simple attribute system capable of interacting with other sections of the program (e.g. def statements). Based on the meager amount of knowledge I currently have of the Python language, I'm assuming the best way to accomplish this task is to use a class coupled with def statements to create commands and actions relevant to the class in question. Whenever I try to run my code, I end up with this error:
if petname['hungry'] == True:
TypeError: 'type' object is not subscriptable
Again, I have a very limited amount of knowledge right now, so I don't know if my program is close to being usable, or if it's just useless garbage. I'll post my code here. I'd greatly appreciate some corrective criticism; or, heck, if someone could correctly rewrite it for me, that'd be great!
Here's the code I wrote. Let me know if anymore info is necessary to give a comprehensive answer:
petname = 'Dog'
class petname (object):
attributes = {'health': 20, 'attack': 4, 'size': 5, 'hunger': True}
def feed(petname):
if petname['hungry'] == True:
petname['hungry'] = False
petname['size'] = petname['size'] + 1
print("{0} happily gobbles down the treat!".format(petname))
else:
print("{0} is not hungry.".format(petname))
if petname['hungry'] == True:
print("{0} is hungry! Feed it something!".format(petname))
input()
You're reusing the name petname across your code, and expecting it to mean different things even in the same context. Your petname class doesn't make much sense, as the single pername.attributes dictionary will be shared among the instances of all petname objects.
Below, I organize the Pet object to have attributes, use inheritance to establish default values for dogs, and make feed a method of the Pet class:
class Pet(object):
def __init__(self, name, health, attack, size, hunger):
self.name = name
self.health = health
self.attack = attack
self.size = size
self.hunger = hunger
def feed(self):
if self.hunger:
self.hunger = False
self.size += 1
print("{0} happily gobbles down the treat!".format(self.name))
else:
print("{0} is not hungry.".format(petname))
class Dog(Pet):
def __init__(self, name):
super(Dog, self).__init__(name, 20, 4, 5, True)
spot = Dog("Spot")
spot.feed()
# Spot happily gobbles down the treat!

Variable within an instance of a class does not take a new value when it is assigned.

So, I'm working on a command line RPG for the sake of filling time, and re-stretching my Python muscles as I've been out of practice for a couple of years. I used to code in a really functional manner but I'm trying to get my head around object-orientated programming.
Preamble aside, I have an issue where after creating an instance of a class, my class variable is no longer being defined. I've made 2 versions of this which I'll use to demonstrate since I'm finding it hard to articulate.
Below I created a "character" class which I intended to use as a basis for both player characters and npcs. In the first draft I was updating this class, before realising it was going to affect subclasses, when I really just wanted it as a template. Either way, this particular code block worked; it adds the values of 2 dictionaries together, then assigns them to character.characterStats. It then prints them as per displayStats().
from collections import Counter
class character:
def __init__(self, *args, **kwargs):
pass
def __setattr__(self, name, value):
pass
characterRace = ''
characterStats = {}
charLocation = ''
charName = ''
class race:
def __init__(self):
pass
baseStatsDict = {
'Strength' : 5,
'Agility' : 5,
'Toughness' : 5,
'Intelligence' : 5 }
humanStatsDict = {
'Strength' : 1,
'Agility' : 1,
'Toughness' : 1,
'Intelligence' : 1 }
def displayRace():
print("Race: ", character.characterRace, "\n")
def displayStats():
for stat, value in character.characterStats.items():
print(stat, "=", value)
print("\n")
def raceSelection():
playerInput = input("I am a ")
playerInput
playerLower = playerInput.lower()
while "human" not in playerLower:
if "human" in playerLower:
character.characterStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
character.characterRace = 'Human'
break
playerInput = input()
playerInput
playerLower = playerInput.lower()
playerChar = character()
raceSelection()
displayRace()
displayStats()
And this was the output:
Race: Human
Strength = 6
Agility = 6
Toughness = 6
Intelligence = 6
This however is the new code when I tried to tidy it up and turn the class into the template it was meant to be, and started using the class instance playerChar which for whatever reason can't assign the new value to playerChar.characterStats. playerChar.displayStats() prints the characterRace and characterStats variables as empty, even though they are assigned when the player enters the value human.
from collections import Counter
class character:
characterRace = ''
characterStats = {}
def __init__(self):
pass
def displayRace(self):
print("Race: ", self.characterRace, "\n")
def displayStats(self):
for stat, value in self.characterStats.items():
print(stat, "=", value)
print("\n")
class race:
def __init__(self):
pass
baseStatsDict = {
'Strength' : 5,
'Agility' : 5,
'Toughness' : 5,
'Intelligence' : 5 }
humanStatsDict = {
'Strength' : 1,
'Agility' : 1,
'Toughness' : 1,
'Intelligence' : 1 }
def raceSelection():
playerInput = input("I am a ")
playerInput
playerLower = playerInput.lower()
while "human" not in playerLower:
if "human" in playerLower:
playerChar.characterStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
playerChar.characterRace = 'Human'
break
playerInput = input()
playerInput
playerLower = playerInput.lower()
playerChar = character()
raceSelection()
playerChar.displayRace()
playerChar.displayStats()
So this will output:
Race:
\n
\n
\n
So I know it's able to draw from the class race dictionaries and add their contents together as from the previous code. If I try and print the player.x characteristics it won't throw any errors so it recognises they exist. If anyone could explain to me what's going wrong and why in this new iteration, I'd be very grateful.
EDIT: So a friend and I have tried passing the class as an argument of raceSelection(), we've tried printing a string after each call/update of a variable and we've tried entering a string into the variable, printing it, then redefining the variable with a new string.
Input:
class character:
charRace = ''
charStats = {}
charLocation = ''
charName = ''
charString = "Cole said define a string."
Within the if statements:
if "human" in playerLower:
print("Oh, you're just a really ugly human.")
playerChar.charStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
playerChar.charRace = 'Ugly Human'
print("playerChar.charString = ", playerChar.charString)
playerChar.charString = "Redefine."
print("playerChar.charString = ", playerChar.charString)
break
Output:
Oh, you're just a really ugly human.
playerChar.charString = Cole said define a string.
playerChar.charString = Cole said define a string.
Race:
It should not be character.characterStats.items(), but self.characterStats.items(). Similarly for all other values that belong to one, specific character.
Using the name of the class assigns a value that belongs to the class, and is the same for all objects you create. Lookup instance vs class attributes.
So, after trying to move the variables in and out of __init__, trying setattr(), trying to pass any sort of argument through the class just so it had some data, trying to run the instance of the class through a function, none of those solutions came to work in the end.
The solution turned out to be to create a subclass of character and manipulate that instead. I figured this would be alright as well since the player character will mutate throughout gameplay, and will never see further subclasses of itself.

Categories