本节开始,我们将讨论面向对象编程的三大特征:封装、继承和多态。下面,我们将由简至难,依次讨论封装、继承、多态。
一. 封装 enclosure
- 封装:指隐藏类的实现细节,让使用者不用关心这些细节;
- 目的:让使用者通过尽可能少的方法(或属性)操作对象;
- 如何封装:通过私有属性和方法;
- 私有属性和方法:
- 以双下划线'__'开头,不以双下划线结尾的标识符为私有成员;
- 私有成员只能用此类的方法进行访问和修改
- 扩展:了解java的读者可能知道,java中使用了private、default、protected、public关键字,实现了更丰富的封装;
- 示例:
class A:
"""用私有属性和私有方法封装属性和方法"""
__dd = 300
def __init__(self):
# 创建私有属性,此属性在类外无法访问
self.__e = 100
@staticmethod
# 私有方法
def __en():
print('私有方法__en被调用!')
@classmethod
def get__dd(cls):
print("私有的类变量__dd:{}".format(cls.__dd))
def info(self):
print('A的实例方法info访问私有属性__e:', self.__e)
# 调用私有方法
self.__en()
if __name__ == "__main__":
a = A()
a.info()
a.__class__.get__dd()
# print(a.__e) #AttributeError: 'A' object has no attribute '__e'
# print(a.__en()) #AttributeError: 'A' object has no attribute '_en'
# 创建新变量(属性)
a.__e = 'hello'
print(a.__e)
print(a.__dict__)
运行结果:
二. 继承 inheritance 和 派生 derived
- 什么是继承/派生:
- 继承是指从已有的类中派生出新的类,新类具有原类的行为,并能扩展新的行为;
- 派生类就是从一个已有的类衍生出新的类,在新的类上可以添加新的属性和行为;
- 作用:
- 用继承派生机制,可以将一些共有功能加在基类中,实现代码共享;(共有的属性和方法向上提,形成抽象)
- 在不改变超类的代码基础上改变原有功能
- 名词:
- 基类 base class / 超类 super class / 父类 father class
- 派生类 derived class / 子类
1. 单继承
- 语法:
class 类名(
基类名
):
语句块
- 说明:单继承是派生类由一个基类衍生而来的
- 类的__base__属性:用来记录此类的父类
- 子类对象可以当成父类对象来使用:
- 示例
class Human:
"""此类用来描述人类的共同行为"""
@staticmethod
def say(what):
print('说:', what)
@staticmethod
def walk(distance):
print('走了', distance, '公里')
class Student(Human):
"""描述学生的共同行为"""
@staticmethod
def study(subject):
print('学习', subject)
class Teacher(Student):
@staticmethod
def teach(content):
print('正在教', content)
if __name__ == "__main__":
h1 = Human()
h1.say('Today is a good day.')
h1.walk(5)
print("#########################")
s1 = Student()
s1.say('How are you?')
s1.walk(5)
s1.study('Python')
print("#########################")
t1 = Teacher()
t1.say('I am a teacher.')
t1.walk(3)
t1.teach('讲解继承派生')
t1.study('滑冰')
运行结果:
2. 覆盖 override
- 概念:覆盖是指在有继承关系的类中,子类中实现了与父类同名的方法,子类实例调用该方法时,实际调用的是子类中覆盖版本的方法,这种现象被称为覆盖;
- super 函数:
- super(type, obj):返回绑定超类的实例(要求obj必须为type类型的实例);
- super():返回绑定超类的实例,等同于super(__class__,实例方法的第一个参数self);
- super()必须放在方法内调用
- 作用:返回绑定超类的实例,用超类的实例来调用其父类的覆盖方法;
- 显示调用父类的构造方法:当子类中实现了__init__方法,父类的构造方法并不会被调用,此时需要显示调用父类的构造方法:super().__init__(参数)
- 示例1:在方法内使用super()
class Human:
def __init__(self, n, a):
self.name = n
self.age = a
def info(self):
print('name:', self.name)
print('age:', self.age)
class Student(Human):
def __init__(self, n, a, s):
# 显示调用父类的初始化方法
super().__init__(n, a)
self.score = s
def info(self):
"""# 覆盖,子类只负责干子类的事情"""
super().info()
print('score:', self.score)
if __name__ == "__main__":
h1 = Human('Alex', 22)
h1.info()
s1 = Student('Thomas', 25, 99)
s1.info()
示例2:
class A:
def work(self):
print('A.work被调用!')
class B(A):
"""用super构造函数来间接调用父类的覆盖版本的方法"""
def work(self):
print('B.work被调用!')
def super_work(self):
"""此方法先调用一下子类的方法,再调用一下父类的方法"""
self.work()
# super().work() #调用父类的work,不能在方法外调用
# super(B,self).work()
super(__class__, self).work()
if __name__ == "__main__":
a = A()
a.work()
print("###################")
b = B()
b.work()
# 方法外部,使用super()函数调用B的父类A的work方法
print("###################")
super(B, b).work()
print("###################")
b.super_work()
示例2运行结果:
2. 多继承 multiple inheritance
- 概念:多继承是指一个子类继承自两个或两个以上的基类;
- 语法:class类名(基类名1, 基类名2...);
- 说明:
- 一个子类同时继承自多个父类,父类中的方法可以同时被继承下来;
- 如果两个父类中有同名的方法,则在子类中又没有覆盖此方法时,调用结果难以确定
- 问题(缺陷):多继承可能会有标识符(名字空间)冲突的问题;
- 多继承的MRO(Method Resolution Order)问题:
- '类'的__mro__属性:用来记录属性或方法的查找顺序;
- 示例:
class A:
def m(self):
print('A.m()')
class B:
def m(self):
print('B.m()')
class C:
def m(self):
print('C.m()')
class D(A, B, C):
def m(self):
super(A, self).m()
super(B, self).m()
super().m()
print('D.m()')
if __name__ == "__mian__":
d = D()
d.m()
print(D.__mro__)
运行结果:
结果分析: super()函数根据MRO顺序来查找方法
类D同时继承自类A,B, C,且这些类中都有方法m,在调用super时,方法的查找顺序是按照D.__mro__属性指定的顺序;在D.m()中首先调用的是super(A, self).m(),A的上一个是B,因此首先打印B.m();同理,super(B, self).m(),B的上一个是C,因此打印C.m()。
3. 用于类的函数:issubclass(cls, class_or_tuple)
- 判断一个类是否继承自其它的类,如果此类是 class 或 tuple 中的一个派生子类,则返回 True ,否则返回 False;
- 一切类型都是 object 的子类
三. 多态 polymorphic
- 什么是多态:多态是指在有继承/派生关系的类中,调用“基类对象的方法”,实际能调用子类的覆盖方法,这种现象被称为多态;
- 说明:
- 多态“调用的方法与对象相关”,不与类型相关;
- Python全部对象都只有运行时状态(动态),没有'C++语言'里的编译时状态(静态)
- 示例:
class Shape:
def draw(self):
print('Shape的draw()被调用')
class Point(Shape):
def draw(self):
print('正在画一个点')
class Circle(Point):
def draw(self):
print('正在画一个圆')
def my_draw(s):
"""示意多态的使用"""
# s.draw调用谁是在运行时由s的类动态决定
s.draw()
if __name__ == "__main__":
my_draw(Shape())
my_draw(Point())
my_draw(Circle())
运行结果: