Here's my code:
class student:
def __init__(self):
self.totalSumOGrades = 0
self.numberOGrades = 0
def getAverageScore(self):
return (self.totalSumOGrades / self.numberOGrades)
def addGrade(self,grade):
self.totalSumOGrades = str(grade)
self.numberOGrades = self.numberOGrades + 1
return (self.totalSumOGrades)
class GPA:
def __init__(self,grade):
self.grade = grade
self.score = 0
def gradesScore(self):
gradeLetter = self.grade[0]
gradeSign = ' '
if (len(self.grade)) == 2:
gradeSign = self.grade[1]
if (gradeLetter == 'A'):
self.score = 4
elif (gradeLetter == 'B'):
self.score = 3
elif (gradeLetter == 'C'):
self.score = 2
elif (gradeLetter == 'D'):
self.score = 1
elif (gradeLetter == 'F'):
self.score = 0
if (gradeSign == '+'):
self.score += 0.3
elif (gradeSign == '-'):
self.score -= 0.3
def getScore(self):
self.gradesScore()
return self.score
I need both classes on one sheet. The problem I'm having is the argument that is being taken for class GPA is what "getScore" is calculating. I need it so the addGrade from class student adds a grade and I can have "getScore" calculate those grades instead. How do i fix this?
So as I promised an answer / feedback version to help the OP walk through and pick some new questions - here it is (offered in the hope that train hacking is sufficiently high quality to show case some concepts and offer agood start to create a real solution based on this):
#! /usr/bin/env python
from __future__ import division, print_function
class GradePointAverage(object):
"""Implements a "Grade Point Average" (GPA) calculation.
Note: It keeps all grades to offer an incremental update path.
"""
FILL_CHAR_ZERO_ADJUST = '.'
VALID_LETTERS = ('A', 'B', 'C', 'D', 'E', 'F')
VALID_ADJUSTMENTS = ('+', FILL_CHAR_ZERO_ADJUST, '-')
BASE_SCORE_MAP = dict(zip(reversed(VALID_LETTERS), (0., 1., 2., 3., 4.)))
ADJ_SCORE_MAP = dict(zip(VALID_ADJUSTMENTS, (0.3, 0., -0.3)))
def __init__(self, grades=None):
"""Inintializes the _grades, a sequence of canonical grade
values e.g. ['A+', 'B-'] where any grade recieved is
mapped to uppercase letter plus either ''|'+'|'-' or
and exception ValueError is thrown.
"""
if grades is None:
self._grades = list()
else:
self._grades = [self.canonicalize_grade(g) for g in grades]
def __repr__(self):
"""Helper to (de)serialize and put more in print et al."""
return ('GradePointAverage(%s)' % (str(self._grades)))
def add_grades(self, grades):
"""Add a new result / grade to data."""
for g in grades:
self.add_grade(g)
def add_grade(self, grade):
"""Add a new result / grade to data."""
self._grades.append(self.canonicalize_grade(grade))
def count_grades(self):
"""Return the count of grades, the scoring is based upon."""
return len(self._grades)
def grades(self):
"""Return the grades as list, the scoring is based upon."""
return self._grades
def canonicalize_grade(self, grade):
"""Ensure grade is valid, ensure uppercase letter plus either
''|'+'|'-' on output. If invalid, let raise or throw ValueError. """
c_grade = grade.strip().upper() # May raise
if 1 <= len(c_grade) <= 2:
if len(c_grade) < 2:
c_grade += self.FILL_CHAR_ZERO_ADJUST
else:
raise ValueError("Invalid grade length")
if c_grade[0] not in self.VALID_LETTERS:
raise ValueError("Invalid main grade")
if c_grade[1] not in self.VALID_ADJUSTMENTS:
raise ValueError("Invalid grade adjustment")
return c_grade
def _score(self, canonical_grade):
"""Calculate score from canonical grade."""
base, adj = canonical_grade[0], canonical_grade[1]
return self.BASE_SCORE_MAP[base] + self.ADJ_SCORE_MAP[adj]
def average_score(self):
"""Calculate average score."""
if not self.count_grades():
return None
# implicit else:
score_sum = sum(self._score(c_g) for c_g in self._grades)
return score_sum / float(self.count_grades())
def median_score(self):
"""Calculate median score."""
if not self.count_grades():
return None
# implicit else:
middle_index = self.count_grades() // 2
return sorted([self._score(c_g) for c_g in self._grades])[middle_index]
def best_score(self):
"""retrieve highest score."""
return NotImplemented
class Student:
"""Models student with updateable Grade Point Average."""
def __init__(self, grades):
self._gPA = GradePointAverage(grades)
self.number_of_grades = self._gPA.count_grades()
def __repr__(self):
"""Helper to (de)serialize and put more in print et al."""
return ('Student(%s)' % (str(self._gPA.grades())))
# Delegated / proxy methods
def average_score(self):
return self._gPA.average_score()
def count_grades(self):
return self._gPA.count_grades()
def grades(self):
return self._gPA.grades()
def median_score(self):
return self._gPA.median_score()
def best_score(self):
return self._gPA.best_score()
def add_grade(self, grade):
return self._gPA.add_grade(grade)
def add_grades(self, grades):
return self._gPA.add_grades(grades)
def main():
"""Drive some tests on "scored" Students."""
print('Positive test cases:')
print('... service class under test:')
gPA = GradePointAverage(['a+', 'c-'])
print(gPA)
print('... main class under test:')
student = Student(['F+'])
print(student)
print(student.count_grades())
print(student.average_score())
print(student.median_score())
a_grade = 'E-'
print("Added %s" % (a_grade,))
student.add_grade('E-')
print(student.count_grades())
print(student.average_score())
print(student.median_score())
some_grades = ['E', 'b+', 'b-', 'c+', 'D', 'D']
print("Added %s" % (str(some_grades),))
student.add_grades(some_grades)
print(student.count_grades())
print(student.average_score())
print(student.median_score())
print(student.grades())
print('Negative test cases:')
print(student.best_score())
print('... too long:')
try:
_ = GradePointAverage(['aa+', 'no_impact'])
except ValueError as e:
print(e)
print('... wrong grade letter:')
try:
_ = GradePointAverage(['z', 'no_impact'])
except ValueError as e:
print(e)
print('... wrong adjustment:')
try:
_ = GradePointAverage(['A*', 'no_impact'])
except ValueError as e:
print(e)
print('... wrong grade "type" we did let it just bubble:')
try:
_ = GradePointAverage([42, 'no_impact'])
except AttributeError as e:
print(e)
if __name__ == '__main__':
main()
So a student instance always delegates grade and score related tasks to the member instance of the GradePointsAverage class. This shifts the Student class close to ebing a superfluous layer (as is) but in reality you would now stuff personal info identifying the student being modelled into the Student instance.
When I run above code on my machine (with a python v2 interpreter):
Positive test cases:
... service class under test:
GradePointAverage(['A+', 'C-'])
... main class under test:
Student(['F+'])
1
0.3
0.3
Added E-
2
0.5
0.7
Added ['E', 'b+', 'b-', 'c+', 'D', 'D']
8
2.1625
2.0
['F+', 'E-', 'E.', 'B+', 'B-', 'C+', 'D.', 'D.']
Negative test cases:
NotImplemented
... too long:
Invalid grade length
... wrong grade letter:
Invalid main grade
... wrong adjustment:
Invalid grade adjustment
... wrong grade "type" we did let it just bubble:
'int' object has no attribute 'strip'
[Finished in 0.0s]
One should IMO not over engineer toy problems, but this one might offer interesting extension tasks, like storing a hash/anonymized id with the student instances. That would match more real life, where the storage of data that might allow identification of a person or might have the potential to disclose private details of a person is often split into spreading salted hashes to attach to all class instances that need a back reference, but keep the mapping back to ther real names, dates and places etc. in one especially secure place only.
Also maybe introduce (besides the added median) also min/max i.e. worst/best score or grade "info" from any student instance, maybe even try a simple linear regression on the scores to find some "trend".
Another class of extensions would be trying to "cover" all paths in tests.
A further way to "blow things up" might be, a more elgant mini language internal mapping, where the "collection stages" (those mapping from grades to numeric values) are fully transformed to integers eg. by scaling all by a factor of ten and so have lossless arithmetics, with the price to think about "reporting" the expected back transformed real scores (i.e. as sample 4.3 and not 43) but also have the benefit of reporintg easily the grade representation from any score and remebering to only perform one final "rounding" step.
Note also that the helpful pep8 tool or e.g. python3 -m pep8 so_get_gpa_for_student_edited.py gives neither errors nor warnings.
Another hint, often during development and when extending objects / adding fetaures, the names slowly drift out of bounds. Until now (to me) GradePointAverage is a matching class / type name, as I often accept a median in comparison to an arithmetic average as a usefull twin information. But if I already had entered say. a trend method, then it would have been a good time to further separate the functionality or rename the class.
Also deciding on a consisten error strategy helps a lot. In above code we mostly state the problem class, but eg. do not report back what exactly caused the problem. In many cases this is ok or even wanted, but in other cases one might add some detail to the response.
One last detail: Just so you know how to first define all interface methods, and second implement these one by one while incrementally testing I also added one of the tricks to signal when somethig is planned, but not (yet) implemented. Here I simply return NotImplementedin the best_score method. One might also raise NotImplementedError("Method_best_score") instead, but as this leads to:
File "/Users/sthagen/tmp/so_get_gpa_for_student_edited.py", line 184, in <module>
main()
File "/Users/sthagen/tmp/so_get_gpa_for_student_edited.py", line 157, in main
print(student.best_score())
File "/Users/sthagen/tmp/so_get_gpa_for_student_edited.py", line 117, in best_score
return self._gPA.best_score()
File "/Users/sthagen/tmp/so_get_gpa_for_student_edited.py", line 90, in best_score
raise NotImplementedError("Method best_score")
NotImplementedError: Method best_score
I often during active creation from "zero" the more silent return NotImplemented option and in (pre-)production when a call to a not yet implemented method or function is more probable an error in usage, I switch to the Exception, your mileage may vary ...
Please feel free to comment (in case I misread the task) or if I forgot to comment on a change you notice when comparing to your code smaple.
Related
I develop bottom up, starting with small simple methods to go to the big full fledged implementation
class Pop(object):
def welcome(self, name, new_member = False):
response = ""
if new_member:
response = " NOT"
return str("hello there "+name+", you seem"+response+" to be a member\n")
def ageVerification(self, name, age, new_member = False):
the_welcome_string = self.welcome(name, new_member)
minimum = ""
excuse = ""
if age < 16:
minimum = " NOT"
excuse = ", sorry"
return str(the_welcome_string+str(age)+" is"+minimum+" the minimum required age to buy beer in Belgium"+excuse+"\n")
def theWholething(self, name, age, address, new_member = False):
if age < 16:
appology = str("you cannot order any beer\n")
else:
appology = str("your beer will be shipped to "+address+"\n")
return str(self.ageVerification(name, age, new_member)+appology)
# EOF
My question is if it is normal that when i reach theWholeThingMethod, I carry along all the parameters of the previously defined methods? Is this pythonic?
My population class has almost 20 "helper" methods called in theWholeThing, and it seems I am just fiddling with parameters to get them in the right order ...
theWholeThing(self,\
name,\
age,\
address,\
registered = True,\
first_date_entered,\
last_date_entered,\
purchased_amount,\
favorite_beer,\
promotional_code,\
and_so_on0,\
and_so_on1,\
and_so_on2,\
and_so_on3,\
and_so_on4,\
and_so_on5,\
and_so_on6,\
and_so_on7,\
and_so_on8,\
and_so_on9,\
and_so_on10):
My question is if it is normal that when i reach theWholeThingMethod, I carry along all the parameters of the previously defined methods? Is this pythonic?
Neither.
There is really no point in having a class if all the methods take all the arguments anyway. These might as well just be functions.
There are many ways this could be done, depending on whether the various parameters are mandatory, or what happens when one is not provided, but here is one possibility:
from dataclasses import dataclass
#dataclass
class Pop(object):
name: str
age: int
address: str
new_member : bool = False
def welcome(self):
response = ""
if self.new_member:
response = " NOT"
return str("hello there "+self.name+", you seem"+response+" to be a member\n")
def ageVerification(self):
the_welcome_string = self.welcome()
minimum = ""
excuse = ""
if self.age < 16:
minimum = " NOT"
excuse = ", sorry"
return str(the_welcome_string+str(self.age)+" is"+minimum+" the minimum required age to buy beer in Belgium"+excuse+"\n")
def theWholething(self):
if self.age < 16:
appology = str("you cannot order any beer\n")
else:
appology = str("your beer will be shipped to "+self.address+"\n")
return str(self.ageVerification()+appology)
# EOF
Note: #nneonneo had a great suggestion of using a dataclasses, so answer tweaked to incorporate that
I'd like to create a class that can use the current version of the data inputted to it.
I've tried:
class DisplayUpdatedNum:
def __init__(self, number):
self.the_num = number
def print_num(self):
print(f'the number is {self.the_num}')
my_num = 1
class_inst = DisplayUpdatedNum(my_num)
class_inst.print_num()
# requested output: the number is 1
my_num = 1905
class_inst.print_num()
# requested output: the number is 1905
This doesn't work, I get the original input number (1) when calling class_inst.print_num() even after changing my_num
Is there a pythonic solution to this?
in my opinion, what you are trying to do is a bad design (as already mentioned):
Attention: (bad solution)
num = 10
class DispalyUpdateNum(object):
global num
def print_num(self):
print(num)
c = DispalyUpdateNum()
c.print_num()
num = 100
c.print_num()
>>>10
>>>100
Much better would be if you use the #property, something like this :
class DispalyUpdateNum(object):
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value
print("The number is %s" % self._value)
c = DispalyUpdateNum()
c.value = 100
c.value = 10000
c.value = 777
>>> The number is 100
>>> The number is 10000
>>> The number is 777
Would this solution fit in your code is up to you, good luck :)
The class does not have the responsibility to be aware of the changes of a variable at the global level. In fact one of the principles of OOP is to encapsulate the variables that give context to the class (data attributes). There is no pythonic way, but there a straightforward OOP way:
from numbers import Real
class DisplayUpdatedNum:
def __init__(self, number):
self._the_num = number
#property
def num(self):
return self._the_num
#num.setter
def num(self,value):
if not isinstance(value,Real):
raise TypeError('Not a valid numeric type!') #you have now the chance to do some validations here (if you need to)
self._the_num = value
def print_num(self):
print(f'the number is {self._the_num}')
Now you can instaciate your custom class, and when you want to change the valua of the _the_num pseudo-private attr, you just call the num interface that gives you the property class with his getter and setter:
my_num = 1
class_inst = DisplayUpdatedNum(my_num)
class_inst.print_num()
# requested output: the number is 1
my_num = 1905
class_inst.num = my_num
class_inst.print_num()
# requested output: the number is 1905
# Try the basic validation
my_mun = '1905'
class_inst.num = my_num
class_inst.print_num()
# requested output: TypeError: Not a valid numeric type!
Also if you want a little bit further in OOP and combinate it to pythonic ways to do this kind of thing, you could read up about data desciptors
I am making a text based adventure game in python. Once the game begins, I would like to create an instance of a class called "Character" which is the player's character object. I would like the user to be able to choose the race of the character they want to play. So far I have:
class Race:
def __init__(self, name, passive, hp):
self.name = name
self.passive = passive
self.hp = hp
and
class Lizard(Race):
def __init__(self, name, passive, hp):
super().__init__(name, passive, hp)
self.name = 'Lizardman'
self.passive = 'Regrowth'
self.hp = 20
def regrowth(self):
if 0 < self.hp <= 18:
self.hp += 2
and
def race_select():
races = ['Lizard']
while True:
for i, j in enumerate(races):
print(f"[{i + 1}]", j)
choice = int(input('Pick a race:'))
if choice <= len(races):
print('You are a ', races[choice - 1])
return races[choice - 1]
else:
continue
If I understand correctly, if I wanted the race to be a Lizard, I would still have to do
character = Lizard('Lizardman', 'Regrowth', 20)
Is there an easy way to let the user choose the race and the object to be created accordingly? Thanks
A simple solution would be to map a name to a class using a dictionary. As a simple example:
race_map = {"lizard": Lizard,
"human": Human} # I'm adding a theoretical other class as an example
choice = input('Pick a race:')
race_initializer = race_map.get(choice, None) # Get the chosen class, or None if input is bad
if race_initializer is None:
# They entered bad input that doesn't correspond to a race
else:
new_creature = race_initializer(their_name, their_passive, their_hp)
new_creature is now the new object of the chosen class.
You may want to standardize the input using choice.lower() to ensure that capitalization doesn't matter when they enter their choice.
I changed it to allow for specifying a race by a string name instead of a number. If you wanted a number, you could keep your list, but apply the same idea. Something like:
race_list = races = [('Lizard', Lizard), ('human', Human)]
choice = int(input('Pick a race:'))
try:
race_initializer = race_list[choice][1] # 1 because the class object is the second in the tuple
new_creature = race_initializer(their_name, their_passive, their_hp)
except IndexError:
# Bad input
I included the name in the race_list so that you can loop over the list and print out index->name associations for the user to pick from.
You may also want to use a more robust structure than a plain tuple to store name->initializer mappings, but it works well in simple cases.
Let's say I have a spell named heal. How can I prevent a user from spamming heal every time they are damaged. I have considered applying this to individual combat functions; however, I am not sure how to implement a global rule for this? This code may clear it up:
available_spells = ['fireball', 'heal']
equipped = {'Weapon': "Staff",
'Armor': "Robes",
'Spells': ['fireball', 'heal']}
print "Your available spell(s) is(are) '%s'. " % equipped["Spells"]
inp = raw_input("Type the name of a spell you want to use.: ").lower()
lst = [x for x in available_spells if x.startswith(inp)]
if len(lst) == 0:
print "No such spell"
print ' '
elif len(lst) == 1:
spell = lst[0]
print "You picked", spell
#COMBAT FUNCTIONS HERE
else:
print "Which spell of", equipped["Spells"], "do you mean?"
If I were to make a class that defines certain actions for spells to take, how could I implement that into the code I have? For example if I have a class of spells, with functions defining damage rules, cool down times, etc., how could I reference that function in the code I already have? i.e. the player types 'heal' and I want it to reference an above class that has those values defined to check if the player recently played the spell, and what it does when played.
Am I clear enough in this question? How should I write a spell cool-down mechanic? How can I implement this mechanic into the code above?
Instead of storing all available spells as a list, you could store them as a dictionary, which allows you to also store the desired cooldown duration:
available_spells = {
# spell name: cooldown duration in seconds
'fireball': 3.0,
'heal': 5.0,
}
Each player could have another dict that keeps track of the last time they cast each spell. When the game starts, it would be empty:
cast_spells = {}
When the player attempts to cast a spell, check if the spell name is in the cast_spells dict. If it's not, then they have not yet cast it this game, so they are allowed to cast it:
if spell_name not in cast_spells:
cast_spells[spell_name] = datetime.now()
Otherwise, if the spell name is in the cast_spells dict, check if the required cooldown has elapsed:
elif cast_spells[spell_name] + datetime.timedelta(seconds=spells[spell_name]) < datetime.now():
cast_spells[spell_name] = datetime.now()
Otherwise, the cooldown is still in effect.
else:
print 'Spell not ready.'
I would probably do it using with, an exception handler, and a simple timer. That way you can just repeat the cooldown pattern, have shared cooldowns (like shown below), or even global cooldowns, etc.
Here are the classes:
import time
class CooldownException(Exception):
pass
class Cooldown(object):
def __init__(self, seconds):
self.seconds = seconds
self.expire = None
def __enter__(self):
if not self.expire or time.time() > self.expire:
self.expire = time.time() + self.seconds
else:
raise CooldownException('Cooldown not expired!')
def __exit__(self, type, value, traceback):
pass
heal_cooldown = Cooldown(5)
def heal():
try:
with heal_cooldown:
print 'You heal yourself!'
except CooldownException as e:
print e
def apply_bandage():
try:
with heal_cooldown:
print 'You bandage yourself!'
except CooldownException as e:
print e
def drink_potion():
try:
with heal_cooldown:
print 'You quaff a potion!'
except CooldownException as e:
print e
And here's how they're used:
>>> heal()
You heal yourself!
>>> time.sleep(3)
>>> drink_potion()
Cooldown not expired!
>>> time.sleep(3)
>>> apply_bandage()
You bandage yourself!
If I were to make a class that defines certain actions for spells to take, how could I implement that into the code I have?
As you guessed, your problem is very well suited to classes.
Am I clear enough in this question?
Yes.
Your program, but with classes
Here is your program modified to use two custom classes, FireballSpell and HealSpell. Each one has a .name, which is a string, and a .cast(), which is a custom behaviour. It's nearly identical to your original code, so it should be easy for you to understand:
available_spells = [FireballSpell(), HealSpell()]
equipped = {'Weapon': "Staff",
'Armor': "Robes",
'Spells': [FireballSpell(), HealSpell()]}
while True:
print "Your available spell(s) is(are) '%s'. " % [spell.name for spell in equipped["Spells"]]
inp = raw_input("Type the name of a spell you want to use.: ").lower()
lst = [spell for spell in available_spells if spell.name.startswith(inp)]
if len(lst) == 0:
print "No such spell"
print ' '
elif len(lst) == 1:
spell = lst[0]
print "You picked", spell.name
spell.cast()
else:
print "Which spell of", [spell.name for spell in equipped["Spells"]], "do you mean?"
print ""
Run it and give it a try! Here is the complete script. I'm pretty sure it does exactly what you want.
Specific spells
Each specific class has a name, cooldown time, and specific behaviour. The parent Spell class (see bottom) handles the rest.
class FireballSpell(Spell):
def __init__(self):
self.name = "fireball"
self.cooldown_seconds = 5
def spell_specific_behaviour(self):
# do whatever you like with fireball
# this is only called if the spell has cooled down
print "casting fireball"
class HealSpell(Spell):
def __init__(self):
self.name = "heal"
self.cooldown_seconds = 10
def spell_specific_behaviour(self):
# same applies here as from FireballSpell
print "casting heal"
Spell class
This is a generic Spell class - the parent of all spells. It knows the name, cooldown time, and behaviour from the specific spells (child classes above). It also has generic cooldown mechanic that's shared by the spells:
class Spell:
# spell data - filled in by specific spells
name = "default"
cooldown_seconds = 0
last_cast_time = 0
def cast(self):
# only cast the spell if it has cooled down
if self.is_cooled_down():
# call the behaviour set by the specific spell
self.spell_specific_behaviour();
# set the last cast time to the current time
self.last_cast_time = time.time()
else:
print self.name + " has not cooled down yet!"
def spell_specific_behaviour(self):
# implement in specific spell subclasses
return
def is_cooled_down(self):
current_time_seconds = time.time()
cooldown_expire_time_seconds = self.last_cast_time + self.cooldown_seconds
return current_time_seconds > cooldown_expire_time_seconds
Again, here is the whole thing in one working script. Have fun!
META: decorators, exceptions, and with blocks? Whoa, guys. OP is just now learning about classes. Let's keep it simple here.
Here is another example using decorators...
from functools import wraps
class Cooldown(object):
def __init__(self, seconds, cooldown_message):
self.seconds = seconds
self.expire = None
self.cooldown_message = cooldown_message
def decorator(self, fail_message_callback):
def _wrap_decorator(foo):
def _decorator(*args, **kwargs):
if not self.expire or time.time() > self.expire:
self.expire = time.time() + self.seconds
result = foo(*args, **kwargs)
return result
else:
if fail_message_callback:
fail_message_callback(self.cooldown_message)
return None
return wraps(foo)(_decorator)
return _wrap_decorator
heal_cooldown = Cooldown(5, 'Cooldown not expired!')
def display(message):
print message
#heal_cooldown.decorator(display)
def heal():
display('You heal yourself!')
#heal_cooldown.decorator(display)
def apply_bandage():
display('You bandage yourself!')
#heal_cooldown.decorator(display)
def drink_potion():
display('You quaff a potion!')
heal()
time.sleep(3)
drink_potion()
time.sleep(3)
apply_bandage()
class courseInfo(object):
def __init__(self, courseName):
self.courseName = courseName
self.grade = "No Grade"
def setGrade(self, grade):
if self.grade == "No Grade":
self.grade = grade
def getGrade(self):
return self.grade
class edx(object):
def __init__(self, courses):
self.myCourses = []
for course in courses:
self.myCourses.append(courseInfo(course))
def setGrade(self, grade, course="6.01x"):
"""
grade:integer greater than or equal to 0 and less than or equal to 100
course: string
This method sets the grade in the courseInfo object named by `course`.
If `course` was not part of the initialization, then no grade is set, and no
error is thrown.
The method does not return a value.
"""
for crs in self.myCourses:
if crs.courseName == course:
crs.setGrade(grade)
def getGrade(self, course="6.02x"):
"""
course: string
This method gets the grade in the the courseInfo object named by `course`.
returns: the integer grade for `course`.
If `course` was not part of the initialization, returns -1.
"""
for crs in self.myCourses:
if crs.courseName == course:
return crs.getGrade()
else:
return -1
My test cases for the code above are:
> edX = edx( ["6.00x","6.01x","6.02x"] )
> edX.setGrade(100, "6.00x")
> edX.getGrade("6.00x")
> 100
So far so good, but running edX.setGrade(50, "6.00x") doesn't take and getGrade still returns 100. Also, setting grades for 6.01 and 6.02 doesn't seem to work and getGrade returns -1 all the time.
Any help pointers would be much appreciated!
(Full disclosure: this is for an on-line course. But I don't think there are spoilers here for anyone and I really want to understand what's going on. Tks)
Of course it only works once, you coded it that way:
def setGrade(self, grade):
if self.grade == "No Grade":
self.grade = grade
After setting the grade, the test self.grade == "No Grade" is no longer true.
You do have a problem in the getGrade() method:
for crs in self.myCourses:
if crs.courseName == course:
return crs.getGrade()
else:
return -1
You return -1 if the first course doesn't match the name; you return from the for loop right there. Perhaps you want to return -1 only after testing all courses:
for crs in self.myCourses:
if crs.courseName == course:
return crs.getGrade()
return -1
Now the for loop is only interrupted if the matching course is found, and you no longer return -1 for anything other than "6.00x".