Contents
Object-oriented programming—Object Oriented Programming, or OOP for short—is a programming paradigm. OOP treats objects as the basic units of a program; each object contains data and functions that operate on that data.
Procedural programming views a computer program as a sequence of commands, that is, a set of functions executed in order. To simplify program design, procedural programming further splits functions into subfunctions, reducing system complexity by breaking large functions into smaller ones.
Object-oriented programming, by contrast, views a computer program as a collection of objects. Each object can receive messages from other objects and process them; program execution is a series of messages passed among objects.
I. Class and Instance
The most important concepts in object-oriented programming are class and instance.
- A class is an abstract template.
- An instance is a concrete “object” created from a class.
In Python, you define a class with the class keyword:
class Student(object):
pass
The class name immediately follows class—in this case, Student. Class names are usually capitalized words. Next comes (object), which indicates which class this one inherits from. We will cover inheritance later. If there is no suitable parent class, use object, the class that all classes ultimately inherit from.
You create an instance with the class name + ():
huffie = Student()
The variable bart points to a Student instance.
II. The Three Major Characteristics of Object-Oriented Programming: Encapsulation, Inheritance, and Polymorphism
2.1 Encapsulation
(1) init method
By defining a special __init__ method, you can bind attributes such as name and score when an instance is created:
class Student(object):
def __init__(self, name, score):
self.name = name
self.age = age
The first parameter of __init__ is always self, representing the instance being created. Inside __init__, you can bind various attributes to self, because self refers to the instance itself.
With __init__ in place, you can no longer create an instance with empty arguments. You must pass arguments that match __init__, but you do not pass self; the Python interpreter supplies the instance automatically:
huffie = Student('huffie', 22)
(2) Methods of the class
A Student instance already holds this data. To access it, there is no need to use external functions; you can define functions inside the class that access the data directly. That is how you encapsulate the “data.” These data-access functions are associated with the Student class itself; we call them methods of the class.
To define a method, the first parameter is self; everything else is the same as an ordinary function.
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 call a method, invoke it directly on the instance variable. You do not pass self; pass any other arguments normally:
bart.print_score()
(3) Access restrictions
A class can have attributes and methods internally. External code operates on data through the instance’s methods, which hides the internal complexity.
However, external code can still freely modify an instance’s attributes.
To prevent external access to internal attributes, prefix the attribute name with two underscores, __. In Python, if an instance variable name starts with __, it becomes a private variable, accessible only inside the class, not from outside:
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))
This ensures external code cannot arbitrarily modify object attributes, making the code more stable and reliable.
If external code needs to get name and score, or to modify score, you can add methods such as get_name and get_score to the Student class:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
(4) Class attributes and instance attributes
You bind attributes to an instance through the instance variable, or through self:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
You bind attributes to a class by defining them directly in class:
class Student(object):
name = 'Student'
See the following test for how class attributes behave:
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
From the example above, when writing programs, never use the same name for an instance attribute and a class attribute. An instance attribute with the same name shadows the class attribute, but after you delete the instance attribute, accessing that name again returns the class attribute.
2.2 Inheritance
When defining a class, you can inherit from an existing class. The new class is called the subclass; the inherited class is called the base class, parent class, or superclass (Base class, Super class).
(1) Benefit of inheritance 1: subclasses inherit parent attributes and methods
The biggest advantage of inheritance is that a subclass gets all the functionality of its parent. That is, a subclass automatically has all shared attributes and methods of the parent.
For example, for Dog, Animal is its parent class; for Animal, Dog is its subclass. Cat is similar to Dog.
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
(2) Benefit of inheritance 2: subclasses can override parent methods
When both the subclass and the parent class define a run() method, we say the subclass’s run() overrides the parent’s run(). At runtime, the subclass’s run() is always called. That gives us another benefit of inheritance: polymorphism.
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
2.3 Polymorphism
First, when we define a class, we are actually defining a data type. The data types we define are no different from built-in Python types, such as str, list, and dict.
In an inheritance relationship, if an instance’s data type is a subclass, that instance can also be treated as an instance of the parent class.
(1) Benefits of polymorphism
When you add a new subclass, any function or method that takes the parent class as a parameter can keep working without modification. That is:
- Open for extension: allow new subclasses of the
父类; - Closed for modification: functions that depend on the
父类type do not need to change. (For example,def func(父类).)
(2) Dynamic languages
Define an Animal class:
class Animal(object):
def run(self):
print('Animal is running...')
Consider the following function that depends on the Animal class and calls the Animal class’s method run:
def run_twice(animal):
animal.run()
animal.run()
For a dynamic language like Python, you do not necessarily need to pass an Animal type. You only need to ensure the passed object has a run() method:
class Timer(object):
def run(self):
print('Start...')
Even if the Timer class and the Animal class are unrelated, as long as both have a run method, you can use a function that depends on Animal.
This is “duck typing” in dynamic languages: it does not require a strict inheritance hierarchy. If an object “looks like a duck and walks like a duck,” it can be treated as a duck.
animal = Animal()
timer = Timer()
run_twice(animal)
run_twice(timer)
III. Advanced Object-Oriented Programming
3.1 @property
Earlier we introduced set and get methods to keep code stable, but that calling style is too cumbersome. So… for Python programmers who pursue perfection, using parameters has to be simple!
Python’s built-in @property decorator turns a method into something you call like an attribute:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
To turn a getter into an attribute, add @property.
To turn a setter into an attribute, add @func_name.setter.
If you define only a getter and no setter, the attribute is read-only.
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
3.2 Customized Classes
(1) slot
Define a class:
class Student(object):
pass
Bind an attribute to an instance:
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
Bind a method to an instance:
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
Bind a method to a class so all instances can call it:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
As shown above, after an instance is defined you can add attributes freely. But what if we want to restrict which attributes an instance may have?
Python lets you define a special __slots__ variable when defining class to limit which attributes instances of that class may have:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
Test:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
When using __slots__, note that __slots__ restricts attributes only for instances of the current class; it does not affect inherited subclasses.
(2) str
First define a Student class and print an instance:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> s = Student('Michael')
>>> print(s)
<__main__.Student object at 0x109afb190>
>>> s
<__main__.Student object at 0x109afb190>
That prints a pile of <__main__.Student object at 0x109afb190>—not pretty.
How do you make the output look better? Define __str__() and __repr__ to return a readable string.
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s) from __str__' % self.name
... def __repr__(self):
... return 'Student object (name: %s) from __repr__' % self.name
...
>>> s = Student('Michael')
>>> print(s)
Student object (name: Michael) from __str__
>>> s
Student object (name: Michael) from __repr__
Printed instances look better and make important internal data easy to see.
Shortcut: __repr__ = __str__
(3) getattr
Normally, when you access a method or attribute of the class that does not exist, Python raises an error.
To avoid that error, besides adding a score attribute, Python provides another mechanism: implement __getattr__() to return an attribute dynamically. Modify as follows:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
When you access a nonexistent attribute or method, you get a return value:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
>>> s.age()
25
(4) call
Any class can be called directly on its instances by defining a __call__() method.
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
Call it like this:
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
__call__() can also take parameters. Calling an instance directly is like calling a function, so you can treat an object as a function and a function as an object—there is no fundamental difference between the two.
3.3 Enumeration Classes
When we need to define constants, one approach is to use uppercase variables assigned integers. That is simple, but the type is still int, and they remain variables.
A better approach is to define a class type for such enumerations, where each constant is a unique instance of the class. Python provides the Enum class for this:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
That gives us an enumeration class of type Month; you can reference a constant directly with Month.Jan.
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
The value attribute is the int constant assigned to each member, counting from 1 by default.
To control the enumeration more precisely, derive a custom class from Enum:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
Comments