Python creating an Object while reading file - python

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

Related

How do I take a list and parse into a list of objects in a class? (Python)

I have a class with several objects. I am supposed to take a text file, open it and parse it into a list of objects, then return the list of Entry objects
I'm struggling to understand how to convert the list I made from the file, into a list of objects.
class Entry:
def __init__(self, account_num, name, balance, phone, city):
self.account_num = account_num
self.name = name
self.balance = balance
self.phone = phone
self.city = city
def read_file(file_given):
open_file = open(file_given)
entry_obj = []
for line in open_file:
word = line.split()
entry_obj.append(word)
open_file.close()
return entry_obj
the text file I'm reading contains:
100 Alan Jones 348.17 8053564820 SLO
700 Suzy Green -14.22 8052586912 SLO
Maybe something like this should work:
def map_to_entry(line):
attribute_list = line.split()
account_num = attribute_list[0]
name = "{} {}".format(attribute_list[1], attribute_list[2])
balance = attribute_list[3]
phone = attribute_list[4]
city = attribute_list[5]
return Entry(account_num, name, balance, phone, city)
def read_file(file_given):
open_file = open(file_given)
entry_obj = []
for line in open_file:
entry_obj.append(map_to_entry(line))
open_file.close()
return entry_obj
It could be as easy as using
objList = [Entry(*li) for li in data]
But sadly, for some reason your names are split like this. Alan Jones and it creates a list like
[['100', 'Alan', 'Jones', '348.17', '8053564820', 'SLO'], ['700', 'Suzy', 'Green', '-14.22', '8052586912', 'SLO']]
therefor we will get the error, TypeError: __init__() takes 6 positional arguments but 7 were given.
So we have to join these elements.
new = []
for l in read_file():
l[1:2] = [' '.join(l[1:3])]
l.pop(2)
new.append(l)
objList = [Entry(*li) for li in new]
objList will look like.
[<__main__.Entry object at 0x7fe78db6a970>, <__main__.Entry object at 0x7fe78db6acd0>]
You can unpack all the elements during an iteration. If your names are always assumed to be First Name and Last Name, you can concatenate them before creating the Entry object. The following example is quite readable:
entries = []
for account_num, first_name, last_name, balance, phone, city in read_file('data.txt'):
name = first_name + " " + last_name
entries.append(Entry(account_num, name, balance, phone, city))
Also one-liner if you're into that:
entries = [Entry(account_num, first_name + " " + last_name, balance, phone, city) for account_num, first_name, last_name, balance, phone, city in read_file('ok.txt')]
Unpacking in python means to take an iterable with a size you know before-hand and unpack every single element into different variables. Something like:
a, b, c = ['a', 'b', 'c']
The same idea can be applied to elements in a for loop, on which element is another iterable (of a fixed size) itself, as is the case of each row in your file. This allows you to convert a list into named variables, which are usually easier to read.
If your original question was to modify the read_file function, you can use a similar idea and unpack while iterating the file:
def read_file(file_given):
open_file = open(file_given)
entry_obj = []
for line in open_file:
account_num, first_name, last_name, balance, phone, city = line.split()
name = first_name + " " + last_name
entry_obj.append(Entry(account_num, name, balance, phone, city))
open_file.close()
return entry_obj
You defined your class already, but in order to 'instantiate' (create an instance of) class objects, now you must call the class. (seen in example below as Entry(), where the () lets us know that we are calling / invoking the thing)
You definitely want to keep your arguments spelled out in the init() method, just as you have them. It's good to keep things concise and human-readable.
*edit - i changed the name args to be _first, and _last, separately. Reason below.
Note that in the below example, the loop variable line is a complete, single set of Entry data. Ergo, by using an asterisk before it, Python will look for a list (or really, any iterable) and it will automatically send each list element to the function call as its own arg.
e.g. if line == 'a b c', then Entry(*line.split()) is equivalent to Entry('a', 'b', 'c')
class Entry:
def __init__(self, account_num, name_first, name_last, balance, phone, city):
self.account_num = account_num
self.name = name_first + ' ' + name_last
self.balance = balance
self.phone = phone
self.city = city
def read_file(file_given):
open_file = open(file_given)
lines = open_file.readlines()
entry_obj = []
for line in lines:
entry_obj.append(Entry(*line.split()))
open_file.close()
return entry_obj
**edit - this, of course, assumes that we know the order of the data columns as they are received. there are much more robust ways of making this code future-proof, but I digress.

Avoid creating another object if an object representing that person already exists (using first and last name from excel data)

