Is it possible to create nested class attributes? - python

I'm pretty new to Python and have recently learned about classes. I've been experimenting around with them and have come up with a student/course grading system. Here's the code so far:
class course:
TOTAL_COURSES = 0
def __init__(self, name, room, max_students):
self.name = name
self.room = room
self.max_students = max_students
self.student_list = []
course.TOTAL_COURSES += 1
def addStudent(self, student):
# Checks if the class is below max capacity
if len(self.student_list) < self.max_students:
self.student_list.append(student)
# Adds the course to the student's course list
student.course_list.append(self)
return True
return False
So this creates a course class, which I can add students to and set their rooms and other stuff. I've got another class, which is intended to store information on students:
class student:
TOTAL_STUDENTS = 0
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# Courses in course_list are stored as objects
self.course_list = []
student.TOTAL_STUDENTS += 1
Pretty simple; I've just been stuck on how to create the actual grading system. If I were to do:
s1 = student("Tom", 17, "Male")
c1 = course("English", "E123", 25)
Would it be possible to then use "nested attributes"? So I'd assign that student's grade of a course to a value like:
s1.c1.grade = "A+"
This however doesn't work, and throws an (expected) AttributeError. So would I have to use my previously created course_list?
s1.course_list[0].grade = "A+"
Even then I'm not sure how I'd assign grade to that course object.

Here's a solution that addresses some of the above issues by assigning a "course slot" to a student, rather than the course itself. As you might imagine, there is a limit to the number of course slots available which depends on the course max size. The code can be developed a lot further, but I thought this could be good to get you started:
class Student:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
self.courses = {}
def addCourse(self, course):
if course.status=='Enrolled':
self.courses[course.name] = course
else:
self.courses[course.name] = course.status
class Course:
def __init__(self, name, room, max_students):
self.name = name
self.room = room
self.max_students = max_students
self.student_list = []
self.course_slots_filled = 0
self.course_slots_available = max_students
def __str__(self):
return 'Course_object_{}'.format(self.name)
def check_slots_available(self):
if self.course_slots_filled < self.max_students:
return True
else:
return False
class CourseSlot:
def __init__(self, name, student_name, status):
self.name = name
self.student_name = student_name
self.status = status
self.grade = 'No grade assigned'
def __repr__(self):
return 'CourseSlot_object_{}'.format(self.name)
def set_grade(self, grade):
self.grade = grade
def assign_course_slot(self, student_name):
if self.check_slots_available():
self.course_slots_filled+=1
self.course_slots_available-=1
status = 'Enrolled'
self.student_list.append(student_name)
else:
print('Sorry, {} class is full! {} not enrolled.'.format(self.name, student_name))
status = 'Not enrolled'
return self.CourseSlot(self.name, student_name, status)
Example usage
Instantiate courses:
physics = Course('Physics','101',5)
chemistry = Course('Chemistry','102',1)
Instantiate student 1 and assign a course slot:
s1 = Student("Tom", 17, "Male")
s1.addCourse(physics.assign_course_slot(s1.name))
s1.addCourse(chemistry.assign_course_slot(s1.name))
s1.courses['Physics'].set_grade('A+')
[v for v in s1.courses.values()]
# >>> [CourseSlot_object_Physics, CourseSlot_object_Chemistry]
Instantiate student 2 and assign a course slot
s2 = Student("Susan", 18, "Female")
s2.addCourse(physics.assign_course_slot(s2.name))
s2.addCourse(chemistry.assign_course_slot(s2.name))
#>>> Sorry, Chemistry class is full! Susan not enrolled.
Get course info:
print('Physics course slots filled: ',physics.course_slots_filled)
print('Physics course slots available: ',physics.course_slots_available)
print('Chemistry course slots filled: ',chemistry.course_slots_filled)
print('Chemistry course slots available: ',chemistry.course_slots_available)
#>>> Physics course slots filled: 2
# Physics course slots available: 3
# Chemistry course slots filled: 1
# Chemistry course slots available: 0
print('Physics student list: ',physics.student_list)
print('Chemistry student list: ',chemistry.student_list)
# >>> Physics student list: ['Tom', 'Susan']
# Chemistry student list: ['Tom']
for s in [s1,s2]:
for c in s.courses.values():
try:
print('{} {} grade: {}'.format(s.name, c.name, c.grade))
except AttributeError:
pass
# >>> Tom Physics grade: A+
# Tom Chemistry grade: No grade assigned
# Susan Physics grade: No grade assigned
I guess the cheat is that the course student_list only gets the name of the student and not the Student object, which could probably work if you pass it a unique ID and then iterate through a list of Student objects to match on ID. Something to think about anyway.

