I've recently been refactoring some of my code to use OOP, and I've run into a problem where I can't quite get either global vars, exec(), or a combination of the two to work. The relevant code is below:
# invObject class. Has all properties of an inventory object.
class invObject:
def __init__(self, name, info, output, effect):
self.name = name # Used in menus.
self.info = info # Describes effect.
self.output = output # Printed on use.
self.effect = effect # Executed on use.
def printInfo(self): # Function for name and description.
return "{} - {}".format(self.name, self.info)
def use(self): # Function to use items. It's that easy.
global dodgeChance
global maxHp
global hp
global atk
exec(self.effect)
print(self.output) # Prints output. Also very simple.
print("{} {} {} {}".format(dodgeChance, maxHp, hp, atk)) # debugging
...
inventory[slot].use() # does not update values
Basic rundown: inventory[slot].use() should call the use() function of the object. use() should execute the code stored in inventory[slot].effect.
The output from the debugging line doesn't change anything, even inside the function. I've already tried making it return exec(self.effect) to no avail. print(self.output) does work.
EDIT: Here's a minimal reproducible example that includes everything it needs to run, not just the most important things.
# Assign base stats
dodgeChance = 0
maxHp = int(input("Input maximum HP. > "))
hp = int(input("Input current HP. > "))
# invObject class. Has all properties of an inventory object.
class invObject:
def __init__(self, name, info, output, effect):
self.name = name # Used in menus.
self.info = info # Describes effect.
self.output = output # Printed on use.
self.effect = effect # Executed on use.
def printInfo(self): # Function for name and description.
return "{} - {}".format(self.name, self.info)
def use(self): # Function to use items. It's that easy.
global dodgeChance
global maxHp
global hp
global atk
exec(self.effect)
print(self.output) # Prints output. Also very simple.
print("{} {} {} {}".format(dodgeChance, maxHp, hp, atk)) # debugging
empty = invObject("None", "Vacant slot.", "There's nothing in that slot!", "")
apple = invObject("Apple", "Gives 20 health.", "Ate the apple. Gained 20 health.", "hp = hp + 20\nif hp > maxHp: hp = maxHp")
drink = invObject("Drink", "Some kind of energy drink. Raises dodge chance to 75%.", "Drank the drink. Dodge chance is now 75%!", "dodgeChance = 75")
# Assign base inventory
inventory = [apple, drink, empty]
slot = int(input("Input slot number to use. ")) - 1
inventory[slot].use() # does not update values
# Show final stats
print("New HP value: " + str(hp))
print("Dodge chance: " + str(dodgeChance) + "%")
print()
print("Inventory contents:")
print("Slot 1: " + str(inventory[0].name))
print("Slot 2: " + str(inventory[1].name))
print("Slot 3: " + str(inventory[2].name))
EDIT 2: Another thing: the code works if I don't use exec() (e.g. change it out for hp += 20).
exec() has optional arguments for you to provide the global and local variable contexts.
But you didn't provide them.
Related
While trying to complete an assignment, my result keeps coming up as partially correct in the ZyBooks system. I’ve tried everything I can possibly think of to solve the issue, and have no idea what else to try. Here are the instructions for the assignment:
class Team:
def __init__(self):
self.name = 'team'
self.wins = 0
self.losses = 0
# TODO: Define get_win_percentage()
def get_win_percentage(self):
return team.wins / (team.wins + team.losses)
# TODO: Define print_standing()
def print_standing(self):
print(f'Win percentage: {team.get_win_percentage():.2f}')
if team.get_win_percentage() >= 0.5:
print('Congratulations, Team', team.name,'has a winning average!')
else:
print('Team', team.name, 'has a losing average.')
if __name__ == "__main__":
team = Team()
user_name = input()
user_wins = int(input())
user_losses = int(input())
team.name = user_name
team.wins = user_wins
team.losses = user_losses
team.print_standing()
I’m passing all the auto-generated tests aside from the last three, and I can’t understand why? To Do’s have to be included as well.
As noted in comments, in the below you've used team rather than self.
# TODO: Define get_win_percentage()
def get_win_percentage(self):
return team.wins / (team.wins + team.losses)
# TODO: Define print_standing()
def print_standing(self):
print(f'Win percentage: {team.get_win_percentage():.2f}')
if team.get_win_percentage() >= 0.5:
print('Congratulations, Team', team.name,'has a winning average!')
else:
print('Team', team.name, 'has a losing average.')
Corrected:
# TODO: Define get_win_percentage()
def get_win_percentage(self):
return self.wins / (self.wins + self.losses)
# TODO: Define print_standing()
def print_standing(self):
print(f'Win percentage: {self.get_win_percentage():.2f}')
if self.get_win_percentage() >= 0.5:
print('Congratulations, Team', self.name, 'has a winning average!')
else:
print('Team', self.name, 'has a losing average.')
Just to add to the answer of joshmeranda.
In the class, when you call the atribute of this object you use the "self" parameter. This parameter is used to reference the instance itself.
For example:
def print_wins(self): print(self.wins) to refence the instance
When you do:
team = Team()
you created a instance of team that is called "team". With this, you can print the numbers of win, for example: print(team.wins)
Or, call the function "print_wins":
team.print_wins()
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.
(For those who saw this question the last time I asked it, I sincerely apologize, I used the term "module" when I meant "function", but thank you for your very helpful advice nontheless! I'll make sure to keep it in mind when I begin to add other files into the equation.)
I'm trying to make a text based adventure game using python, and as a result it requires a lot of variables, and as backtracking is a must, I need to use global variables for the essential ones. I have run into speed bumps when trying to get these to be read by other functions. This is the line of code used to define the universal variables, and their starting value
def reset():
global gold, exp, etnl, maxHP, curHP, maxmana, curmana, attack, defence, helm, armtop, armbot, boots, gloves, weapons
gold = 0
exp = 0
etnl = 100 #exp to next level
maxHP = 50
curHP = 50
maxmana = 10
curmana = 10
attack = 5
defence = 5
helm = "none"
armtop = "none"
armbot = "none"
boots = "none"
gloves = "none"
weapon = "fists"
And for example, when I try to display one of the global variables, it shows up as the variable being undefined, as shown here:
def gamestart():
clear() #this command is fine, simply to make it look neater when it is run again
print("you wake up in a clearing in the forest, you can't remember what happened.")
print("you feel numb, you realize you're lying flat on your back.")
print
print("HP " + str(curHP) + "/" + str(maxHP))
Can someone help me out with this?
Is there an easier way to do this?
All help is appreciated!
(yes, I make sure to run the reset function before the newgame function)
A much simpler version if this, at least according to me is:
def variable():
global foo
foo = 7
def trigger():
variable():
output():
def output():
print(foo)
You could store those things into a class used as storage-container. If you declare them classvariables and any accessors as #classmethods you do not need an instance.
class GameState:
gold = 0
exp = 0
etnl = 100 #exp to next level
maxHP = 50
curHP = 50
maxmana = 10
curmana = 10
helm = "none"
armtop = "none"
armbot = "none"
boots = "none"
gloves = "none"
weapon = "fists"
weapons = {"fists":(5,5),"sword":(15,12),"mace":(30,3),"cushion":(2,20)}
#classmethod
def reset(cls):
cls.gold = 0
cls.exp = 0
cls.etnl = 100 #exp to next level
cls.maxHP = 50
cls.curHP = 50
cls.maxmana = 10
cls.curmana = 10
cls.helm = "none"
cls.armtop = "none"
cls.armbot = "none"
cls.boots = "none"
cls.gloves = "none"
cls.weapon = "fists"
#classmethod
def attack(cls):
return cls.weapons.get(cls.weapon,(0,0))[0]
#classmethod
def defense(cls):
return cls.weapons.get(cls.weapon,(0,0))[1]
for w in State.weapons:
State.weapon = w
print("{} has attack {} and defense {}.".format(w, State.attack(),State.defense()))
Output:
fists has attack 5 and defense 5.
sword has attack 15 and defense 12.
mace has attack 30 and defense 3.
cushion has attack 2 and defense 20.
You might want to seperate some things out - f.e. an extra class for the weapon/damage/defense related stuff ...
More reading:
What is the difference between #staticmethod and #classmethod?
https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
Instead of global variables have you considered storing all the stats in a class/struct? Create an instance of the class at the start of the game, with its default values being specified in the constructor.
G = StartClass()
def gamestart():
print("you wake up in a clearing in the forest, you can't remember what happened.")
print("you feel numb, you realize you're lying flat on your back.")
print("HP " + str(G.curHP) + "/" + str(G.maxHP))
Alternatively, declaring G globally and passing it into gamestart(G) and/or re-instantiating in the reset() function might be options.
Here is a simple example of what I think you are trying to accomplish. If you are using global variables, then you need to be sure you are not inadvertently creating local variables with the same names in your functions (when you mean to be modifying the global variable).
You should look at using classes which I think would help you with some of the semantic confusion here.
value = 10
def reset():
global value
value = 10
def start():
print(f'initial value: {value}')
global value
value += 1
print(f'updated value: {value}')
reset()
print(f'reset value: {value}')
start()
# OUTPUT
# initial value: 10
# updated value: 11
# reset value: 10
Hi I'm making a program on python and I'm having trouble adding a global variable to my program so I'm just going to post my code and show you how I tried to do it.
So this is my class:
import globalvariables
class Bus :
def __init__(self, Number, Capacity, Destination, Seats):
self.Bus_Number = Number
self.Bus_Capacity = Capacity
self.Bus_Destination = Destination
self.Seats_taken = Seats
def Book(self):
self.Seats_taken = Seats + 1
def ShowBus(self):
return (str(self.Bus_Number) + ", " + str(self.Bus_Capacity) + ", " + str(self.Bus_Destination) + ", " + str(self.Seats_taken))
and this is my module for global variables
Seats = 0
and this is what I'm trying to run:
import Transport
import globalvariables
Big_Red = Transport.Bus(1, 50, "NYC", 0)
Big_Red.Book()
print(Big_Red.ShowBus())
I'm getting this error:
Traceback (most recent call last):
File "D:\Python\Assignment 3\Tester.py", line 5, in <module>
Big_Red.Book()
File "D:\Python\Assignment 3\Transport.py", line 14, in Book
self.Seats_taken = Seats + 1
NameError: global name 'Seats' is not defined
The variable Seats is local to __init__ function and can't be accessed outside of it.
So,
self.Seats_taken = Seats + 1
Should be :
self.Seats_taken = self.Seats_taken + 1
or :
self.Seats_taken += 1
Instead of using global variables inside class you should use class attributes:
class Bus :
seats = 50 #shared across all instances
def __init__(self):
#code
def Book(self):
self.Seats_taken = self.seats + 1
Globals should be avoided. In case you still want it to be :
def Book(self):
self.Seats_taken = globalvariables.Seats + 1
When you import globalvariables, you gain access to names qualified by the module name: globalvariables.Seats. To import Seats into the namespace of another module, use from globalvariables import Seats. (In desperate cases, you can import all names from a module: from globalvariables import *, but usually you don't want this.)
When you define a function, it has its own local namespace. It includes all function's arguments.
Seats = 100
def foo(Seats):
# returns the value of variable named Seats
# defined within "def foo", *not* defined by "Seats = 100"
return Seats
print foo(200) # prints 200
print foo() # fails because Seats are not set
To initialize a function parameter, use default value:
def foo(seats=0):
print foo() # prints 0
print foo(55) # prints 55
Also, global variables are evil. Global constants are good.
You want to use a global variable to track seats taken. You'll be much better off if you encapsulate it into a class that only allows reasonable access, does not allow to set the value arbitrarily, log access if needed, etc:
class SeatsDispenser(object):
def __init__(self, initial_count):
self.count = initial_count
def allocate(self, number_of_seats):
self.count -= number_of_seats
if self.count < 0:
raise ValueError("Overcommitted!")
def seats_left(self):
return self.number_of_seats
Naming your variables, classes, constants, and functions with the same Title Case is impractical. Usually variables are lower_case, functions are lowerCamelCase, classes are TitleCamelCase and constants are ALL_CAPS.
A reasonable piece of code would look like this:
import constants # modules are usually lower case
import transport
def Bus(object):
def __init__(self, number, capacity, seats=constants.SEATS):
self.number = number
self.capacity = capacity
self.seats = seats
big_red = Bus(constants.NYC_BUS_NUMBER, 50, 25)
default_blue = Bus(1, 20) # seats not passed, the default value is used
seats_dispenser = SeatsDispenser(100)
seats_dispenser.allocate(big_red.count)
seats_dispenser.allocate(default_blue.count)
print seats_dispenser.seats.left()
I'm writing a mini game to learn Python. I created a weapons class that can be imported into my main.py file.
Here is the class I made:
class weapon(object):
def __init__(self, name):
self.weaponName = name
def weaponStrength(self, level, strength):
self.weaponLevel = level
self.weaponStrength = strength
damage = self.weaponStrength * level
print "Damage is equal to %r" % damage
return damage
Here are the objects that are created using the weapons class.
# Creates an Object called sword using the weaponsClass
sword = weapon("sword")
# Calls a method of the weaponsClass to calculate weapon Strength. Returns a int
sword.weaponStrength(3, 20)
# Creates an Object called Magic Staff using the weaponsClass
magicStaff = weapon("Magic Staff")
# Calls a method of the weaponsClass to calculate weapon Strength. Returns a int
magicStaff.weaponStrength(5, 30)
# Sets a variable
swordStrength = sword.weaponStrength
# Sets a variable
magicStaffStrength = magicStaff.weaponStrength
# Prints the variable
print swordStrength
# Prints the variable
print magicStaffStrength
I'm trying to figure out why the swordStrength and magicStaffStrength are equal to the strength value passed to the method.
Any help is much appreciated.
Thanks.
you're overwriting weaponStrength in the weapon namespace:
self.weaponStrength = strength
and
def weaponStrength(...):
are actually conflicting. Maybe think about your naming conventions
This method is attempting to do two things:
Storing the weaponLevel and weaponStrength
Calculating and returning damage
I think this is a bad design, since the first purpose suggests the function should be called something like setWeaponLevelAndStrength while the second suggests it should be called calculateDamage. weaponStrength is obviously a terrible choice of name if you also wish to have an attribute with that name
def weaponStrength(self, level, strength):
self.weaponLevel = level #first purpose
self.weaponStrength = strength #first purpose
damage = self.weaponStrength * level #second purpose
print "Damage is equal to %r" % damage #second purpose
return damage
I'd suggest you split the method into two
def setWeaponLevelAndStrength(self, level, strength):
self.weaponLevel = level
self.weaponStrength = strength
def calculateDamage(self)
damage = self.weaponStrength * self.weaponLevel
print "Damage is equal to %r" % damage
return damage