0
点赞
收藏
分享

微信扫一扫

Python 面向对象编程(三)

梦想家们 2021-09-29 阅读 127

提到面向对象,我们会毫不犹豫地想到其三大特征:

  • 封装
  • 继承
  • 多态

下面,我们将由简至难,依次讨论封装、继承和多态。

一. 封装 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()

通过实例方法 info 可以间接访问私有属性及私有方法:

>> a = A()
>> a.info()
A 的实例方法 info 访问私有属性 __e : 100
私有方法 __en 被调用!

在外部直接访问私有属性及私有方法:

>> a.__en()
...
AttributeError: 'A' object has no attribute '__en'
>> a.__e
...
AttributeError: 'A' object has no attribute '__e'
>> A.__dd
...
AttributeError: type object 'A' has no attribute '__dd'

下面我们分别查看对象 a 的属性及 A 的属性:

>> a.__dict__
{'_A__e': 100}
>> mappingproxy({'__module__': '__main__',
              '_A__dd': 300,
              '__init__': <function __main__.A.__init__(self)>,
              '_A__en': <staticmethod at 0x21553ea79a0>,
              'get__dd': <classmethod at 0x21553ea7340>,
              'info': <function __main__.A.info(self)>,
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              '__doc__': None})

可见私有属性/方法实际上被命名为了 _A私有属性/方法,因此使用 a.__e A.__dd a.__en() 抛出了 AttributeError 异常。

二. 继承 inheritance / 派生 derived

什么是继承 / 派生?
继承是指从已有的类中派生出新的类,新类具有原类的行为,并能扩展新的行为;
派生类就是从一个已有的类衍生出新的类,在新的类上可以添加新的属性和行为;

作用:

  • 用继承派生机制,可以将一些共有功能加在基类中,实现代码共享。
  • 将共有的属性和方法向上提,形成抽象。
  • 在不改变超类的代码基础上改变原有功能。

名词:
基类 base class / 超类 super class / 父类 father class 三者是等价的。
派生类 derived class / 子类 child class 二者是等价的。

2.1 单继承

语法:

class 类名(基类名):
   语句块

单继承即派生类由一个基类衍生而来:

class MyList(list):
    def insert_head(self, n):
        self.insert(0, n)

类的 __base__ 属性用来记录此类的父类:

>> MyList.__base__
list

子类对象可以当成父类对象来使用:

>> L = MyList(range(3,6))
>> L
[3, 4, 5]
>> L.append(6)
>> L
[3, 4, 5, 6]

子类可以添加新的行为 / 属性:

>> L.insert_head(2)
>> L
[2, 3, 4, 5, 6]

示例 2
Human 类用来描述人类的共同行为:

class Human:
    @staticmethod
    def say(what):
        print('说:', what)
 
    @staticmethod
    def walk(distance):
        print('走了', distance, '公里')

运行结果:

>> h1 = Human()
>> h1.say('Today is a good day.')
>> h1.walk(5)
说: Today is a good day.
走了 5 公里

Student 类描述学生的共同行为:

class Student(Human):
    @staticmethod
    def study(subject):
        print('学习', subject)

运行结果:

>> s1 = Student()
>> s1.say('How are you?')
>> s1.walk(5)
>> s1.study('Python')
说: How are you?
走了 5 公里
学习 Python

Teacher 类描述老师的共同行为:

class Teacher(Student):
    @staticmethod
    def teach(content):
        print('正在教', content)

运行结果:

>> t1 = Teacher()
>> t1.say('I am a teacher.')
>> t1.walk(3)
>> t1.teach('讲解继承派生')
>> t1.study('滑冰')

2.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)

运行结果:

>> h1 = Human('Alex', 22)
>> h1.info()
name: Alex
age: 22
>> s1 = Student('Thomas', 25, 99)
>> s1.info()
name: Thomas
age: 25
score: 99

示例 2:用 super 构造函数来间接调用父类中被覆盖的方法

class A:
    def work(self):
        print('A.work 被调用!')
 
 
class B(A):
    def work(self):
        print('B.work 被调用!')
 
    def super_work(self):
        """此方法先调用子类的方法,再调用父类的方法"""
        self.work()
        # super().work()
        # super(B,self).work()
        super(__class__, self).work()

调用父类 A 中的 work

>> a = A()
>> a.work()
A.work 被调用!

调用子类 B 中的 work

>> b = B()
>> b.work()
B.work 被调用!

在子类中调用被父类覆盖的方法:

>> super(B, b).work()
A.work 被调用!
>> b.super_work()
B.work 被调用!
A.work 被调用!

2.3 多继承 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()')

初始化子类,并调用子类的方法 m

>> d = D()
>> d.m()
B.m()
C.m()
A.m()
D.m()

上面的打印顺序怎么解释呢?我们不妨先来看 D.__mro__ 记录的查找顺序:

>> D.__mro__
(__main__.D, __main__.A, __main__.B, __main__.C, object)

super() 函数根据 MRO 顺序来查找方法:D 同时继承自类 ABC,且这些类中都有方法 m,在调用 super 时,方法的查找顺序是按照 D.__mro__ 属性指定的顺序:

  • D.m() 中首先调用的是 super(A, self).m()A 的上一个是 B,因此首先打印 B.m()
  • 接下来调用 super(B, self).m()B 的上一个是 C ,因此打印 C.m()
  • supper().m() ,即 D 的上一个,因此打印 A.m()

2.4 用于类的函数

issubclass(cls, class_or_tuple)
判断一个类是否继承自其它的类,如果此类是 classtuple 中的一个派生子类,则返回 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()

my_draw 中的 s.draw 调用谁是在运行时由 s 的类动态决定的:

>> my_draw(Shape())
Shape 的 draw() 被调用
>> my_draw(Point())
正在画一个点
>> my_draw(Circle())
正在画一个圆
举报

相关推荐

0 条评论