Counter variable for class - python

I am having problem getting this piece of code to run. The class is Student which has a IdCounter, and it is where the problem seems to be. (at line 8)
class Student:
idCounter = 0
def __init__(self):
self.gpa = 0
self.record = {}
# Each time I create a new student, the idCounter increment
idCounter += 1
self.name = 'Student {0}'.format(Student.idCounter)
classRoster = [] # List of students
for number in range(25):
newStudent = Student()
classRoster.append(newStudent)
print(newStudent.name)
I am trying to have this idCounter inside my Student class, so I can have it as part of the student's name (which is really an ID#, for example Student 12345. But I have been getting error.
Traceback (most recent call last):
File "/Users/yanwchan/Documents/test.py", line 13, in <module>
newStudent = Student()
File "/Users/yanwchan/Documents/test.py", line 8, in __init__
idCounter += 1
UnboundLocalError: local variable 'idCounter' referenced before assignment
I tried to put the idCounter += 1 in before, after, all combination, but I am still getting the referenced before assignment error, can you explain to me what I am doing wrong?

The class variable has to be accessed via the class name, in this example Studend.idCounter:
class Student:
# A student ID counter
idCounter = 0
def __init__(self):
self.gpa = 0
self.record = {}
# Each time I create a new student, the idCounter increment
Student.idCounter += 1
self.name = 'Student {0}'.format(Student.idCounter)
classRoster = [] # List of students
for number in range(25):
newStudent = Student()
classRoster.append(newStudent)
print(newStudent.name)
Thanks to the point out by Ignacio, Vazquez-Abrams, figured it out...

Coming to this answer some time ago helped me find what I needed to sort out class versus instance variables and their scoping.
So, an extension, which does the same thing, only using a generator. The generator assigns a unique number to the student as idCounter does -- only it consumes the values. There is no prev method on the generator class, of which I'm aware. Neither idGenerator nor idCounter is memoized, so if you want to externalize the list then come back to add one or more students, you'd have to update the range(start,,) accordingly, or iterate through each value without assigning it until you arrive at the unique one in sequence, a path somewhat shorter with idCounter which you cou simply set with a single dummy instance construct and go.
class Student:
""" Implement a shared generator among all sub-classes
in addition to idCounter. """
# A student ID counter
idCounter = 0
# A student ID from generator
idGenerator = (x for x in range(0xAAAAAA, 0xEEEEEE, 0xBA))
def __init__(self):
self.gpa = 0
self.record = {}
# Each time I create a new student, the idCounter increment
Student.idCounter += 1
self.id = Student.idGenerator.__next__()
self.name = f"{self.id} Student {Student.idCounter}"
classRoster = [] # List of students
for number in range(25):
newStudent = Student()
classRoster.append(newStudent)
print(newStudent.name)

Related

How to get object from class based on unique attribute?

I have a class called Student, where they have two attributes: studentName and a unique number for each student, studentID.
In another class, called Course, I already have a function
add_student(self, student) that adds the given student to the given course. I want to create another function add_student_list(self, lst) which intakes a list of studentIDs. I have a for loop which looks at the IDs in lst to see which students to add, I just need to call the add_student(self, student) function for the ones in lst, however, I'm unable to do this, as I only have an id, and not the student object.
So my question is, how can I call the necessary student object based on the unique ID?
EDIT: here's the relevant part for my code:
import itertools
import pandas as pd
class Student (object):
#The following two objects are used to assign them unique IDs and to keep track of them.
id_iter = itertools.count()
all_students = pd.DataFrame(columns=["Student", "Student ID"])
#Adding a new student:
def __init__(self, studentName):
#The name of the student will be given, the ID will be generated:
self.studentName = [studentName, next(self.id_iter)]
#The new student will have a list of courses attended:
self.courses = pd.DataFrame(columns=["Course", "Course ID", "Passed/Failed", "Completed Assignments"])
#The new student will be added to the list of all students:
Student.all_students = pd.concat([Student.all_students, pd.DataFrame.from_dict({"Student": self.studentName[0], "Student ID": self.studentName[1]}, orient = "index")], ignore_index = True, axis = 1)
Student.all_students = Student.all_students.dropna(axis=1)
def __str__(self):
return str(self.studentName)
class Course (object):
#The two objects are similar to that of the student class:
id_iter = itertools.count()
all_courses = pd.DataFrame(columns=["Course", "Course ID"])
#the courses are similarly added as students above
def add_student(self,student,completed_assignments):
#this adds students with the number of assingments they completed (not relevant), the code is not relevant
def add_student_list(self, lst):
for i in range(0,len(lst)):
for j in range(0,len(Student.all_students.swapaxes("index", "columns"))):
if lst[i][0] == Student.all_students[j][1]:
self.add_student()
Two methods:
Store your students in a dictionary with a id as the key. Than can retrieve the students based on their ids.
Search through the students everytime you have a list.
ids_to_search = [2, 34, 3]
students = [student for student in students if student.id==id_to_search]

