With the following sample code containing #property decorators:
class Grade_Calculator(object):
def __init__(self,score):
self.score = score
#property
def grade(self):
if self.score in range(60,70):
grade = 'D'
elif self.score in range(70,80):
grade = 'C'
elif self.score in range(80,90):
grade = 'B'
elif self.score in range(90,101):
grade = 'A'
return grade
#property
def failure(self):
if self.score < 60:
print 'See me'
grade = 'F'
return grade
and an instance:
g = Grade_Calculator(28)
g.grade returns an UnboundLocalError. I would like to call failure() within grade() to avoid this error.
With an instance:
g = Grade_Calculator(89)
g.failure() fails silently. I would like to call grade() within failure() to act as a fail-safe in this situation.
I've seen a lot of references to just doing self.method() calls, but they're not working for me and I think the presence of the decorator is screwing me up somehow:
#property
def grade(self):
if self.score < 60:
self.failure()
elif self.score in range(60,70):
grade = 'D'
elif self.score in range(70,80):
grade = 'C'
elif self.score in range(80,90):
grade = 'B'
elif self.score in range(90,101):
grade = 'A'
return grade
g = Grade_Calculator(28)
g.grade
See me
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-667-fb10e6cf27d4> in <module>()
----> 1 g.grade
./sample.py in grade(self)
6 def grade(self):
7 if self.score < 60:
----> 8 self.failure()
9 elif self.score in range(60,70):
10 grade = 'D'
TypeError: 'str' object is not callable
What am I not understanding?
You have defined failure as a property. Therefore, you can't call it using () notation; in fact, not doing that is kinda the whole point of using a property. self.failure returns 'F' and you then try to call it, which is why you get the error about not being able to call a string. It's as if you wrote 'F'() which is an obvious mistake.
Also, you're throwing away the value you get from failure and you're only returning a value in the case of failure (which means you get None otherwise). Of course you only access failure if you have a failing grade to begin with... in which case you don't need to check that condition inside failure, now do you?
Also, printing stuff in properties is pretty bad style. You don't expect something to be printed when you access an attribute. Separation of concerns: something outside of the class should probably be doing the printing, or you should have a separate method to call to print whatever is returned by grade.
I'd rewrite your class as follows:
class Grade_Calculator(object):
def __init__(self, score):
self.score = score
#property
def grade(self):
if self.failure:
return 'F'
if 60 <= self.score < 70:
return 'D'
if 70 <= self.score < 80:
return 'C'
if 80 <= self.score < 90:
return 'B'
if 90 <= self.score < 100:
return 'A'
#property
def failure(self):
return self.score < 60 # returns True or False
def print_grade(self):
print self.grade, "- see me" * self.failure
if __name__ == '__main__':
c = Grade_Calculator(71)
c.print_grade()
class Grade_Calculator(object):
def __init__(self, score):
self.score = score
#property
def grade(self):
grade = self.failure
if 60 <= self.score < 70:
grade = 'D'
elif 70 <= self.score < 80:
grade = 'C'
elif 80 <= self.score < 90:
grade = 'B'
elif 90 <= self.score < 100:
grade = 'A'
return grade
#property
def failure(self):
if self.score < 60:
print 'See me'
grade = 'F'
return grade
if __name__ == '__main__':
a = Grade_Calculator(71)
print a.grade
You're basically creating a new attribute to your object with the failure method. And you're never trying to get its value, that is why you'll never see 'See me'
By the way, you're overriding the value of grade which was initially an integer. You converted it to a string ('A', 'B', 'C', ...)
The code I provided works, but I changed a few things. There is no need to call range (this is expensive). You can use value < variable < other_value in Python
Related
I want to use a few else statements in my class method:
class Musician :
def __initiate__(self,name, instrument, years_playing, practice_hours):
self.name = name
self.instrument =instrument
self.years_playing = years_playing
self.practice_hours = practice_hours
# self.level = int(self.practice_hours*self.years_playing)
def level(self):
if self.practice_hours <= 1 and self.years_playing <=1:
print (self.name,'is a beginner')
else:
self.practice_hours <=2 and self.years_playing <=2
print (self.name,'is an intermediate')
else:
self.practice_hours <= 3 and self.years_playing <=3
return (self.name, 'is an expert')
player_1 = Musician('Don', 'guitar', 1,3)
player_1.level()
There are many things wrong with your code. The actual question you have seems to be about using elif where you tried to use else; the general structure is
if condition:
something should happen
elif other_codition:
things which should be done in this case
elif third_condition:
other things
else:
unconditionally do this if none of the conditions above were true
where all the branches after the first are optional.
You are mixing print with return, and I'm guessing you meant __init__. Here's an attempt to fix the code.
class Musician:
def __init__(self, name, instrument, years_playing, practice_hours):
self.name = name
self.instrument =instrument
self.years_playing = years_playing
self.practice_hours = practice_hours
# self.level = int(self.practice_hours*self.years_playing)
def level(self):
if self.practice_hours <= 1 and self.years_playing <= 1:
return "beginner"
elif self.practice_hours <= 2 and self.years_playing <= 2:
return "intermediate"
elif self.practice_hours <= 3 and self.years_playing <= 3:
return "expert"
player_1 = Musician('Don', 'guitar', 1, 3)
lvl = player_1.level()
print(f"the level of {player_1.name} is {lvl}")
__init__ is a reserved name for the method which gets invoked when you create a new instance; you can't use a different name and hope for it to be invoked under the shme circumstances.
The level function will still return None if none of its conditions are true, but how to fix that depends on criteria I can't know. Perhaps a better design would be to check for "expert" or "intermediate" and then otherwise always return "beginner" if neither of those are true.
def level(self):
if self.practice_hours > 2 and self.years_playing > 2:
return "expert"
elif self.practice_hours > 1 and self.years_playing > 1:
return "intermediate"
# else
return "beginner"
You can use bisect to achieve the same, without using if
import bisect
def level(self):
level_dict = {0 : 'is a beginner', 1 : 'is an intermediate', 2 : 'is an expert'}
grade_ranges = [(0, 1), (1, 2), (2, 3)]
points, grade = zip(*grade_ranges)
return self.name + level_dict[bisect.bisect(points, self.practice_hours)-1]
I'm working on a Discord bot that can be used to play blackjack (Python). The following code is supposed to calculate the values of the respective hands:
class Player(object):
def __init__(self, id=''):
self.id = id
self.hand = None
self.status_text = ''
self.bet = 0
self.value = 0
self.score = 0
self.wins = 0
self.no_response = 0
self.request_leave = False
self.playing = False
def calculate_value(self):
"""Calculates value of player's hand"""
if not self.hand:
return 0
num_aces = 0
total_value = 0
for card in self.hand:
if pydealer.const.DEFAULT_RANKS['values'][card.value] == 13:
num_aces += 1
total_value += 11
elif pydealer.const.DEFAULT_RANKS['values'][card.value] >= 10:
total_value += 10
else:
total_value += int(card.value)
while num_aces > 0 and total_value > 21:
total_value -= 10
num_aces -= 1
return total_value
Unfortunately I get at
for card in self.hand:
if pydealer.const.DEFAULT_RANKS['values'][card.value] == 13:
num_aces += 1
total_value += 11
the error Non-iterable value self.hand is used in an iterating context is displayed.
I hope someone can help! Greetings and thank you very much.
Your self.hand is initialized to be None.
So do make sure your self.hand gets set to an iterable object before you run stuff with it.
I'm learning to program with python and I came across this issue: I'm trying to make a Guessing Game, and while trying to check for the win condition, the function doesn't recognise the input variable, which I made sure I returned with a previous function. So i get the 'name << 'first_input' is not defined' >> error. I thought it had something to do with the variable not being global or sth like that.
import random
ran_int = random.randint(1,100)
guesses = 0
# here you input the number and it keeps asking unless you do so with 1 to 100
def ask():
first_input = 0
while first_input < 1 or first_input > 100:
first_input = int(input('Enter a number between 1 and 100: '))
return first_input
# this is just to increment the number of guesses stored for showing at the end # of the game
def guesses_inc():
global guesses
guesses += 1
return guesses
# here is where i get the error, as if my ask() function didn't return
# the value properly or as if I assigned it wrongly
def check_win_1():
if first_input == ran_int:
guesses_inc()
print(f'BINGO!\nYou guessed correctly after {guesses} times.')
elif (abs(ran_int - first_input) <= 10):
guesses_inc()
print('WARM!')
ask2()
elif first_input < 1 or first_input > 100:
print('Out of bounds!')
ask2()
else:
guesses_inc()
print('COLD!')
ask2()
ask()
check_win_1()
And here is the error
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-11-bfd5497995df> in <module>
----> 1 check_win_1()
NameError: name 'first_input' is not defined
I didn't paste the whole code because while testing it it returned the error at this stage so I didn't think the rest mattered for this particular problem. I tried making the var input global and stuff like that but i don't think I did it properly.
Your method call is not correct. You should call your functions like this
def check_win_1(first_input):
if first_input == ran_int:
guesses_inc()
print(f'BINGO!\nYou guessed correctly after {guesses} times.')
elif (abs(ran_int - first_input) <= 10):
guesses_inc()
print('WARM!')
ask2()
elif first_input < 1 or first_input > 100:
print('Out of bounds!')
ask2()
else:
guesses_inc()
print('COLD!')
ask2()
first_input = ask()
check_win_1(first_input)
The error is there because you are trying to use first_input somewhere (i.e. inside check_win_1()).
A possible, not recommended, solution is to qualify your variable as global, which should be used VERY sparingly.
Instead, it is recommended to use function parameters, so as to encapsulate your code in self-contained blocks, e.g.:
def func(a, b):
return a + b
x = func(10, 5)
rather than:
def func():
global a, b
return a + b
a = 10
b = 5
x = func()
For your that may mean doing something like:
def check_win_1(first_input, ran_int):
...
and use them accordingly, e.g.:
first_input = ask()
check_win_1(first_input, ran_int)
etc.
EDIT
Following the above principle, your code could have looked like:
import random
MIN_VAL = 1
MAX_VAL = 100
WARM_LIMIT = 10
def ask_number(
min_val=MIN_VAL,
max_val=MAX_VAL):
guess = None
while guess is None:
guess = int(input(f'Enter a number between {min_val} and {max_val}: '))
if guess < min_val or guess > max_val:
print('Out of bounds!')
guess = None
return guess
def check_guess(
guess,
target,
num_guesses,
warm_limit=WARM_LIMIT):
if guess == target:
print(f'BINGO!\nYou guessed correctly after {num_guesses} times.')
return True
else:
if (abs(guess - target) <= warm_limit):
print('WARM!')
else:
print('COLD!')
return False
# : main
target = random.randint(MIN_VAL, MAX_VAL)
num_guesses = 0
won = False
while not won:
guess = ask_number()
num_guesses += 1
won = check_guess(guess, target, num_guesses)
I'm trying to call a class function player.Decide() in another function where it wasn't declared. Im getting the error 'player is not defined. How would I go about fixing this?
def BattleLogic():
global enemy, enemy_race, name, level, strength, dexterity, cunning, enemy_speed, player_speed, type_speed
global move, turn, prefix
if enemy in ['deer']:
enemy_race = 'animal'
if enemy_race == 'animal':
chance = random.randint(1,1000)
if chance <= 10:
prefix = 'crippled'
elif chance > 10 and chance <= 50:
prefix = 'old'
elif chance >50 and chance <= 250:
prefix = 'young'
elif chance > 250 and chance <= 750:
prefix = None
elif chance > 750 and chance <= 950:
prefix = 'strong'
elif chance > 950 and chance <= 990:
prefix = 'alpha'
elif chance > 990 and chance <= 999:
prefix = 'possessed'
elif chance == 1000:
prefix = '*CONVERTED*'
else:
prefix = 'error'
opponent = Enemy(str(prefix),str(enemy),str(enemy_race))
player = Player(str(name),level,strength,dexterity,cunning)
player.Initiative()
opponent.Initiative()
if enemy_speed > player_speed:
move = 0
elif player_speed > enemy_speed:
move = 1
else:
move = random.randint(0,1)
turn = 0
Battle()
def Battle():
global turn, move, prefix, enemy, type_speed, enemy_title
if turn == 0:
print('\n\n')
if prefix == None:
enemy_title = enemy.capitalize()
else:
enemy_title = prefix.capitalize()+' '+enemy.capitalize()
SlowPrint('A '+enemy_title+' gets into position for battle',type_speed,0.5)
if move == 1:
SlowPrint(enemy_title+' makes the first move',type_speed,0.25)
else:
SlowPrint('You make the first move',type_speed,0.25)
if move == 0:
turn += 1
move = 1
player.Decide()
else:
turn += 1
move = 0
opponent.Decide()
Just pass a player object into the function. The you are free to use player.Decide().
The major problems with your coding, which is my personal opinion, is that you do not have the habit of passing something into the function, which as a result forces you to declare too many global variables.
A simple example (in your case, you want the object to be a player)
def Battle(obj):
return obj.Decide()
Here is my code trying to learn unit testing.
Create a Student class for the purpose of testing. The test invalid test case constantly failed.
FAIL: test_invalid (__main__.TestStudent)
----------------------------------------------------------------------
Traceback (most recent call last):
File "mystudent.py", line 46, in test_invalid
s1.get_grade()
AssertionError: ValueError not raised
above is from running result.
Could anyone help me to figure out why I have this failure while I think I have put the right 'Raise Error' code there....
import unittest
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
try:
if self.score >= 60 and self.score < 80:
return 'B'
if self.score >= 80 and self.score <= 100:
return 'A'
if self.score >= 0 and self.score <60:
return 'C'
if self.score < 0 or self.score > 100:
raise ValueError('Invalid score value')
except Exception as e:
print('Value error!')
class TestStudent(unittest.TestCase):
def test_invalid(self):
s1 = Student('Bob', -1)
s2 = Student('Bat', 101)
with self.assertRaises(ValueError):
s1.get_grade()
with self.assertRaises(ValueError):
s2.get_grade()
if __name__ == '__main__':
unittest.main()
Thanks
You're catching the ValueError inside the function. You need to either remove the try/except block in the function or re-raise it after doing whatever you want inside:
def get_grade(self):
try:
if self.score >= 60 and self.score < 80:
return 'B'
if self.score >= 80 and self.score <= 100:
return 'A'
if self.score >= 0 and self.score <60:
return 'C'
if self.score < 0 or self.score > 100:
raise ValueError('Invalid score value')
except Exception as e:
print('Value error!')
raise # Passes the exception up