The most important concepts in object-oriented programming are Class and Instance. It is essential to remember that a Class is an abstract template (e.g., the Student class), while an Instance is a specific “object” created from the Class. Every object has the same methods, but their respective data may differ.
Taking the Student class as an example again, in Python, a class is defined using the class keyword:
class Student(object):
pass
Immediately after class is the class name (i.e., Student), which is typically a word starting with an uppercase letter. This is followed by (object), indicating the parent class from which this class inherits. We will discuss inheritance later; generally, if there is no suitable parent class, use the object class — the ultimate parent class for all classes in Python.
Once the Student class is defined, we can create instances of Student using the class name followed by parentheses:
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>
As you can see, the variable bart points to an instance of Student, and 0x10a67a590 is its memory address (each object has a unique address). Student itself is the class.
You can freely bind attributes to an instance variable. For example, bind a name attribute to the bart instance:
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'
Since a class acts as a template, we can enforce the binding of essential attributes when creating an instance. By defining a special __init__ method, we bind attributes like name and score to the instance at creation:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
The special method __init__ has two underscores before and after its name!
Note that the first parameter of the __init__ method is always self, which refers to the instance being created. Therefore, inside the __init__ method, we can bind various attributes to self (as self points to the newly created instance).
With the __init__ method, you can no longer create an instance with empty parameters — you must pass arguments matching the __init__ method (excluding self, which the Python interpreter automatically passes):
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
Compared to ordinary functions, functions defined in a class differ only in that their first parameter is always the instance variable self (and self does not need to be passed when calling the method). Beyond this, class methods are no different from ordinary functions — you can still use default parameters, variable-length parameters, keyword parameters, and named keyword parameters.
A key feature of object-oriented programming is data encapsulation. In the Student class above, each instance has its own name and score data. We could access this data via an external function (e.g., to print a student’s grade):
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
However, since the Student instance already contains this data, there is no need to access it via external functions. We can directly define data access functions inside the Student class to “encapsulate” the data. These functions, which are associated with the Student class, are called class methods:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
To define a method, only the first parameter (self) differs from ordinary functions. To call a method, invoke it directly on the instance variable (omit self and pass other arguments normally):
>>> bart.print_score()
Bart Simpson: 59
From an external perspective, we only need to know that creating a Student instance requires name and score — the printing logic is defined internally in the Student class. The data and logic are “encapsulated”: calling the method is simple, and we do not need to know the internal implementation details.
Another benefit of encapsulation is the ability to add new methods to the Student class (e.g., get_grade):
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
Similarly, the get_grade method can be called directly on the instance variable without knowing its internal implementation:
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())
Unlike static languages, Python allows binding any data to instance variables. This means two instances of the same class may have different variable names:
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
student.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print("%s: %s" % (self.name, self.score))
def get_grade(self):
if self.score >= 90:
return "A"
elif self.score >= 60:
return "B"
else:
return "C"
bart = Student("Bart Simpson", 59)
lisa = Student("Lisa Simpson", 87)
print("bart.name =", bart.name)
print("bart.score =", bart.score)
bart.print_score()
print("grade of Bart:", bart.get_grade())
print("grade of Lisa:", lisa.get_grade())