In the previous lesson, we introduced the basics of object-oriented programming in Python. In this lesson, we continue with more advanced OOP topics.
In many object-oriented programming languages, object attributes are usually set as private or protected members. Simply speaking, that means these attributes are not allowed to be accessed directly. The methods of an object are usually public, because public methods are the messages that an object can receive, and they are also the calling interface exposed by the object to the outside world. This is the so-called access visibility.
In Python, we can use an underscore prefix on an object's attribute name to show access visibility. For example:
__namesuggests a private attribute_namesuggests a protected attribute
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
def study(self, course_name):
print(f'{self.__name} is studying {course_name}.')
stu = Student('Wang Dachui', 20)
stu.study('Python程序设计')
print(stu.__name) # AttributeError: 'Student' object has no attribute '__name'The last line of the code above raises AttributeError. From this we can see that the attribute __name starting with __ is like a private attribute, and it cannot be accessed directly outside the class. But the study method inside the class can access this attribute through self.__name. It should be explained that most people who use Python usually do not choose to make object attributes private or protected when defining classes. As one famous saying goes, "We are all consenting adults here." Adults can be responsible for their own behavior, and do not need Python itself to restrict access visibility. In fact, most programmers believe that open is better than closed, and making object attributes private is not absolutely necessary. So Python does not make the strictest limitation semantically. In other words, if you really want to, you can still access the private attribute __name above by using stu._Student__name.
Python is a dynamic language. A common explanation on Wikipedia is that it is a language whose structure can be changed at runtime. For example, new functions, objects, and even code can be introduced, and existing functions can be deleted, or other structural changes can happen. In Python, we can dynamically add attributes to objects. This is one privilege of Python as a dynamic language.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('王大锤', 20)
stu.sex = '男'If you do not want to dynamically add attributes to an object when using it, you can use the __slots__ magic.
class Student:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
stu = Student('王大锤', 20)
# AttributeError: 'Student' object has no attribute 'sex'
stu.sex = '男'The methods we defined before were all instance methods. In addition to instance methods, a class can also have static methods and class methods. These two kinds of methods are messages sent to the class. There is no essential difference between them. In the object-oriented world, everything is an object. Every class we define is actually also an object, and static methods and class methods are messages sent to class objects.
For example, define a triangle class. Use three side lengths to build a triangle and provide methods for calculating perimeter and area. Calculating perimeter and area are clearly methods of a triangle object. But when creating a triangle object, the three side lengths passed in may not be able to form a triangle. So we can first write a method to verify whether three given side lengths can form a triangle. This kind of method clearly is not an object method, because the triangle object has not been created yet when calling this method. We can design this kind of method as a static method or class method.
class Triangle(object):
"""Triangle"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
@staticmethod
def is_valid(a, b, c):
"""Check whether three sides can form a triangle."""
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self.a + self.b + self.c
def area(self):
p = self.perimeter() / 2
return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5
if Triangle.is_valid(3, 4, 5):
t = Triangle(3, 4, 5)
print(f'周长: {t.perimeter()}')
print(f'面积: {t.area()}')
else:
print('无效的边长!!!')The code above uses the staticmethod decorator to declare is_valid as a static method of the Triangle class. If we want to declare a class method, we can use the classmethod decorator. We can directly use ClassName.method_name to call static methods and class methods. The difference between them is that the first parameter of a class method is the class object itself, while a static method does not have this parameter. To summarize briefly, instance methods, class methods, and static methods can all be called in the form ClassName.method_name(...); the difference is whether the first parameter of the method is an ordinary object, a class object, or there is no object receiving the message at all. Static methods can usually also be written directly as independent functions, because they are not bound to a specific object.
Here, let us add one more explanation. We can add the property decorator to the methods that calculate the perimeter and area of the triangle. In this way, perimeter and area of the triangle class become two attributes. We no longer access them by calling methods, but get them directly by object attribute access. The changed code is shown below.
class Triangle(object):
"""Triangle"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
@property
def perimeter(self):
return self.a + self.b + self.c
@property
def area(self):
p = self.perimeter / 2
return (p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5
if Triangle.is_valid(3, 4, 5):
t = Triangle(3, 4, 5)
print(f'周长: {t.perimeter}')
print(f'面积: {t.area}')
else:
print('无效的边长!!!')Object-oriented programming languages support creating new classes based on existing classes, so that duplicate code can be reduced. The class that provides inheritance information is called the parent class (or base class), and the class that gets inheritance information is called the child class (or derived class).
For example, if we define a student class and a teacher class, we will find that they have a lot of duplicate code, and this duplicate code is the common properties and behaviors of teachers and students as people. So in this case, we should first define a Person class, and then derive Student and Teacher from the Person class.
class Person:
"""Person"""
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(f'{self.name} is eating.')
def sleep(self):
print(f'{self.name} is sleeping.')
class Student(Person):
"""Student"""
def __init__(self, name, age):
super().__init__(name, age)
def study(self, course_name):
print(f'{self.name} is studying {course_name}.')
class Teacher(Person):
"""Teacher"""
def __init__(self, name, age, title):
super().__init__(name, age)
self.title = title
def teach(self, course_name):
print(f'{self.name} {self.title} is teaching {course_name}.')
stu1 = Student('白元芳', 21)
stu2 = Student('狄仁杰', 22)
tea1 = Teacher('武则天', 35, '副教授')
stu1.eat()
stu2.sleep()
tea1.eat()
stu1.study('Python程序设计')
tea1.teach('Python程序设计')
stu2.study('数据科学导论')The syntax of inheritance is to specify the parent class of the current class in the parentheses after the class name when defining the class. If a class does not specify its parent class, then the default parent class is object. The object class is the top-level class in Python, and this means all classes are its child classes, either inheriting from it directly or indirectly. Python also allows multiple inheritance, which means one class can have one or more parent classes. We will discuss multiple inheritance in more detail later. In the initialization method of a child class, we can call the parent-class initialization method through super().__init__(). The super function is a built-in Python function specially designed to get the parent class object of the current object.
A child class can also override methods inherited from the parent class. Different child classes can give different implementation versions of the same method in the parent class. Methods like this will show polymorphic behavior when the program runs, meaning that calling the same method does different things. Polymorphism is the most essential part of object-oriented programming, and of course it is also the hardest part for beginners to really understand and use flexibly.
Python is a dynamically typed language, and objects in Python can have attributes added dynamically. The methods of an object are also attributes in essence, except that the attribute corresponds to a callable function. In the object-oriented world, everything is an object, and even classes can receive messages. Through inheritance, we can create new classes from existing classes, and then reuse the code of existing classes.