Python OOP Exercise - python

I'm trying to learn using classes in Python, and have written this test program.
It is to some extent based on code I found in another question I found here on
Stack OverFlow.
The code looks as follows:
class Student(object):
name = ""
age = 0
major = ""
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def list_values():
print "Name: ", self.name
print "Age: ", self.age
print "Major: ", self.major
def make_student(name, age, major):
student = Student(name, age, major)
return student
print "A list of students."
Steve = make_student("Steven Schultz",23,"English")
Johnny = make_student("Jonathan Rosenberg",24,"Biology")
Penny = make_student("Penelope Meramveliotakis",21,"Physics")
Steve.list_values()
Johnny.list_values()
Penny.list_values()
When I run this, get the error "TypeError: list_values() takes no arguments (1 given)".
In my oppinion I have not given any arguments, but I remove the parenthesis, giving the
code
Steve.list_values
Johnny.list_values
Penny.list_values
This renders no errors, but does not do anything - nothing gets printed.
My questions:
What's the deal with the parenthesis?
What's the deal with the print statements?

The list_values method needs to be bound to your Student instance:
You should change:
def list_values()
to:
def list_values(self)
For an explanation of why, see:
Why do you need explicitly have the "self" argument into a Python method?
What is the purpose of self?
Also this blog post by Guido covers the subject too:
http://neopythonic.blogspot.com.au/2008/10/why-explicit-self-has-to-stay.html

Python requires you explicitly add the self arguments to member functions, you forgot to add self to your function decleration:
def list_values(self):
This defines it as a member function of the Student class. see here. When you call the functions as members of the Student instances, the self variable is added implicitly, thus triggering the exception, since you did not define a function named list_values that receives one parameter.
As for removing the parenthesis, this means you are not calling the functions but only referring to the function objects, doing nothing.

def list_values(): should be: def list_values(self):
In Python, instance methods take self as the first argument.

As others have mentioned, you should use self in list_values.
But you should really define one of the magic method __str__ or __repr__ instead. Additionally, you're setting some class properties that aren't necessary.
A simpler implementation would be:
class Student(object):
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def __str__(self):
fs = "<name: {}, age: {}, major: {}>"
return fs.format(self.name, self.age, self.major)
Use it like this:
In [9]: Steve = Student("Steven Schultz", 23, "English")
In [10]: Johnny = Student("Jonathan Rosenberg", 24, "Biology")
In [11]: Penny = Student("Penelope Meramveliotakis", 21, "Physics")
In [12]: print Steve
<name: Steven Schultz, age: 23, major: English>
In [13]: print Johnny
<name: Jonathan Rosenberg, age: 24, major: Biology>
In [14]: print Penny
<name: Penelope Meramveliotakis, age: 21, major: Physics>
Since you're not defining special methods, you might as well use a named tuple:
In [16]: from collections import namedtuple
In [17]: Student = namedtuple('Student', ['name', 'age', 'major'])
In [18]: Steve = Student("Steven Schultz", 23, "English")
In [19]: print Steve
Student(name='Steven Schultz', age=23, major='English')

Related

Outputting Specific Prosperities of an Array(List) of Objects in Python