append data to constructor empty list variable in python

I have one class. For that i want to append data to constructor empty list variable. I am trying to append data. But it's not working and throwing error as "NameError: name 'items' is not defined". before this code has been worked.
Here it my code snippet :
class data:
def __init__(self,items=[]):
self.items = items
self.m1(n)
def m1(self,n):
self.n=2
for i in range(self.n):
d = input('enter the values :')
self.items.append(d)
print(self.items)
d=data(items)
here are some issues wrong:
1.) On line 11, items is not defined anywhere before trying to initialize the class, so you end up receiving an error when you call
d=data(items)
2.) On line 4, n is not defined. It is neither passed in along as a parameter with the constructor or defined elsewhere within the constructor block. You will need to define n.
Here is a working version though, with all the variables properly defined:
class data:
def __init__(self, n, items=[]):
self.items = items
self.m1(n)
def m1(self, n):
self.n=2
for i in range(self.n):
d = input('enter the values :')
self.items.append(d)
print(self.items)
items = [1, 5, 7]
d = data(2, items)
class data:
def __init__(self,number,name,list_values=[]):
self.number = int(input('Enter a number :'))
self.name = name
self.list_values = list_values
self.m1()
def m1(self):
for i in range(self.number):
items = input('Enter the values :')
self.list_values.append(items)
print(self.list_values)
list_values= None
d=data('siddarth',list_values)

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.

Python creating an Object while reading file