Grade is assigned to a combination of a student and a course object, so it cannot be a single attribute of any of them.
I would consider grades closer related to students than courses, so I would add a dictionary to student with a unique course ID (for example its name) as key and the grade as value. Next, you will probably need a function to select the course with the given ID (name) from your list of courses.
To further improve your code you can make course a hashable class, which is a class with a __hash__ method (look this up in the Python docs). Then you can use course objects directly as dictionary keys instead of working with IDs.

Related

How can I print each item in an unknown length list that is a class attribute as a string in python?

I have a class (Student) with different attributes, such as studentId, address, and courses. My str method for the class returns all the information that the user put in. However, for the attributes that are lists, such as courses, the location of the information is printed out instead of the actual information. Here is the code (sorry it's a little long, there's a bunch of classes):
class Person:
__name = None
__age = None
__address = None
def __init__(self, name, age=0, address=None):
self.set_name(name)
self.set_age(age)
self.set_address(address)
def __str__(self):
return 'Name: ' + self.__name + '\n' + \
'Age: ' + str(self.__age) + '\n' + \
'Address: ' + str(self.__address)
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_age(self, age):
self.__age = age
def get_age(self):
return self.__age
def set_address(self, address):
self.__address = address
def get_address(self):
return self.__address
class Student(Person):
def __init__(self, name, studentID= None, age= 0, address= None):
super(Student, self).__init__(name, age, address)
self.set_studentID(studentID)
self.__courses =[]
def __str__(self):
result = Person.__str__(self)
result += '\nStudent ID:' + self.get_studentID()
for item in self.__courses:
result += '\n ' + str(item)
return result
def set_studentID(self, studentID):
if isinstance(studentID, str) and len(studentID.strip()) > 0:
self.__studentID = studentID.strip()
else:
self.__studentID = 'NA'
def get_studentID(self):
return self.__studentID
def add_course(self, course):
print('in add_course')
self.__courses.append(course)
def get_courses(self):
for i in range(len(self.__courses)):
return self.__courses[i]
class Course:
__courseName = None
__dept = None
__credits = None
def __init__(self, courseName, dept= 'GE', credits= None):
self.set_courseName(courseName)
self.set_dept(dept)
self.set_credits(credits)
def __str__(self):
return self.get_courseName() + '/' + self.get_dept() + '/' + str(self.get_credits())
def set_courseName(self, courseName):
if isinstance(courseName, str) and len(courseName.strip()) > 0:
self.__courseName = courseName.strip()
else:
print('ERROR: Name must be a non-empty string')
raise TypeError('Name must be a non-empty string')
def get_courseName(self):
return self.__courseName
def set_dept(self, dept):
if isinstance(dept, str) and len(dept.strip()) > 0:
self.__dept = dept.strip()
else:
self.__dept = "GE"
def get_dept(self):
return self.__dept
def set_credits(self, credits):
if isinstance(credits, int) and credits > 0:
self.__credits = credits
else:
self.__credits = 3
def get_credits(self):
return self.__credits
students = []
def recordStudentEntry():
name = input('What is your name? ')
age = input('How old are you? ')
studentID= input('What is your student ID? ')
address = input('What is your address? ')
s1 = Student(name, studentID, int(age), address)
students.append(s1)
s1.add_course(recordCourseEntry())
print('\ndisplaying students...')
displayStudents()
print()
def recordCourseEntry():
courses = []
for i in range(2):
courseName = input('What is the name of one course you are taking? ')
dept = input('What department is your course in? ')
credits = input('How many credits is this course? ')
c1 = Course(courseName, dept, credits)
print(c1)
courses.append(c1)
displayCourses(courses)
return courses
def displayCourses(courses):
print('\ndisplaying courses of student... ')
for c in range(len(courses)):
print(courses[c])
def displayStudents():
for s in range(len(students)):
print()
print(students[s])
recordStudentEntry()
This is how the code above prints out the 'displaying students...' part:
displaying students...
Name: sam
Age: 33
Address: 123 st
Student ID:123abc
[<__main__.Course object at 0x000002BE36E0F7F0>, <__main__.Course object at
0x000002BE36E0F040>]
I know that it is printing out the location because I need to index into the list. However, the length of the list will be different every time. Normally if I wanted to index into a list, for example, to print a list of names, I would do:
listOfNames = ['sam', 'john', 'sara']
for i in range(len(listOfNames)):
print(listOfNames[i])
or
listOfNames = ['sam', 'john', 'sara']
for i in listOfNames:
print(i)
(not sure what if any difference there is between the 2 ways since they both print out the same way:)
sam
john
sara
How can I write something like the indexing into a list technique shown here in my str method for my class so that it prints the information and not the location?
It would be good to keep to the standard conventions for Python, such as naming
private attributes for objects with single underscores, not double underscores.
The latter are reserved for Python "internal" attributes and methods.
Also, it is convention to use object attributes for objects with get/set methods,
not class attributes. This will make it easier to inspect your objects, while
still maintaining data hiding. Example:
class Course:
def __init__(self, courseName, dept= 'GE', credits= None):
self._courseName = None
self._dept = None
self._credits = None
self.set_courseName(courseName)
...
Your question about why the courses don't print out the way you expected
is rooted in a programming error with the way you programmed the recording
of courses. In recordCourseEntry(), you record two courses and put them
in a list. However, you pass that to your Student object using a method
intended for one course at a time. My suggested fix would be:
...
# s1.add_course(recordCourseEntry())
courses = recordCourseEntry()
for course in courses:
s1.add_course(course)
...
This will probably be enough to get you going. An example output I got was:
Name: Virtual Scooter
Age: 33
Address: 101 University St.
Student ID:2021
ff/GE/3
gg/GE/3