I have a spread sheet containing student names and test scores in this format:
first name, last, score
Each student can take the test up to three times, however they are given another row if they attempt the test more than once, example:
John, Smith, 80
Sally, Williams, 90
John, Smith, 100
I am trying to create Student objects for each student and add these to a ClassOfStudents object. But I cannot figure out how to avoid creating 'John Smith' twice. Here are the two classes:
class Student:
def __init__(self, first_name, last_name ):
self.first_name = first_name
self.last_name = last_name
self.score_first_attempt = 0
self.score_second_attempt = 0
self.score_third_attempt = 0
class ClassOfStudents:
"""Represents one class"""
def __init__(self, cohort, assignment_name):
""" intitalize empty list, will hold Student objects """
self.students = []
def add_student(self, student_obj):
self.students.append(student_obj)
and here is my main.py where I read the excel data and create objects from said data:
from student import Student, ClassOfStudents
from openpyxl import load_workbook
# intentionally left out openpxyl code but am reading excel data via the 'sheet_obj' variable
# initialize object that will hold all Student objects
class_of_students= ClassOfStudents()
# Create Student objects and add them to class_of_students
for i in range(1, 3):
first_name = sheet_obj.cell(row = i, column = 2).value
last_name = sheet_obj.cell(row =i, column = 1).value
score = sheet_obj.cell(row = i, column= 3).value
student_obj = Student(first_name, last_name) # create student object
# if there are no Student objects in class_of_students object, add the first one
if not class_of_students_obj.students:
class_of_students.add_student(student_obj)
# loop through class_of_students, if student is already included in class_of_students do not add this iterations student_obj, just discard it
for student in class_of_students_obj.students:
if student.first_name == first_name and student.last_name == last_name:
# logic for retrieving existing object and adding score_second_attempt value would go here
else:
class_of_students_obj.add_student(student_obj)
My code creates 3 Student objects and adds them all to class_of_students (John Smith is created twice). I believe this is because 'Sally Williams' is eventually being compared to 'John Smith', thus creating the third object. I think my attempt is approaching this in the completely wrong way. Can anyone offer a better approach to avoid creating duplicate Student objects that represent the same physical student? Thanks for any help. (I also left out adding the score_first_attempt value intentionally since I need to avoid duplicates before focusing on that)
Just add a loop variable:
exists = False
for student in class_of_students_obj.students:
if student.first_name == first_name and student.last_name == last_name:
exists = student
break
if exists:
# logic for retrieving existing object and adding score_second_attempt value would go here
# the student object of the particular name is in exists object
else:
class_of_students_obj.add_student(student_obj)
In main.py:
from student import Student, ClassOfStudents
from openpyxl import load_workbook
# intentionally left out openpxyl code but am reading excel data via the 'sheet_obj' variable
# initialize object that will hold all Student objects
class_of_students= ClassOfStudents()
# Create Student objects and add them to class_of_students
for i in range(1, 3):
first_name = sheet_obj.cell(row = i, column = 2).value
last_name = sheet_obj.cell(row =i, column = 1).value
score = sheet_obj.cell(row = i, column= 3).value
student_obj = Student(first_name, last_name) # create student object
# if there are no Student objects in class_of_students object, add the first one
if not class_of_students_obj.students:
class_of_students.add_student(student_obj)
# loop through class_of_students, if student is already included in class_of_students do not add this iterations student_obj, just discard it
exists = False
for student in class_of_students_obj.students:
if student.first_name == first_name and student.last_name == last_name:
exists = student
break
if exists:
# logic for retrieving existing object and adding score_second_attempt value would go here
# the student object of the particular name is in exists object
else:
class_of_students_obj.add_student(student_obj)
You can make your class student a dataclass
import dataclasses
#dataclasses.dataclass
class Student:
first_name: str
last_name: str
score_first_attempt: float = 0
score_second_attempt: float = 0
score_third_attempt: float = 0
Now you do not have to loop through your class_of_students_obj.students, just make a check if your dataclass is in your list
# Create Student objects and add them to class_of_students
for i in range(1, 3):
first_name = sheet_obj.cell(row = i, column = 2).value
last_name = sheet_obj.cell(row =i, column = 1).value
score = sheet_obj.cell(row = i, column= 3).value
student_obj = Student(first_name, last_name) # create student object
# if student is already included in class_of_students do not add this iterations student_obj, just discard it
if student_obj in class_of_students_obj.students:
class_of_students_obj.add_student(student_obj)
Than we remove this part in your loop, since its useless now
# if there are no Student objects in class_of_students object, add the first one
if not class_of_students_obj.students:
class_of_students.add_student(student_obj)
If you wan't to make better, you can make the if not class_of_students_obj.students check part inside the add_student method

How to input data in classes in python, if number of students I want to input is not known