I wrote a simple Python script to determine if all students grades are reported. The script first loops through and adds students to arrays regarding grade status. Then I loop through the file again, to determine if each students grades are in. I end up with three arrays that include students with "all grades reported", "some grades reported", "no grades reported". However, I want tackle this problem with more of a object oriented approach. I have attempted to create a class that works. I am stuck at how to loop through and create one Object for each student, then use addcourse to push each course into the Object. Any help I can get to become a better programmer would be great!
Data:
**id,fname,lname,course,grade,mode**
10001,Freddy,Freshman,Art-101,A,online
10001,Freddy,Freshman,Art-101,A,online
10002,Suize,Sophmore,Mat-102,C,inperson
10002,Suize,Sophmore,Bio-101, ,inperson
10002,Suize,Sophmore,Soc-201,D,online
10003,Jilly,Junior,mth-102, ,inperson
10003,Jilly,Junior,Bus-101, ,inperson
10003,Jilly,Junior,Che-204, ,inperson
Working Code:
fh = open('students.txt').readlines()
header = fh.pop(0)
gradereported = []
nogradereported = []
for line in fh:
students = line.split(',')
ids = students[0]
grade = students[4]
if grade != "":
gradereported.append(ids)
else:
nogradereported.append(ids)
allgradesin =[]
nogradesin = []
somegradesin = []
for i in fh:
students = line.split(',')
ids = students[0]
if ids in gradereported and ids not in nogradereported:
if ids not in allgradesin:
allgradesin.append(ids)
elif ids not in gradereported and ids in nogradereported:
if ids not in nogradesin:
nogradesin.append(ids)
elif ids in gradereportedand and ids in nogradereported:
if ids not in somegradesin:
somegradesin.append(ids)
Attempt at class:
class Student(object):
def __init__(self, lname, fname, term, courses = []):
self.studid = studid
self.lname = lname
self.fname = fname
self.term = term
self.courses = []
def addcourse(self, course, grade, mode):
self.course = course
self.grade = grade
self.mode = mode
self.courses.append((self.course, self.grade, self.mode))
You could do this, as #blade suggests, by creating a dictionary indexed by student id and then for each row of your input file either get the existing student from the dictionary if it exists or create a new one. In code, this would look like:
class Student(object):
def __init__(self, student_id, lname, fname):
self.studid = student_id
self.lname = lname
self.fname = fname
self.courses = []
def addcourse(self, course, grade, mode):
self.courses.append((course, grade, mode))
students = {}
fh = open('students.txt').readlines()
header = fh.pop(0)
for line in fh:
row = line.split(',')
if len(row) < 6:
continue
student_id, fname, lname, course, grade, mode = [i.strip() for i in row]
student = students.get(student_id, Student(student_id, lname, fname))
student.addcourse(course, grade, mode)
students[student_id] = student
A couple of things to note. First, I modified the constructor of your Student class, dropping the term argument since it wasn't clear where the term was specified in your input file. Furthermore, since you don't use the courses argument I dropped that as well. (Note that you probably don't want to use [] as a default argument. Read about mutable default arguments here.) You also don't need to create instance variables for the course, grade, and mode in your addcourse function, you can just append them directly to the array.
I also added a call to strip for each of the items pulled from the input file to clean up the newlines at the end of each row.
How about this:
Add a dict that id is the key, and the Student object is the value
Loop the file and if the key is in the dict, get the Student object from the dict. Otherwise create a new Student object. Then add the course to the Student object.
In addition to the answer of #JCVanHanne you could define another function in your class to collect the info, whether a student has none, some or all of his/her grades.
One possible way (assuming a missing grade is represented by an empty string while also grades like A+ or other none-empty values are possible) could be:
def gradeStatus(self):
miss = [course[1] for course in self.courses].count("") # count empty grades
if len(self.courses) == miss:
print('No grades at all')
elif miss in range(1, len(self.courses)):
print('Some, but not all grades')
elif miss == 0:
print('All grades provided')
else:
print('Invalid Data')
You probably would use status codes or other ways (like a return value to further process) to work with the information than just printing them. As an example with the print commands:
students['10003'].gradeStatus() # leads to: No grades at all

Simple OOP query. Python: saving object names in __init__

As close to the title as possible. I am very new to OOP (and coding in general) and would like to create a program that plays Blackjack. I want to save the objects I create into a list automatically so once it's created I can use the list to cycle through them (I want to create player objects, but save the variable names (right word???) to a list so once it's created using user input I can automatically access them.
So far I've built this:
ROSTER = []
class Player():
"""player in the game"""
def __init__(self, name, score= 0):
self.name = name
self.score = score
ROSTER.append(self.name)
But of course this only gives me the names put into the variable self.name... how can I capture the variable names (right term once again?). self.name won't (afaik) let me access the individual objects via:
excuse the crap formatting plz. =/
Also, if I'm using the wrong terms plz correct me. Learning on your own is kinda hard as far as mastering all the terms.
EDIT: sorry, my post was confusing. The code I posted was meant to show a dead end, not what I am looking for, and my terminology is pretty bad (I feel like a foreigner most of the time). When I said variable names, I think I should have said 'object names' (?) so:
p1 = Player("bob")
p2 = Player("sue")
I want ["p1","p2"] (or if a string format will give me problems when I try to call them, whatever the appropriate way is.)
Once again, sorry for the super confusing first post. Hopefully this edit is a little clearer and more focused.
You could put self in the roster instead. I.e.:
ROSTER = []
class Player():
def __init__(self, name, score = 0):
self.name = name
self.score = score
ROSTER.append(self)
Then you would use the ROSTER list like this:
>>> p1 = Player("Jane")
>>> p2 = Player("John")
>>> ROSTER
[<__main__.Player instance at 0x10a937a70>, <__main__.Player instance at 0x10a937a28>]
>>> for p in ROSTER:
... print p.name, p.score
...
Jane 0
John 0
Or, perhaps better, you could make ROSTER a dictionary:
ROSTER = dict()
class Player():
def __init__(self, name, score = 0):
self.name = name
self.score = score
ROSTER[self.name] = self
That way you can access the player objects by name using ROSTER[name], and you can cycle through them with ROSTER.values(). For example:
>>> p1 = Player("Jane")
>>> p2 = Player("John")
>>> print ROSTER["Jane"].name, ROSTER["Jane"].score
Jane 0
>>> print ROSTER["John"].name, ROSTER["John"].score
John 0
>>> for p in ROSTER.values():
... print p.name, p.score
...
Jane 0
John 0
Are you talking about this?
ROSTER = []
class Player():
def __init__(self, name, score= 0):
self.name = name
self.score = score
ROSTER.append(self)
a=Player('Jack',100)
b=Player('Blackk',1000)
c=Player('Mike')
for x in ROSTER:
print(x.name,x.score)
output:
Jack 100
Blackk 1000
Mike 0

Categories