how to take the average of grades of a student class in student.py

I have a python class called student that includes variables and methods.
Each student has a name, age, and a list of grades.
I have a method AVGofGrades() that takes the list of grades and returns the average.
I do not know how to create a function that will take this list of grades from the student object and loop through it to return the avgerage.
So far I have this:
class student():
def __init__(self,name,age, *grades):
self.stdName=name
self.stdAge = age
self.stdGrade = grades
def getName(self):
return self.stdName
def setName(self,Name):
self.stdName = Name
def getAge(self):
return self.stdAge
def setAge(self, Age):
self.stdAge = Age
def getGrade(self):
gradeLevel = None
if AVGofGrades(self.stdGrade) >=20:
gradeLevel = "A"
elif self.stdGrade >=18:
gradeLevel = "B"
elif self.stdGrade >=15:
gradeLevel = "C"
else:
gradeLevel = "F"
print("the student {0}, have a grade {1}".format(self.stdName, gradeLevel))
def setGrade(self,*Grades):
self.stdGrade = Grades
def AVGofGrades(self, *student):
result = 0
for i in student:
student[2]
std1 = student("georges", 17, 80,23,50,34,80,78)
You have grades already in self.stdGrade
So, you don't need to pass anything in the method AVGofGrades, as you are providing all the arguments needed when you create the Student Object.
Your AVGofGrades can be as below:
def AVGofGrades(self):
return sum(self.stdGrade)/len(self.stdGrade)
You can now create an instance of the class and get the AVGofGrades as below:
std1 = Student("georges", 17, 80,23,50,34,80,78)
print(std1.AVGofGrades())
It's a simple math expression:
def AVGofGrades(self, *student):
return sum(student) / len(student)
You should call it as self.AVGofGrades() though, not just AVGofGrades().

How to determine the first and last item in a list of objects in Python?

I need to create a class Person (in Python) that implements the comparison operator such as < to compare their names.
I need to ask the user for input of 10 names and generate 10 Person objects. I need to determine the first and last person among them and print them. I got the class, operator and ask for user input. All is working but I am not sure how to determine the first and last person? Any help would be appreciated, below is my code:
class Person():
def __init__(self, name):
self.name = name
def __str__(self):
return '{}'.format(self.name)
def __eq__(self, other):
return self.name == other.name
def __lt__(self, other):
return self.name < other.name
#create empty list to hold person object
persons =[]
for p in range(0,10):
if p==0:
name = input("please enter name: ")
else:
name = input("please enter another name: ")
#take user input and create 10 person object
persons.append(Person(name))
for person in persons:
print(person)
If you implement the comparison operators, you can use max and min to find the greatest and smallest Person objects.
persons = [Person('Bob'), Person('Charlie'), Person('Dee'), Person('Alice')]
print(min(persons).name) # Alice
print(max(persons).name) # Dee

How to use for loop in class for user inputs?

