So far there has been a strict separation between the "state" of a program (the variables/data that you keep track of in variables) and the code/functions that process that state. You'll hear this referred to as procedural or imperative programming.
Another type of programming, object oriented programming (OOP), takes advantage of situations in which state and code can be logically grouped together into objects. Like the real world, there are types of objects (classes), each of which share properties/fields and operations/methods that can be performed.
OOP is most common when designing relatively large pieces of software. So while you may choose not to employ it in your own programs, you will likely have to interact with object orientation in Python modules (and, in fact, you already have!).
A string
is a class. Once you have an instance of the class (termed an object), you can call methods of the object that refer to its state (the letters that make it up).
x = "hello"
print(type(x))
# returns a new object whose state is
# all the letters of x, but upper case!
print(x.upper())
A list
is a class.
x = [8, 6, 7, 5, 3, 0, 9]
print(type(x))
There are functions that take lists as parameters...
print(len(x))
print(sorted(x))
But there are also methods of each list that use/modify its state (the elements in the list)...
x.sort()
print(x)
x.append(100)
print(x)
A class can be thought of as a template - you are saying all instances of that class (objects) share a set of properties and can perform certain methods.
Let's create a Student
class...
class Student:
# constructor: sets up the initial state
# of all object fields
# called automatically when Student()
def __init__(this, name, id):
# this refers to the object of
# type Student that we are creating
this.name = name
this.id = id
this.grades = []
def my_name_id(this):
# this refers to the object of type
# Student that came right before .my_name_id()
return "{} ({})".format(this.name, this.id)
def add_grade(this, grade):
this.grades.append(grade)
def gpa(this):
if len(this.grades) == 0:
return None
grademap = {
'A':4,
'B':3,
'C':2,
'D':1,
'F':0,
}
return sum([grademap[grade] for grade in this.grades])/len(this.grades)
# create a new object
alice = Student("alice", "12345")
# add grades (changes the object)
alice.add_grade('A')
alice.add_grade('B')
# use methods
print(alice.my_name_id())
print(alice.gpa())
# create another object
bob = Student("bob", "98765")
# add some grades
bob.add_grade('C')
bob.add_grade('C')
# use methods
print(bob.my_name_id())
print(bob.gpa())
To make common operations easier, Python supports "magic" methods that start/end with __
. There are many, but a couple allow you to perform very common operations:
By default, == returns true ONLY if both are actually the same object...
class Foo:
def __init__(this):
this.x = 8675309
x = Foo()
y = Foo()
z = x
print(x == y)
print(x == z)
However, the __eq__
method allows you to define what "same" means...
class Dog:
def __init__(this, name, tag):
this.name = name
this.tag = tag
def __eq__(this, other):
return isinstance(other, Dog) and other.tag == this.tag
a = Dog('spot', '12345')
b = Dog('spot', '98765')
c = Dog('rover', '12345')
print(a == b)
print(a == c)
print(b == c)
By default, str(object) isn't very useful...
print(str(a))
However, the __str__
method allows you to define what gets returned...
class Point2D:
def __init__(this, x, y):
this.x = x
this.y = y
def __str__(this):
return "({}, {})".format(this.x, this.y)
origin = Point2D(0, 0)
print(str(origin))
Let's create a course roster from a file of students
class Roster:
def __init__(this):
this.students = []
def add_student(this, name, id, grades):
s = Student(name, id)
for grade in grades:
s.add_grade(grade)
this.students.append(s)
def __str__(this):
return "\n".join([student.my_name_id() for student in this.students])
def avg_gpa(this):
if len(this.students) == 0:
return None
return sum([student.gpa() for student in this.students]) / len(this.students)
def read_roster(file_path):
r = Roster()
with open(file_path, 'r') as f:
for line in f:
values = line.split()
r.add_student(values[0], values[1], values[2:])
return r
myroster = read_roster('students.txt')
print(myroster)
print(myroster.avg_gpa())