I want to input data in a class. I don't know the number of students I want to input. I can only write p1.name="John" p2.name="Jack" etc but if I want to input more students I have to write p3,p4,p5...
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person
p1.name="John"
p1.age=15
print(p1.name)
print(p1.age)
Is there a method to work like with arrays for example p[0].name="John"....... p[123].name="Jack" or something like that... Sorry for my bad english
You probably meant
p1 = Person('John', 15)
p2 = Person('Jack', 42)
If you needed an arbitrary number of objects, you can store them in a data structure like
people = [Person('John', 15), Person('Jack', 42)]
for person in people:
print(person.name)
You can read data into that array as well-
people = []
with open('people.txt') as f:
for line in f:
name, age = line.split()
people.append(Person(name, age))
But my advice would be to find a more introductory tutorial to follow, you're going to struggle :)
Sounds like you need a list.
A list is a data structure that you can use for zero or more elements, and you can access each element by iteration or by indexing the list.
You can do something like this:
persons = [
Person("John", 15),
Person("Adele", 16),
...
]
and then you can access each person by an index: persons[0] will give you Jonh, and persons[1] will give Adele.
if your objective is to store the students in an array-like structure, then you can definitely do this in Python. What you're currently doing is instantiating a new class object for each student. While this is totally valid, it may not be appropriate for the task you're trying to achieve.
Before mastering classes, I'd recommend familiarising yourself with python data structures like dictionaries, lists and tuples.
Just another idea. You could store all of the students in a dictionary that is contained within a class. Wrapping the dictionary in a class will let you add or fetch students very easily.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Classroom:
def __init__(self):
self.students = {}
def add_student(self, student):
self.students[student.name] = student
def get_student(self, name):
return self.students[name]
classroom = Classroom();
classroom.add_student(Person("John", 12))
classroom.add_student(Person("Sarah", 13))
print(classroom.get_student("Sarah").age)
#Output
#13
def main():
classroom = Classroom()
n = input("How many students would you like yo add? ")
for i in range(int(n)):
classroom.add_student(Person(input("Enter Name: "), int(input("Enter Age: "))))

Writing dictionary output to file - Python

I have a small program to track monthly balances. This worked fine as is, and then I added in the section to write to a .txt file at the bottom. I did some searching but can't figure out a way to make it work. Basically I want to keep appending to this test.txt file. Every time I enter a new month/account/balance I want that appended to the file.
The alternative is to append to the test.txt file after 'exit', so it doesn't do it every loop. Not sure which way is more efficient
***EDIT****
This updated code now creates test.txt file but the file is blank after each loop
Secondary question - I have heard this would be better off using a Class for this, but I haven't any idea how that would look. If someone wants to demonstrate that would be awesome. This isn't homework, this is strictly learning on my own time.
Any ideas? Thanks
# create program to track monthly account balances
savings = []
def add_accounts(date, account, balance):
savings.append({'date': date, 'account': account, 'balance':
balance})
def print_accounts():
print(savings)
while True:
date = input('Enter the date, type exit to exit program: ')
if date == 'exit':
break
account = input('Enter the account: ')
balance = int(input('Enter the balance: '))
add_accounts(date, account, balance)
print_accounts()
with open('test.txt', 'a') as f:
for row in savings():
print (row)
f.write(str(savings[-1]))
file.close()
The problem with your original code is that print_accounts() doesn't return anything, yet you attempt to perform operations on its (nonexistent) return value.
Here is a version of your program made using classes, and with a few corrections:
class Account:
def __init__(self, id, date, balance):
self.id = id
self.date = date
self.balance = balance
def getString(self):
return self.id + "," + self.date + "," + str(self.balance)
savings = []
def add_account(date, account, balance):
savings.append(Account(date, account, balance))
def print_accounts():
for account in savings:
print(account.getString())
while True:
date = input("Enter the date, type exit to exit program: ")
if date.lower() == "exit":
break
else:
account = input('Enter the account: ')
balance = int(input('Enter the balance: '))
add_account(date, account, balance)
print_accounts()
with open("test.txt", "w") as file:
for account in savings:
file.write(account.getString() + "\n")
Some explanation regarding the class: The Account class has 3 fields: id, date, and balance. These fields are defined in the constructor (__init__()). The class also has a method, getString(), which I use to get the string representation of each instance.
Over all, the following changes have been made:
Create an Account class, which serves as the template for the object which holds the data of each account.
Use a loop to print accounts and write them to the file.
Turn date into lowercase before checking to see if it is equal to "exit". This is a minor change but a good habit to have.
Removed f.close(), as it is unnecessary when using a with open() statement.
Created a custom string representation of each instance of Account, consistent with what you would otherwise get.
That last one is achieved via defining the getString method in the account class. There is nothing special about it, it is merely what we use to get the string representation.
A better but quite more advanced way to achieve that is by overriding the __str__ and __repr__ methods of the base object. These are essentially hidden functions that every class has, but which python defines for us. The purpose of these two specific ones is to give string representations of objects. The default code for them doesn't produce anything meaningful:
<__main__.Account object at 0x0000000003D79A58>
However, by overriding them, we can use str() on instances of Account, and we will get a string representation in the exact format we want. The modified class will look like so:
class Account:
def __init__(self, id, date, balance):
self.id = id
self.date = date
self.balance = balance
def __repr__(self):
return self.id + "," + self.date + "," + str(self.balance)
def __str__(self):
return self.__repr__()
This also eliminates the need to loop through savings when writing to the file:
with open("test.txt", "w") as file:
for account in savings:
file.write(account.getString() + "\n")
Turns into:
with open("test.txt", "w") as file:
file.write(str(savings))
This wouldn't have worked before, as str() would have given us the gibberish data you saw earlier. However, now that we have overridden the methods, it works just fine.
Try this code (use -1 to exit):
savings = []
def add_accounts(date, account, balance):
savings.append({'date': date, 'account': account, 'balance':
balance})
def print_accounts():
print(savings)
while True:
date = input('Enter the date, type exit to exit program: ')
if date == -1:
break
account = input('Enter the account: ')
balance = int(input('Enter the balance: '))
add_accounts(date, account, balance)
print_accounts()
with open('test.txt', 'a') as f:
for row in savings:
print (row)
f.write(str(savings[-1]))
f.close()

