how to avoid staticmethod in my Python sample - python

I want to store sorted instances of Student class, into Students class. And I am using staticmethod in Students class for sorting. I could be wrong, but the design seems a bit ugly and want to avoid using static method (the most ideal way in my mind is using a class method in class Student which returns student ID, and use this method for sorting, but it seems not an easier way to set a class method of class Student in class Students for sorting), if there is a better design, appreciate if you could share,
class Student:
def __init__(self, id):
self.id = id
class Students:
def __init__(self, students):
self.sortedStudents = sorted(students, key=Students.getStudentID)
#staticmethod
def getStudentID(student):
return student.id
def dumpAllStudents(self):
for student in self.sortedStudents:
print student.id
students = []
students.append(Student(100))
students.append(Student(90))
students.append(Student(80))
allStudents = Students(students)
allStudents.dumpAllStudents()
thanks in advance,
Lin

Using operator.attrgetter:
import operator
...
def __init__(self, students):
self.sortedStudents = sorted(students, key=operator.attrgetter('id'))
operator.attrgetter('id') returns a callable that returns the attribute id from the operand. So it can be used instead of the static method or lambda.

You could use a lambda expression instead:
class Students:
def __init__(self, students):
self.sortedStudents = sorted(students, key=lambda s: s.id)
...
Whether you want to do this or have your Student class itself be comparable (see tzaman's answer) depends a bit on the rest of your code and your intent. If you need an order on students elsewhere, include the ordering in the Student class. If you only want them ordered inside your Students class, use my approach.
From an OOP design perspective, I wouldn't consider students to be ordered in general, but that's for you to decide (and may in the end be a matter of convenience).

If your objective is just to sort Student's instance based on its id, you don't need another function or method for that. You can achieve this using lambda expression.
class Student:
def __init__(self, id):
self.id = id
class Students:
def __init__(self, students):
self.sortedStudents = sorted(students, key=lambda stud: stud.id)
def dumpAllStudents(self):
for student in self.sortedStudents:
print student.id
students = []
students.append(Student(100))
students.append(Student(90))
students.append(Student(80))
allStudents = Students(students)
allStudents.dumpAllStudents()

Related

Class within a class (as attribute) to call a method of the class that contains it

I am in the situation where I need a class that is within another (as an attribute) to communicate with the class that contains it, however, I cannot wrap my head around how to do it.
Take this situation as an example: There is a class HighSchoolClass that contains a list of Students that belong to that class, and HighSchoolClass has a method foo rearranging how the students are sited or whatever. But then, each student has the capability of asking if a rearranging can be made. So it would need to call the method for rearranging the class.
# Singelton
class HighSchoolClass:
def __init__(self):
self.list_of_students = # A list of instances of Student
self.profesor = ...
def rearrange_class():
# do something
class Student:
def ask_for_rearrange():
# Needs to call rearrange_class() of the class he is in.
Is there any way of do this (ideally just calling it somehow)? Mind that rearrange_class() is not static. If it helps, the HighSchoolClass would be a singelton.
Typically, you would explicitly give each Student a reference to the HighSchoolClass that they belong to. A simple example might look like
class Student:
def __init__(self):
self.class_ = None
def add_class(self, class_):
self.class_ = class_
class HighSchoolClass:
def __init__(self):
self.list_of_students = []
def add_student(self, student):
self.list_of_students.append(student)
class Registrar:
def add_student_to_class(self, student, class_):
class_.add_student(student)
student.add_class(class_)
Note the use of a third class to both the student and the class. Otherwise, it's not obvious whether a student should be responsible for updating each class they join, or whether each class should update their students.
the_class = HighSchoolClass()
s1 = Student()
s2 = Student()
r = Registrar()
r.add_student_to_class(s1, the_class)
r.add_student_to_class(s2, the_class)
Now, each class and student has a reference to the other to be used as needed.

Printing from different class

I've searched for similar questions, and have not found anything.
Apparently because my question is pretty basic, yet I find it hard to understand.
I have a class named Student. In this class I get a name of student, and his grades then calculate his average grade. That one was easy, of course.
Here's what I've succeed so far:
class Student:
def __init__(self, name, grades_list):
self.name = name
self.grades_list = grades_list
def get_grade_avg(self):
avg = sum(self.grades_list) / len(self.grades_list)
return avg
def get_name(self):
return self.name
From the previous part I have:
john = Student("John", [100, 90 ,95])
Now I need to answer the following qeustion:
Write a class ClassRoom which has a constructor that receives a list of type Student, and saves this list to a member. The following should create a class room:
class_room = ClassRoom([John])
Implement class ClassRoom such that when calling print(class_room) on a ClassRoom type prints a list of tuples of student name and student grade average, in that order.
Calling print(class_room) should output:
[('John', 95)]
How do I do it?
I'm very new to OOP and have no idea even how to start.
Thank you!
You can do it like this:
class Student:
...
def __repr__(self):
return str((self.name, self.get_grade_avg()))
class ClassRoom:
def __init__(self, students):
self.students = students
def __str__(self):
return str(self.students)
john = Student("John", [100, 90 ,95])
mike = Student("Mike", [90, 80, 85])
c = ClassRoom([john, mike])
print(c)
# [('John', 95.0), ('Mike', 85.0)]
print(c)
When you call print on some object its __str__ method is invoked and if it is not defined __repr__ is called. By default __repr__ of some object is something like this: <__main__.Student object at 0x7f4b35a3a630>
In the above code a Student knows how to show itself, (name, avg), so you can print it if you like: print(john)
And for your ClassRoom, you just need to override __str__ to show the student list. Since every student knows how to show itself, it will do the job.
you need to add str() function to both class and student classes. str() if class should iterate over the list of students and print them on by one.

How to call a class in a function Python

I'm a complete amateur, and trying to work out how to write a function that takes a list of objects, and returns a list of the names of said objects (based on whether they pass if statement). This is the class I've written from help of tutorials:
class Student:
passmark=50
def __init__(self,name,mark):
self.name=name
self.mark=mark
def passes(self):
return self.mark > Student.passmark
So from now I'm assuming I make a list of objects, say:
students = []
Though this list is just a brand new list, which was necessary sure but how would I link it to the class? From this point I want to find out which students have failed, and return them and also where I am confused:
def failed(list):
for student in Students:
if passmark > self.mark:
return list
Is all I can muster, sorry I've just gotten to classes and calling classes is quite confusing for me. The above code doesn't reference the class at all, and I really am confused on how to do so. I've no syntax errors or anything, I think my logic is fatally flawed.
You want to take all the student from the students list. So use that in the for loop. Also, you correctly encapsulated the logic of pass/fail criteria in a method, so use that.
Here is the code I think will do want you want:
def failed(list_of_students):
failed_students = []
for student in list_of_students:
if not student.passes():
failed_students.append(student.name)
return failed_students
A more advanced way of doing it is by using list comprehension:
def failed(list_of_students):
return [student for student in list_of_students if not student.passes()]
It is more pythonic, but my be harder to understand for a beginner with a C or Java background.
You can use a list comprehension like this:
def failed(list):
return [student.name for student in students if not student.passes()]
Try this code. Using list comprehension to return results. It's a very powerful python tool.
class Student:
passmark = 50
def __init__(self, name, mark):
self.name=name
self.mark=mark
def passes(self):
return self.mark > Student.passmark
def __repr__(self):
return '{} {}'.format(self.name, self.mark)
def failed(students_list):
return [student for student in students_list if student.mark < Student.passmark]
Given a Student class like you defined:
class Student:
passmark=50
def __init__(self,name,mark):
self.name=name
self.mark=mark
def passes(self):
return self.mark > Student.passmark
You could instantiate a list of students with:
students = [Student("John", 49), Student("Mary", 75)]
It looks like you are also trying to define a function that will return a list of all the failed students; you could do something like this:
def failed(student_list):
return [x for x in student_list if not x.passes()]
mark_to_pass = 50
#Approach one
class Student:
def __init__(self, student_name, student_mark):
self.name = student_name
self.mark = student_mark
self.pass_mark = self.calculate_passing_mark(mark_to_pass)
def calculate_passing_mark(self, mark_to_pass):
if self.mark >= mark_to_pass:
return True
return False
if __name__ == '__main__':
example_student = Student("Swanson", 75)
print(example_student.pass_mark)
With this approach every time a student object is created it will tell create a field telling you that student has passed. When working with lists such as a list of student objects you need to add the student to your list. Example
students = []
students.append(example_student)
Now you can look through your student list by doing
for student in students:
print(student.pass_mark) # or do some other logic passed on who passed or failed. Or even here you dont need to create pass_mark object you can just check if student.mark > pass_mark
I'm assuming that failed isn't a member function of the class Student. The below code should work for what you are trying to do.
class Student:
passmark=50
def __init__(self,name,mark):
self.name=name
self.mark=mark
def passes(self):
return self.mark > Student.passmark
students = [Student("tom",40),Student("joe",70)]
def failed(listofStudents):
listofStudentsThatFail = []
for student in listofStudents:
if not student.passes():
listofStudentsThatFail.append(student)
return listofStudentsThatFail
for s in failed(students):
print s.name
The ouput when you run the code is:
tom

Python understanding OOP, inheritance

I solve this problem:
Develop an application which operates with next types:
Person (field Name, method ShowData())
Student (field Education)
Worker (field WorkPlace)
Classes Student and Worker are derived from class Person.
Class Academy in it's container collects Students and Workers and shows Name, Education or WorkPlace for all persons in method ShowAll().
We can add new persons to Academy by calling method AddPerson().
Which hierarchy of classes is the best
for solving this problem?
Code should include inheritance and use collections.
This is my solution, but i don't know how to realize method AddPerson:
class Academy(object):
theWholeList = []
#staticmethod
def showAll():
for obj in Academy.theWholeList:
if isinstance(obj,Student):
print obj.name+' - '+obj.edu
elif isinstance(obj,Worker):
print obj.name+' - '+obj.wplace
class Person(Academy):
def __init__(self,name):
self.name = name
super(Person, self).theWholeList.append(self)
def showData(self):
return vars(self)
class Student(Person):
def __init__(self, name, edu):
super(Student, self).__init__(name)
self.edu = edu
class Worker(Person):
def __init__(self, name, wplace):
super(Worker, self).__init__(name)
self.wplace = wplace
Maybe Academy must inherit Person and method AddPerson will be like that:
def add(self,name):
super(Academy,self).__init__(name)
first thing:
class Academy(object):
theWholeList = []
#staticmethod
def showAll():
for obj in Academy.theWholeList:
if isinstance(obj,Student):
print obj.name+' - '+obj.edu
elif isinstance(obj,Worker):
print obj.name+' - '+obj.wplace
you do not need to have Academy's method showAll() be a static method, as on your design the Academy is legitimate to be a singleton, i.e. a class having a single instance.
Also theWholeList is a very bad name for a list. Because you know it is a list, as you're assigning it a list. The name shall describe its semantic, i.e. the kind of things it contains, what it is used for.
You should rewrite it as follows:
class Academy:
def __init__(self):
self.person_list = []
def show_all(self):
for item in self.person_list:
item.show_data()
And you would instanciate it once:
academy = Academy()
Then the following:
class Person(Academy):
def __init__(self,name):
self.name = name
super(Person, self).theWholeList.append(self)
is bad design: in object oriented programming you should think about encapsulating data. Here you're making the assumption that Person knows the internals of Academy. And what if you decide to change Academy's implementation so theWholeList is renamed? Or switched into a dict()? This should be transparent to the "user" of the class Academy. A better design should be:
class Academy:
... # cf earlier
def add_person(self, person):
self.person_list.append(person)
class Person(Academy):
def __init__(self,name):
self.name = name
def show_data(self):
print("My name is: {}".format(name))
So you can use it as follows:
person_a = Person("John Doe")
person_b = Person("Jim Smith")
academy.add_person(person_a)
academy.add_person(person_b)
And finally you're wondering:
Maybe Academy must inherit Person
Most of the time, subclassing is the wrong answer of a wrong question. You need to subclass when you want to extend or specialize behaviour of a class. A classical example would be:
class Animal:
def noise(self):
raise NotImplementedError # virtual method
class Duck(Animal):
def noise(self):
print("quack")
class Cat(Animal):
def noise(self):
print("meaw")
So in your case, you have a class person that implements show_data, and what you want is to extend the behaviour, for worker and student:
class Worker(Person): # a worker _is_ a person!
def __init__(self, name, unit):
# left as an exercise to the OP
def show_data(self):
# left as an exercise to the OP
class Student(Person):
def __init__(self, name, promo):
# left as an exercise to the OP
def show_data(self):
# left as an exercise to the OP
I won't get into much more details here, as I suppose you have a teacher you can ask more about the comments I made. But at least you tried, made some mistakes (AND MISTAKES ARE GOOD!). But I'm not giving you a full answer, my only goal here is to set you up in the right mind set to make your code a better design!
I hope this helps!
You want to be able to add people:
>>> academy = Academy()
>>> academy.add(Person('Pete'))
>>> academy.showAll()
Name: Pete
>>> academy.add(Student('Taras', 'Higher'))
>>> academy.showAll()
Name: Pete
Name: Taras, Education: Higher
>>> academy.add(Worker('riotburn', 'StackOverflow')
>>> academy.showAll()
Name: Pete
Name: Taras, Education: Higher
Name: riotburn, Workplace: StackOverflow
showAll needs to iterate over all people calling ShowData on them. This will be implemented differently for each type.
class Academy(object):
def __init__(self):
self.people = []
def add(self, person):
self.people.append(person)
def showAll(self):
for person in self.people:
person.ShowData()
Where for example, Worker will implement ShowData as:
def ShowData(self):
print 'Name: ' + self.name + ', Education:' + self.edu

Using certain attributes of an object to make other objects in a different class

For a program that creates a timetable for a doctor(specialist) I want to use certain attributes of an object created by a different class to be used in the class that makes the timetable for the doctor.
class makePatient(object):
def __init__(self,name,room):
self.name = name
self.room = room
def getPatient(self):
print(self.name)
print(self.room)
class makeSpecialist(object):
def __init__(self,name,specialization,timetable):
self.name = name
self.specialization = specialization
self.timetable = timetable
def getSpecialist(self):
print(self.name)
print(self.specialization)
print(self.timetable)
class makeAgenda(object):
def addAgenda(self):
self.timetable.append()
#I want to append the name of the patient I have defined here.
print(self.timetable)
patient1 = makePatient("Michael","101")
specialist1 = makeSpecialist("Dr. John","Hematology",[])
What do I do now, to make sure that the name "Michael" gets added to the list [] of specialist Dr. John?
Thanks in advance, I will provide further details if necessary!
I think another approach would be better; you can put the whole makePatient object into the timetable for the specialist:
specialist1 = makeSpecialist("Dr. John", "Hematology", [patient1])
Now you can access the names and other attributes of the patients in a specialist's timetable:
for patient in specialist1.timetable:
print(patient.name)
You can also define a __repr__ method to tell Python how to display an object, rather than the current getPatient:
class makePatient(object):
# ...
def __repr__(self):
return "{0} (room {1})".format(self.name, self.room)
Now when you print the whole timetable:
>>> print(specialist1.timetable)
You get the necessary information:
[Michael (room 101)]
Note also that the classes should probably be called, simply, Patient, Specialist and Agenda; the make is implied.
Finally, you will get errors in makeAgenda.addAgenda as, without an __init__, self.timetable doesn't exist for a makeAgenda object, and an empty append() doesn't do anything anyway.
Classes are often used to represent entities and operations allowed on them, include constructing, or making, new instances of them. Therefore, your classes would be better named simplyPatient, Specialist, andAgenda. The name of the method that constructs a new instance of any class in Python is always__init__().
That said, after creating aPatientand aSpecialistyou could then add patient instances to the specialist's timetable/agenda by passing it to aSpecialistmethod specifically designed for that purpose. In other words, a Specialist "has-a" Agenda instance namedtimetableand to which patients can be added via an appropriately namedadd_to_timetable()method.
Here's what I mean -- note I've modified your code to follow PEP 8 -- Style Guide for Python Code guidelines which I also suggest that you follow:
class Agenda(object):
def __init__(self):
self.events = []
def append(self, event):
self.events.append(event)
class Patient(object):
def __init__(self, name, room):
self.name = name
self.room = room
def get_patient(self):
print(self.name)
print(self.room)
class Specialist(object):
def __init__(self, name, specialization):
self.name = name
self.specialization = specialization
self.timetable = Agenda()
def add_to_timetable(self, patient):
self.timetable.append(patient)
def get_specialist(self):
print(self.name)
print(self.specialization)
print(self.timetable)
specialist1 = Specialist("Dr. John", "Hematology")
patient1 = Patient("Michael", "101")
specialist1.add_to_timetable(patient1)
I'm not too sure what you're trying to accomplish here with method that just print values or with the makeAgenda class, but here's how you can get Michael in Dr. John's list:
specialist1.timetable.append(patient1.name)

Categories