面向对象三大特性

来源

1.封装

定义: 指的是将数据和操作数据的方法打包在一起,形成一个类或对象。

封装主要包括以下几个方面的内容:

  • 数据隐藏:通过将对象的数据属性设置为私有或受保护的,防止外部直接访问和修改对象的数据。这样可以确保对象的数据在被操作时不会被意外篡改或破坏。
  • 方法封装:将对象对自身数据的操作封装在方法中,只通过方法来访问和修改对象的数据。这样可以确保对对象的操作符合预期,避免了外部错误地修改对象的数据。
  • 接口定义:通过定义公共接口,将对象的功能暴露给外部使用者。使用者只需关心如何使用接口提供的方法,而不需要了解内部实现细节。这样可以提高代码的可读性和可维护性,同时也能够实现代码的模块化和复用。

不同权限分类:
(1)公有权限
Python 中默认定义的属性和方法,都是公有的方法。无论是在类外,还是在派生的子类中,都可以进行访问,类似其它语言中的 public 修饰符的作用。
(2)保护权限(_ 单下划线前缀)
Python 在类中使用 单下划线前缀 实现其它语言中 protected 保护权限的功能,在属性或方法(包括类属性和类方法,作用相同)前添加一个单下划线——该属性或方法,在当前类中可以访问,在类外理论上不可访问(使用时不提示,但写出来程序可以运行,但有警告),在通过继承派生的子类中可以访问;
(3)私有属性( __ 双下划线前缀)
Python 在类中使用 双下划线前缀 实现其它语言中 private 私有权限的功能,在属性或方法(包括类属性和类方法,作用相同)前添加一个双下划线——该属性或方法,只能在当前类中可以访问,在类外任何位置不可访问(只是理论上不可访问,通过某些方式,还是可以在类外访问,不建议这样使用)。
(4)魔法方法(双下划线前缀与后缀):同时具有前后双下划线的变量或方法,这些方法是 Python 中的魔法属性或魔法方法,这些属性或方法名被赋予了特殊的作用,比如:初始化方法 init() , 对象描述方法 str() , 还有 iter() , next(),class(用来获取当前实例对象的类。) 方法等,这些都是魔法方法。

2.继承

定义: 指的是一个类基于另一个类来创建(创建出来的新类称为子类或派生类。被继承的类称为父类或基类)

继承优势:

  • 代码重用:继承允许子类重用父类的代码,减少了代码的冗余,提高了代码的可维护性和复用性。
  • 扩展性:通过继承,子类可以在父类的基础上添加新的属性和方法,实现对父类的扩展,使得子类具有更多的功能。
  • 类型的兼容性:由于子类继承了父类的特性,子类可以被当作父类的实例来使用。这样,在需要父类类型的地方,可以使用子类的实例,增加了代码的灵活性和可扩展性。

2.1单继承

(1)重构(方法重写)
在子类中,可以对父类中的方法实现进行重写(方法名不变,内部逻辑发生改变),实现新的功能实现。
特性:当子类和父类方法名和参数都相同时,子类调用的是子类的方法;

(2)Super()调用父类方法
如果在子类中还要使用父类中的方法,可以使用 super()函数来调用父类中的方法。
场景:比如在重写父类方法时,还要保留父类方法的功能。

class A(object):
    # A 继承自 object 根类
    def show(self):
        print("父类A的方法")

class B(A):
    # 子类重写父类方法
    def show(self):
        # 使用 super() 调用父类方法
        super().show()
        print("子类B的方法")
b = B()
b.show()

(3)单继承初始化
在子类对象初始化时,需要给出父类初始化所需的参数,然后使用 super() 调用父类初始化方法去初始化父类的属性。

class A(object):
    def __init__(self,a):
        self.a = a


class B(A):
    def __init__(self, a, b):
        super().__init__(a) #父类初始化
        self.b = b

总结: 无论在方法的重写,还是初始化时,父类的工作就让父类自己去完成,子类只负责自己部分的实现

2.2多继承

(1) 多继承同名方法查找顺序
如果在一个子类所继承的多个父类中,具有同名方法,那么在调用该方法名的方法时,Python 会使用C3算法实现的 MRO(方法解析顺序)顺序来确定查找的先后顺序,一般情况可以理解成是按继承类的书写顺序。 ——也就是哪个父类写在括号的前面就调用哪个父类的方法

(2)多继承初始化
• 在多继承中,由于有多个父类,每个父类的属性都需要单独初始化,这时 super() 函数只能引用继承书写顺序上的第一个父类,其它的父类是无法通过 super()引用的,所以也就无法利用 super()函数进行初始化。
• 此时,可以使用直接指定父类名的方式调用该父类中的方法。此方法也适用于多继承中的方法重写。

class FA(object):
    def __init__(self, a):
        self.a = a

class FB(object):
    def __init__(self, b):
        self.b = b

class S(FB, FA):
    def __init__(self, a, b, c):
        FA.__init__(self, a) #父类FA调用自己的初始化方法
        FB.__init__(self, b) #父类FB调用自己的初始化方法
        self.c = c


c = S(1,2,3)
print(c.a)
print(c.b)
print(c.c)

3.多态

定义: 简单来说,多态是指同一个方法或操作符在不同的对象实例上可以有不同的行为。这意味着可以通过一个共同的接口或基类引用不同的子类对象,并根据实际的对象类型来调用相应的方法。

多态的实现:
通常通过继承方法重写来实现。在继承关系中,子类可以重写父类的方法,在父类引用子类对象时,调用的实际上是子类重写后的方法。

# 中医
class Father:
    def cure(self):
        print("使用中医方法进行治疗。。。")

# 西医
class Son(Father):
    def cure(self):
        print("使用西医方法进行治疗。。。")

# 患者
class Patient:
    def needDoctor(self, doctor):
        doctor.cure()

if __name__ == '__main__':
    oldDoctor = Father()
    littleDoctor = Son()
    patient = Patient()

    patient.needDoctor(oldDoctor)
    patient.needDoctor(littleDoctor)

3.1鸭子类型

鸭子类型(Duck Typing)是一种动态类型的概念,它源自于“走起来像鸭子、叫声像鸭子、看起来像鸭子,那么它就是鸭子”的观念。
说明:

  • 举个例子,如果我们需要一个能“叫”的对象,并且某个对象有一个名为quack()的方法,那么我们可以将该对象视为一个“鸭子”,不管它实际上是什么类的对象。换句话说,我们关注的是对象的行为而不是其类型。
  • 鸭子类型在动态语言中特别常见,比如 Python。在 Python 中,不需要显式地继承或实现接口,只要一个对象具有必需的方法和属性,它就可以被认为是某种类型。这使得 Python 具有灵活性和简洁性,可以更自由地处理不同类型的对象。
  • 鸭子类型通常是动态语言的特性,相比于静态类型语言,Python在编译时没有类型检查。这意味着无法在编译阶段对类型不匹配或缺失方法和属性进行检测,可能会导致运行时错误。

3.2类型检测

Python 中提供了 isinstance() 和 issubclass() 两个函数,用来对数据进行检查判断。
A、 isinstance(obj, type)
格式:isinstance(obj, type),判断 obj 对象是否是 Type 指定类型或其父类类型的实例。
B、issubclass(Type1, Type2)
格式: issubclass(Type1, Type2),判断 Type1 是否是 Type2 的子类;