Having a constantly changing variable in a python loop

I'm trying to write a program that would ask for a students name, a couple other numerical values, and assign them to groups, via their numerical value, to have all groups as close to equal as possible (by taking the the highest next value in the list, and assigning it to the next group and so on).
However, I'd need to save their number to some variable, as well as their name, to then print out the group's list.
For this I'd need a variable that changes everytime the loop goes through to add another student. I'd also need to sort these number, and then somehow call back the name they corrispond to after they've been sorted into groups, and I'm not sure how to do any of these. Is there any way for this to be done, would I have to use another language?
This is the code I have so far:
from easygui import *
times = 0
name = 0
s_yn = ynbox("Would you like to enter a student?")
while s_yn == 1:
msg = "Student's Information"
title = "House Sorting Program"
fieldNames = ["Name", "Grade","Athleticism (1-10)","Intellect (1-10)","Adherance to school rules (1-10)"]
fieldValues = []
fieldValues = multenterbox(msg,title, fieldNames)
times = times + 1
ath = fieldValues[2]
int_ = fieldValues[3]
adh = fieldValues[4]
ath = int(ath)
int_ = int(int_)
adh = int(adh)
total = ath+int_+adh
s_yn = ynbox("Would you like to enter a student?")
I believe it would be nice to create a Student class that holds all variables associated with a student. Then you could add each student to a list which you could sort by the values you want and divide to how many groups you want.
from easygui import *
from operator import attrgetter
class Student(object):
def __init__(self, name, grade, athleticism, intellect, adherance):
self.name = name
self.grade = int(grade)
self.athleticism = int(athleticism)
self.intellect = int(intellect)
self.adherance = int(adherance)
self.total = self.athleticism + self.intellect + self.adherance
def __str__(self): # When converting an instance of this class to a string it'll return the string below.
return "Name: %s, Grade: %s, Athleticism (1-10): %s, Intellect (1-10): %s, Adherance to school rules (1-10): %s"\
% (self.name, self.grade, self.athleticism, self.intellect, self.adherance)
student_group = []
while ynbox("Would you like to enter a student?"): # Returns 'True' or 'False' so it'll loop every time the user press 'yes'.
message = "Student's Information"
title = "House Sorting Program"
field_names = ["Name", "Grade", "Athleticism (1-10)", "Intellect (1-10)", "Adherance to school rules (1-10)"]
field_values = multenterbox(message, title, field_names)
student = Student(*field_values) # Unpack all elements in the list 'field_values' to the initializer.
student_group.append(student) # Add the student to the group 'student_group'.
# When the user has put in all the students we sort our group by 'total' (or any other value you want to sort by).
sorted_group = sorted(student_group, key=attrgetter("total"), reverse=True)
# Just as an example I divided the students into 3 groups based on their total.
best_students = sorted_group[:len(sorted_group) // 3]
average_students = sorted_group[len(sorted_group) // 3:2 * len(sorted_group) // 3]
worst_students = sorted_group[2 * len(sorted_group) // 3::]

Categories