I have an class Student which has an array(list) of Objects called Students. I am trying to output the names of all the students in the array.
class Student(object):
name = ""
age = 0
major = ""
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
Students = []
Students.append(Student("Dave",23,"Chem"))
Students.append(Student("Emma",34,"Maths"))
Students.append(Student("Alex",19,"Art"))
print(Students[0].__dict__)
print(Students[1].__dict__)
print (Students[0])
Both the ways I have found and tired do not output the specific name but the location or the whole object. Is there a way to just output the name? For example output Students[0] name Dave
{'name': 'Emma', 'age': 34, 'major': 'Maths'}
<__main__.Student object at 0x000001FCDE4C2FD0>```
If you just want the name, you can print(Students[0].name). If you want to see the relevant attributes when printing a Student object, you can implement the __repr__ method.
class Student:
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def __repr__(self):
return f"<Student name={self.name} age={self.age} major={self.major}>"
This way you can simply do print(Students[0]) to see the name, age and major of a student.
By the way, for a normal class definition, you want to initialize instance attributes inside the __init__ method, instead of declaring them above __init__: those are class attributes. Please read this section of the documentation to familiarize yourself with the syntax.

Make a class iterable over its instances using a decorator

I came across code which makes the class Person allow iteration here and here. It worked great for a single class, but I tried to modify it to allow iteration of multiple classes (separately) by changing _ClassRegistry to a dict, which would store
{class_str_name_1: [instance_obj_1, instance_obj_2, ...], ...}
instead of a list. My code is:
class ClassIter(type):
def __iter__(cls):
# iterate over the list in the dict value
return iter(cls._ClassRegistry[cls.__name__])
def __len__(cls):
return len(cls._ClassRegistry[cls.__name__])
class Person(metaclass=ClassIter):
_ClassRegistry = {}
def __init__(self, name, age):
if type(name).__name__ in self._ClassRegistry.keys():
self._ClassRegistry[type(self).__name__].append(self)
else:
self._ClassRegistry[type(self).__name__] = [self]
self.name, self.age = name, age
class Dog(metaclass=ClassIter):
_ClassRegistry = {}
def __init__(self, name, age, owned_by):
if type(name).__name__ in self._ClassRegistry.keys():
self._ClassRegistry[type(self).__name__].append(self)
else:
self._ClassRegistry[type(self).__name__] = [self]
self.name, self.age = name, age
self.owned_by = type(owned_by).__name__
Alice = Person("Alice", 20)
Bob = Person("Bob", 25)
Spud = Dog("Spud", 6, Alice)
Buster = Dog("Buster", 12, Bob)
for p in Person:
print(p.name, p.age)
for d in Dog:
print(d.name, d.age, d.owned_by)
Unfortunately this did not work, and the output is
Bob 25
Buster 12 Person
while I was hoping it would produce
Alice 20
Bob 25
Spud 6 Alice
Buster 12 Bob
so not only is it using the class name Person instead of the name attribute (which is probably an easy fix but I've lost myself in the code) but also not storing the previous instances.
How can I either:
(1) convert this program to a class decorator (such as #iterator; class Person:) to make it easier to implement this for multiple classes? or
(2) fix the program to work as expected using the current implementation?
Any help much appreciated, I am unexperienced in OOP.

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.

Iterate through instantiated objects of a class

Isit possible to loop through the objects of class person and extract the attributes?
so for e.g. from below code after looping the output would be for each object as such
Object mike, name-Mike, age-20
class Person(object):
def__init__(self,name,age):
self.name = name
self.age = age
mike = Person('Mike',20)
john = Person('John',20)
jack = Person('Jack',20)
adam = Person('Adam',20)
Thanks
Python classes don't automatically track all their instances. You can use a class attribute to track them yourself if you need to:
class Person(object):
people = []
def __init__(self, name, age):
self.name = name
self.age = age
self.people.append(self)
Even better would be to have a separate object that tracked people. The Person.people attribute here is a single global value for the process, which can limit the usability and testability of the class.
To loop the attributes of a given instance, I think you're looking for the builtin function vars:
>>> mike = Person('Mike', 20)
>>> vars(mike)
{'age': 20, 'name': 'Mike'}
To loop through all instances of a given class is not possible, unless you add some code to maintain the list of instances yourself.
if my understanding is right, you are asking about looping the class person
then it can be done like this.
class person(object):
def __init__(self,name,age):
self.name=name
self.age=age
for name,age in {'mike':20,'john':20,'jack':20}.iteritems():
p = person(name,age)
print ('I am %s my age is %d' %(p.name,p.age))
## answer
I am mike my age is 20
I am john my age is 20
I am jack my age is 20

Python, creating objects

I'm trying to learn python and I now I am trying to get the hang of classes and how to manipulate them with instances.
I can't seem to understand this practice problem:
Create and return a student object whose name, age, and major are
the same as those given as input
def make_student(name, age, major)
I just don't get what it means by object, do they mean I should create an array inside the function that holds these values? or create a class and let this function be inside it, and assign instances? (before this question i was asked to set up a student class with name, age, and major inside)
class Student:
name = "Unknown name"
age = 0
major = "Unknown major"
class Student(object):
name = ""
age = 0
major = ""
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def make_student(name, age, major):
student = Student(name, age, major)
return student
Note that even though one of the principles in Python's philosophy is "there should be one—and preferably only one—obvious way to do it", there are still multiple ways to do this. You can also use the two following snippets of code to take advantage of Python's dynamic capabilities:
class Student(object):
name = ""
age = 0
major = ""
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
# Note: I didn't need to create a variable in the class definition before doing this.
student.gpa = float(4.0)
return student
I prefer the former, but there are instances where the latter can be useful – one being when working with document databases like MongoDB.
Create a class and give it an __init__ method:
class Student:
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def is_old(self):
return self.age > 100
Now, you can initialize an instance of the Student class:
>>> s = Student('John', 88, None)
>>> s.name
'John'
>>> s.age
88
Although I'm not sure why you need a make_student student function if it does the same thing as Student.__init__.
Objects are instances of classes. Classes are just the blueprints for objects. So given your class definition -
# Note the added (object) - this is the preferred way of creating new classes
class Student(object):
name = "Unknown name"
age = 0
major = "Unknown major"
You can create a make_student function by explicitly assigning the attributes to a new instance of Student -
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
return student
But it probably makes more sense to do this in a constructor (__init__) -
class Student(object):
def __init__(self, name="Unknown name", age=0, major="Unknown major"):
self.name = name
self.age = age
self.major = major
The constructor is called when you use Student(). It will take the arguments defined in the __init__ method. The constructor signature would now essentially be Student(name, age, major).
If you use that, then a make_student function is trivial (and superfluous) -
def make_student(name, age, major):
return Student(name, age, major)
For fun, here is an example of how to create a make_student function without defining a class. Please do not try this at home.
def make_student(name, age, major):
return type('Student', (object,),
{'name': name, 'age': age, 'major': major})()
when you create an object using predefine class, at first you want to create a variable for storing that object. Then you can create object and store variable that you created.
class Student:
def __init__(self):
# creating an object....
student1=Student()
Actually this init method is the constructor of class.you can initialize that method using some attributes.. In that point , when you creating an object , you will have to pass some values for particular attributes..
class Student:
def __init__(self,name,age):
self.name=value
self.age=value
# creating an object.......
student2=Student("smith",25)

Categories