I have created employee details using class by giving pre defined inputs. I'm not able to store the results into a dict. I need to write it as a csv. I would be thankful if you could help me, as I'm a novice in python
Here are my codes:
Is it correct way to use for loop in classes?
class Employee():
def main(self,name,idno,position,salary):
self.name=name
self.idno=idno
self.position=position
self.salary = salary
def input(self):
n=int(raw_input("Enter the number of employees:"))
for i in range(n):
self.name=raw_input("Name:")
self.idno=raw_input("Idno:")
self.position=raw_input("position:")
self.salary=raw_input("salary:")
print("Name:", self.name, "Idno:", self.idno, "position:", self.position,
"salary:", self.salary)
if __name__=='__main__':
result=Employee()
result.input()
First of all, I don't think you're class will be working like you intend it to. Since you're constantly overwriting the class variables, there is no point in entering more than one employee, as the class can currently only save information on one employee. I would consider saveing employees as dictionarys and have those dictionaries saved as a list in your Employee(s) class.
class Employees():
all_employees = [{...}, {...}]
dict_keys = ["name", "idno", "position",...]
def input(self):
counter = 0
n = input("Number of employees: ")
while counter < n:
new_employee = dict()
for key in dict_keys:
new_employee[key] = raw_input("{}: ".format(key))
all_employees.append(new_employee)
if __name__ == "__main__":
e = Employees()
e.input()
This illustrates what I was saying in my comment about using a for loop outside of the class:
class Employee(object):
def __init__(self, name, idno, position, salary):
self.name=name
self.idno=idno
self.position=position
self.salary = salary
def print_data(self):
print("Name:", self.name, "Idno:", self.idno, "position:", self.position,
"salary:", self.salary)
if __name__=='__main__':
def input_employee_data():
print('Enter data for an employee')
name = raw_input("Name:")
idno = raw_input("Idno:")
position = raw_input("position:")
salary = raw_input("salary:")
print('')
return name, idno, position, salary
employees = list()
n = int(raw_input("Enter the number of employees:"))
for i in range(n):
name, idno, position, salary = input_employee_data()
employee = Employee(name, idno, position, salary)
employees.append(employee)
print('List of employess')
for employee in employees:
employee.print_data()

Python class initialization - attributes memory

So I was writing a program in Python, which would take all my university classes (from csv) and print info about them. I've wrote a simple class Subject to manage everything better. In my uni there are classes in even weeks, odd weeks, and every-week classes, and I have lectures, exercises and laboratories. So my Subject class is like this:
class Subject:
number = 0
name = ""
dummyData = []
even = {}
odd = {}
all = {}
type = ""
def __init__(self, name, number, type):
self.name = name
self.number = number
self.type = type
self.info = str(number) + " " + name + " " + type
Previously I had all days written in even, odd, and all dicts, like this:
even = {"mon":"",
"tue":"",
"wed":"",
"thu":"",
"fri":"",
}
So I could add all the classes hours to specific day key. But, there was a problem. For example lets say Programming lecture is subject 1 and Programming laboratories are subject 2. Subject 1 is on Monday at 9.15. Subject 2 is on Monday as well, but at 17.05. So I have a function, which would check if the subject is on even/odd week or it is every week. And then I would assign f.e 9.15 to even["mon"] on subject 1. Then I would go for subject 2, and tried to add 17.05 to even["mon"]. Every subject was an other Subject class object stored in a list. But there was a mistake. When I tried to add 17.05 to subject 2s even["mon"] it added it, okay, but then even["mon"] should ="17.05", but it was ="9.15/17.05". I was trying to figure out whats wrong, and I finally did, by changing my class from:
class Subject:
number = 0
name = ""
dummyData = []
even = {"mon":"",
"tue":"",
"wed":"",
"thu":"",
"fri":"",
}
...etc...
type = ""
def __init__(self, name, number, type):
self.name = name
self.number = number
self.type = type
self.info = str(number) + " " + name + " " + type
to:
class Subject:
number = 0
name = ""
dummyData = []
even = {}
odd = {}
all = {}
type = ""
def __init__(self, name, number, type):
self.name = name
self.number = number
self.type = type
self.info = str(number) + " " + name + " " + type
self.even = {"mon":"",
"tue":"",
"wed":"",
"thu":"",
"fri":"",
}
+ odd and all. So why is Python like remembering whats been written into the first object attributes?
You need to declare the attributes inside the __init__ method. Here's an example
class Subject:
def __init__(self, name, number, type):
self.number = number
self.name = name
self.dummyData = []
self.even = {}
self.odd = {}
self.all = {}
self.type = type
Declaring the variables inside the class declaration makes them "class" members and not instance members. Declaring them in the __init__ method makes ensures a new instance of the members is created every time you create a new instance of the object.

Categories

Resources