See Classes and Objects from Socratica.
This notebook includes code from oop.py.
Python uses namespaces to achieve encapsulation. Other OOP languages have private and public data and methods. Python, not so much.
Many shapes. The same method can mean different things depending on the object on which it is invoked. Consider the install method. It means different things for a refrigerator, stove, television, alarm system, IKEA cabinet, or military dictatorship.
What is depicted above?
OOP uses ISA hierarchy principle from cognitive psychology.
You may infer that a dog has fur and nurses its young.
You may infer that it has four legs and a tail. However, you may override defaults, e.g., a Mexican hairless dog or an amputee pooch.
class course:
''' A class for describing courses, e.g., math or computer science.'''
# class variables
count = 0
classes = [] ## gratuitous confusion.
def __init__(self, title, dept):
'''
This is an example of function overloading.
The constructor which sets the initial values of the course title and department.
We increment the class variable for count and add the new instance to the classes list.
We set default values for the instance variables:
students, room, hours, and prereqs.
'''
# instance variable
## code below did not handle inheritance class variable correctly
## self.count = self.__class__.count
self.count = course.count
# self.__class__.count += 1
course.count += 1
## same problem as above
## self.__class__.classes.append(self)
course.classes.append(self)
self.title = title
self.dept = dept
self.students = []
self.room = ''
self.hours = ''
self.prereqs = []
def __repr__(self):
''' Display an object so it can be evaluated and created.'''
return '"course({}, {!r})"'.format(repr(self.title), self.dept)
def __str__(self):
''' Display an object as a string. '''
return "<course title:{self.title}, ({self.count})>".format(self=self)
## a static method is shared by all instances.
@staticmethod
def all(dept=''):
''' A static method is shared by all instances.
It is invoked with the class name', e.g., course.all()
'''
for c in course.classes:
if dept:
if c.dept == dept:
print (c)
else:
print (c)
def add_room(self, item):
''' Specify the classroom for an instance. '''
self.room = item
def add_student(self, item):
''' Enroll a student in the course. This method allows duplicates. '''
self.students.append(item)
def add_hours(self, item):
''' Specify the meeting time for the course. '''
self.hours = item
## allows duplicates
def add_prereqs(self, item):
''' Specify the prerequisites for the course. '''
self.prereqs.append(item)
def pp(self):
''' Pretty print the object. Format all the existing data elements. '''
p = "Course Title " + self.title
if self.dept: p += "\n\tDepartment: {self.dept}".format(self=self)
if self.room: p += "\n\tRoom: {}".format(self.room)
if self.hours: p += "\n\tHours: {}".format(self.hours)
if self.students: p += "\n\tStudents: {}".format(self.students)
if self.prereqs: p += "\n\tPrereqs: {!s}".format(self.prereqs)
return p
def all_prereqs(self):
''' Print out all the prerequsites for a given course.
Note that this function uses tree recursion.
'''
if self.prereqs:
for pr in self.prereqs:
print ("Prereq for {}: {}".format(self, pr))
pr.all_prereqs()
def __eq__(self, other):
''' Two courses are equal if they have the same title and
the same department.
This method overloads the == operator.
'''
return self.title == other.title and self.dept == other.dept
We can call the help function on the course class. We see the doc strings and methods.
help(course)
Help on class course in module __main__: class course(builtins.object) | course(title, dept) | | A class for describing courses, e.g., math or computer science. | | Methods defined here: | | __eq__(self, other) | Two courses are equal if they have the same title and | the same department. | This method overloads the == operator. | | __init__(self, title, dept) | This is an example of function overloading. | The constructor which sets the initial values of the course title and department. | We increment the class variable for count and add the new instance to the classes list. | We set default values for the instance variables: | students, room, hours, and prereqs. | | __repr__(self) | Display an object so it can be evaluated and created. | | __str__(self) | Display an object as a string. | | add_hours(self, item) | Specify the meeting time for the course. | | add_prereqs(self, item) | Specify the prerequisites for the course. | | add_room(self, item) | Specify the classroom for an instance. | | add_student(self, item) | Enroll a student in the course. This method allows duplicates. | | all_prereqs(self) | Print out all the prerequsites for a given course. | Note that this function uses tree recursion. | | pp(self) | Pretty print the object. Format all the existing data elements. | | ---------------------------------------------------------------------- | Static methods defined here: | | all(dept='') | A static method is shared by all instances. | It is invoked with the class name', e.g., course.all() | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __hash__ = None | | classes = [] | | count = 0
A user-defined prototype for an object that defines a set of attributes that characterize any object of the class. The attributes are data members (class variables and instance variables) and methods, accessed via dot notation.
class course:
A variable that is shared by all instances of a class. Class variables are defined within a class but outside any of the class's methods. Class variables are not used as frequently as instance variables are.
count = 0 classes = []
A class variable or instance variable that holds data associated with a class and its objects.
count = 0 self.count = self.__class__.count ## this does not handle inheritance properly. or self.count = course.count
The assignment of more than one behavior to a particular function. The operation performed varies by the types of objects or arguments involved. __init__()
defines the constructor for a class. Here, course()
creates a new course instance.
def __init__(self, title, dept):
Note that help() function includes the inherited methods.
class gradcourse(course):
''' The graduate course class inherits from the course class. '''
def __init__(self, title, dept):
course.__init__(self, title, dept)
self.grad = True
help(gradcourse)
Help on class gradcourse in module __main__: class gradcourse(course) | gradcourse(title, dept) | | The graduate course class inherits from the course class. | | Method resolution order: | gradcourse | course | builtins.object | | Methods defined here: | | __init__(self, title, dept) | This is an example of function overloading. | The constructor which sets the initial values of the course title and department. | We increment the class variable for count and add the new instance to the classes list. | We set default values for the instance variables: | students, room, hours, and prereqs. | | ---------------------------------------------------------------------- | Methods inherited from course: | | __eq__(self, other) | Two courses are equal if they have the same title and | the same department. | This method overloads the == operator. | | __repr__(self) | Display an object so it can be evaluated and created. | | __str__(self) | Display an object as a string. | | add_hours(self, item) | Specify the meeting time for the course. | | add_prereqs(self, item) | Specify the prerequisites for the course. | | add_room(self, item) | Specify the classroom for an instance. | | add_student(self, item) | Enroll a student in the course. This method allows duplicates. | | all_prereqs(self) | Print out all the prerequsites for a given course. | Note that this function uses tree recursion. | | pp(self) | Pretty print the object. Format all the existing data elements. | | ---------------------------------------------------------------------- | Static methods inherited from course: | | all(dept='') | A static method is shared by all instances. | It is invoked with the class name', e.g., course.all() | | ---------------------------------------------------------------------- | Data descriptors inherited from course: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes inherited from course: | | __hash__ = None | | classes = [] | | count = 0
m120 = course("Math 120", "Math")
c100 = course("CS100", "Computer Science")
c200 = course("CS200", "Computer Science")
c201 = course("CS201", "Computer Science")
c223 = course("CS223", "Computer Science")
c323 = course("CS323", "Computer Science")
c690 = gradcourse("CS690", "Computer Science")
m120
"course('Math 120', 'Math')"
c200
"course('CS200', 'Computer Science')"
str(c200)
'<course title:CS200, (2)>'
c200.title
'CS200'
c200.dept
'Computer Science'
dir(c200)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_hours', 'add_prereqs', 'add_room', 'add_student', 'all', 'all_prereqs', 'classes', 'count', 'dept', 'hours', 'pp', 'prereqs', 'room', 'students', 'title']
c200.__class__
__main__.course
Now we will add more data to the objects. First, the classrooms.
c100.add_room("Law School Auditorium")
c200.add_room("LC 101")
c201.add_room("LC 101")
c223.add_room("DL 220")
c323.add_room("DL 220")
c200.room
'LC 101'
Next, we add some students.
c200.add_student("Joe")
c200.add_student("Mary")
c200.add_student("Joe")
c200.add_student("John")
c200.add_student("Jane")
c200.students
['Joe', 'Mary', 'Joe', 'John', 'Jane']
Finally, we specify the pre-requisites.
c323.add_prereqs(c223)
c223.add_prereqs(c201)
c201.add_prereqs(c100)
c200.add_prereqs(c100)
c200.prereqs
["course('CS100', 'Computer Science')"]
c200.pp()
'Course Title CS200\n\tDepartment: Computer Science\n\tRoom: LC 101\n\tStudents: [\'Joe\', \'Mary\', \'Joe\', \'John\', \'Jane\']\n\tPrereqs: ["course(\'CS100\', \'Computer Science\')"]'
print(c200.pp())
Course Title CS200 Department: Computer Science Room: LC 101 Students: ['Joe', 'Mary', 'Joe', 'John', 'Jane'] Prereqs: ["course('CS100', 'Computer Science')"]
The pp() method pretty prints an instance of the course class.
The add_room, add_student, add_prereqs, and pp methods are instance methods. That is, they apply to a particular and specific instance of the class.
By contrast, the all()
method is a class method which operates over the entire class. It prints out all the instances of the course class.
course.all()
<course title:Math 120, (0)> <course title:CS100, (1)> <course title:CS200, (2)> <course title:CS201, (3)> <course title:CS223, (4)> <course title:CS323, (5)> <course title:CS690, (6)>
The all() method has an optional dept parameter to filter the results by department.
course.all('Math')
<course title:Math 120, (0)>
c323.prereqs
["course('CS223', 'Computer Science')"]
The all_prereqs() method recursively lists all prereqs for a course. Below we iterate through all the course instances, pretty printing each course, and then listing the pre-requisites for CS 323.
def test():
for c in course.classes:
print (c.pp())
print (c323.all_prereqs())
test()
Course Title Math 120 Department: Math Course Title CS100 Department: Computer Science Room: Law School Auditorium Course Title CS200 Department: Computer Science Room: LC 101 Students: ['Joe', 'Mary', 'Joe', 'John', 'Jane'] Prereqs: ["course('CS100', 'Computer Science')"] Course Title CS201 Department: Computer Science Room: LC 101 Prereqs: ["course('CS100', 'Computer Science')"] Course Title CS223 Department: Computer Science Room: DL 220 Prereqs: ["course('CS201', 'Computer Science')"] Course Title CS323 Department: Computer Science Room: DL 220 Prereqs: ["course('CS223', 'Computer Science')"] Course Title CS690 Department: Computer Science Prereq for <course title:CS323, (5)>: <course title:CS223, (4)> Prereq for <course title:CS223, (4)>: <course title:CS201, (3)> Prereq for <course title:CS201, (3)>: <course title:CS100, (1)> None
(from Learning Python, 5th Edition, page 1077.) The most common reasons to use Object Oriented Programming:
This one's easy (and is the main reason for using OOP). By supporting inheritance, classes allow you to program by customization instead of starting each program from scratch.
Wrapping up implementation details behind object oriented interfaces insulates users of a class from code changes.
Classes provide new local scopes, which minimizes name clashes. They also provide a natural place to write and look for implementation code, and to manage object state.
Classes naturally promote code factoring, which allows us to minimize redundancy. Thanks both to the structure and code reuse support of classes, usually only one copy of the code needs to be changed.
Classes and inheritance allow you to implement common interfaces, and hence create a common look and feel in your code; this eases debugging, comprehension, and maintenance.
This is more a property of OOP than a reason for using it, but by supporting code reuse generally, polymorphism makes code more flexible and widely applicable, and hence more reusable.
And, of course, the number one reason students gave for using OOP: it looks good on a resume! (OK, I threw this one in as a joke, but it is important to be familiar with OOP if you plan to work in the software field today.) Finally, ... you won't fully appreciate OOP until you've used it for a while. Pick a project, study larger examples, work through exercises -- do whatever it takes to get your feet wet with OO code; it's worth the effort.
from hamlet import *
Person Name hamlet Likes: ['fencing', 'philosophy'] Friends: [person('horatio', 'male')] Siblings: [person('rocky', 'male')] Parents: [person('gertrude', 'female'), person('King Hamlet', 'male')] Father: person('King Hamlet', 'male') Mother: person('gertrude', 'female') Uncles: [person('claudius', 'male'), person('larry', 'male')] Person Name laertes Siblings: [person('ophelia', 'female')] Parents: [person('polonius', 'male')] Father: person('polonius', 'male') Aunts: [person('lucy', 'female')] Person Name gertrude Children: [person('rocky', 'male'), person('hamlet', 'male')] Sons: [person('rocky', 'male'), person('hamlet', 'male')] Person Name ophelia Siblings: [person('laertes', 'male')] Parents: [person('polonius', 'male')] Father: person('polonius', 'male') Aunts: [person('lucy', 'female')] Person Name polonius Siblings: [person('lucy', 'female')] Children: [person('ophelia', 'female'), person('laertes', 'male')] Sons: [person('laertes', 'male')] Daughters: [person('ophelia', 'female')] Person Name horatio Friends: [person('hamlet', 'male')] Person Name King Hamlet Siblings: [person('claudius', 'male'), person('larry', 'male')] Children: [person('hamlet', 'male')] Sons: [person('hamlet', 'male')] Person Name larry Siblings: [person('King Hamlet', 'male')] Person Name lucy Siblings: [person('polonius', 'male')] Person Name rocky Siblings: [person('hamlet', 'male')] Parents: [person('gertrude', 'female')] Mother: person('gertrude', 'female')
The main class in this file is the person
class.
help(person)
Help on class person in module hamlet: class person(builtins.object) | person(name, gender) | | Class for person, including friends and relatives. | | Methods defined here: | | __init__(self, name, gender) | Constructor for person(name, gender) | | __repr__(self) | return string that can be evaluated to recreate this instance. | | __str__(self) | String representation of person. | | add_child(self, item) | Add child to list without dulication. Add parent to child. | | add_friend(self, item) | Add friend to list without duplication. Make reciprocal. | | add_like(self, item) | Add like to list without duplication. | | add_parent(self, item) | Add parent to list without duplication. Add child to parent. | | add_sibling(self, item) | Add sibling to list without duplication. Make reciprocal. | | aunt(self) | Get aunts (female siblings of parents). | | daughter(self) | Get daughters (female children) | | father(self) | Get father (male parent). | | mother(self) | Get mother (female parent). | | pp(self) | Pretty print person. | | son(self) | Get sons (male children). | | uncle(self) | Get uncles (male siblings of parent). | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | count = 11
jane = person('Jane', 'female')
jane
person('Jane', 'female')
str(jane)
'<Person Name: Jane (11)>'
jane.pp()
'Person Name Jane'
jane.add_like('programming')
print (jane.pp())
Person Name Jane Likes: ['programming']
dir(jane)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_child', 'add_friend', 'add_like', 'add_parent', 'add_sibling', 'aunt', 'children', 'count', 'daughter', 'father', 'friends', 'gender', 'likes', 'mother', 'name', 'parents', 'pp', 'siblings', 'son', 'uncle']
jane.__class__
hamlet.person
jane.gender
'female'
jane.likes
['programming']
john = person("John", 'male')
john.add_friend(jane)
print (jane.pp())
Person Name Jane Likes: ['programming'] Friends: [person('John', 'male')]