Python测开28期 - 旎奥 - 学习笔记 - python高级语法:面向对象

一、面向对象概念

面向对象编程

面向对象编程(Object-Oriented Programming,OOP)是一种基于对象的编程范式。

它将程序视为一组相互作用的对象,每个对象都有自己的属性(数据)和方法(行为)。

通过对象之间的交互和消息传递,来完成任务和解决问题。

在面向对象编程中,将真实世界中的事物抽象成对象,并通过定义对象的类来描述对象的属性和方法。类是对象的模板,描述了对象共有的属性和可以执行的方法。通过创建类的实例(即对象),可以使用该实例的属性和方法。

面向对象编程的特点包括:

  1. 封装性(Encapsulation):将数据和对数据的操作封装在对象中,仅向外部暴露必要的接口,隐藏了内部的实现细节。
  2. 继承性(Inheritance):通过继承机制,可以从已存在的类派生出新的类,新类自动获得了父类的属性和方法,并可以在此基础上进行扩展和修改。
  3. 多态性(Polymorphism):同一种类型的对象在不同的上下文中可以有不同的行为,即在不同的情况下可以使用相同的接口来实现不同的功能。
  4. 类与对象:类是对象的模板,描述了对象的属性和方法。对象是类的实例,通过类创建的实例可以访问和操作该类定义的属性和方法。
  5. 信息传递:面向对象编程通过消息传递的方式实现对象之间的交互和通信。

面向过程编程与面向对象编程的区别

面向过程编程(Procedural Programming)和面向对象编程(Object-Oriented Programming)是两种不同的编程范式,它们在思想、设计和实现上存在一些重要的区别。

  1. 抽象程度:面向过程编程将程序划分为一系列的过程或函数,通过函数之间的调用来完成任务。而面向对象编程将程序看作是一组相互作用的对象,每个对象都有其自己的属性和行为。
  2. 封装性:面向过程编程强调的是数据和函数的分离,函数可以直接访问和操作数据。而面向对象编程通过封装将数据和对数据的操作绑定在一起,只暴露出必要的接口,隐藏了内部的具体实现细节。
  3. 继承性:面向对象编程具有继承的特性,可以通过继承机制从已存在的类派生出新的类,并继承父类的属性和方法,以实现代码的重用和扩展。而面向过程编程没有继承的概念,代码重用通常通过函数的封装和复用来实现。
  4. 多态性:面向对象编程支持多态,即同一种类型的对象在不同的上下文中可以有不同的行为。这使得代码更加灵活和可扩展。而面向过程编程没有多态的概念,需要通过条件语句来实现不同情况的处理。
  5. 设计思想:面向对象编程注重的是问题领域的建模和抽象,关注对象之间的关系和交互。而面向过程编程更加注重问题的解决步骤和过程化的思维。

二、类和对象

什么是类

在面向对象编程中,类(Class)是一种定义现实事物属性和方法的蓝图或模板。类描述了现实事物的特征(属性)和行为(方法)。可以把类看作是创建现实事物的原型。

类是现实事物的抽象,它定义了一类具有相似特征和行为的事物的通用结构和行为。类提供了对象所需的状态和行为,并定义了对象的初始化、操作和销毁等方法。

类由属性(也称为成员变量)和方法组成。属性是类的特征,用于描述类的状态。方法是定义在类中的函数,用于描述类的行为和操作。

通过使用类,我们可以创建多个具有相同属性和方法的对象。类中的属性和方法是相对独立的,每个对象都有自己的属性副本,但共享类中的方法。

例如,我们可以定义一个名为"Person"的类,该类可以有属性如姓名、年龄、性别等,方法如获取姓名、修改年龄、输出个人信息等。然后,我们可以创建多个实例(对象)来表示不同的人,每个实例都有自己的姓名、年龄和性别。

通过类的封装和抽象,可以更好地组织和管理代码,提高代码的可读性、可维护性和重用性。类是面向对象编程的重要概念之一。

什么是对象

在面向对象编程中,对象(Object)是类的一个实例。对象是具体存在的,具有状态和行为的实体。

可以把类看作是对象的模板或蓝图,描述了对象应该具有的属性和方法。当我们通过类创建一个具体的实例时,这个实例就是一个对象。

对象有两个关键的特征:状态和行为。

  1. 状态(State):对象的状态由它的属性(也称为成员变量)决定,属性表示了对象的特点和特征。例如,一个人对象的状态可以包括姓名、年龄、性别等属性,这些属性的值可以根据对象的实际情况而不同。
  2. 行为(Behavior):对象的行为由它的方法(也称为成员函数)决定,方法表示了对象能够执行的操作和行为。例如,一个人对象的行为可以包括走路、说话、工作等方法,通过调用这些方法可以让对象执行相应的行为。

对象是类的实例,可以根据类的定义创建多个对象。每个对象都有自己的属性值,在相同的类下,不同的对象可以具有不同的状态和行为。

通过对象,我们可以对数据进行封装和操作,通过对象之间的交互和消息传递,实现程序的逻辑和功能。

总结来说,对象是具体的实体,具有属性和方法。它是类的一个实例,通过类定义可以创建多个对象。对象是面向对象编程的核心和基本单位,通过对象的封装和抽象,我们可以更好地组织和管理代码。

类的定义

Python 使用 class 关键字来创建一个新类,class 之后为类的名称。

Python 存在一个根类 object类,所有的类都由根类派生而来,如果自定义类继承于根类,可以省略。

格式:

class ClassName:
    pass
# 或
class ClassName(object):
    pass

示例:

class Plane(object):

    def flying(self,hour):
        print(f"飞机已飞行{hour} 小时。。。")

实例对象

一个类定义好了,还不能使用,比如系统的list类,类只是规定了该类型的数据具有什么特征和行为,而真正要去使用这些特征和行为,必须有一个真实的列表存在。

实例对象本质上就是使用自定义类型去声明一个变量

格式:实例对象名 = 类名()

class Plane(object):

    def flying(self,hour):
        print(f"飞机已飞行{hour} 小时。。。")

# 实例了两个对象
airPlane1 = Plane()
airPlane2 = Plane()
# 使用不同的对象调用了类中的方法
airPlane1.flying(3)
airPlane1.flying(5)
airPlane2.flying(3)

三、实例属性

动态绑定

Python 中的属性变量都是使用动态绑定的方式绑定到实例对象上的。

格式:实例对象名.实例属性名

class Student:
    pass
# 实例对象
s1 = Student()
s2 = Student()

# 为实例对象s1动态绑定属性
s1.name = "Tom"
s1.age = 22
# 访问实例对象s1的属性
print(s1.name)
print(s1.age)

# 输出什么?
print(s2.name)
print(s2.age)

从代码中可以看出,在使用动态绑定属性时,给哪个实例对象绑定的属性,哪个对象才会拥有属性变量,没有绑定的则没有。

四、构造方法

在实例属性中,通过动态绑定的方式为实例对象添加了属性。

但是这种操作显然是不符合逻辑的。每个实例对象一旦被实例,就应该含有类中定义的属性。

此时就需要使用构造方法来实现。

构造方法

构造方法__init__(self) 在实例对象时自动调用, self 参数不需要手动传参,该参数在实例对象时,由解释器自动传入被实例的对象。

class Student:
    def __init__(self):
        print("Init Run ...")

s1 = Student()
s2 = Student()

此时,还不能定义属性,构造方法本质上就是一个具有特殊意义的函数,而在函数中直接定义变量不是属性,而是函数内的局部变量。

self

self 是一个特殊的关键字,用来表示当前被实例的对象,可以理解为人称代词

通过 self 可以定义或访问实例对象的属性或方法

格式:self.属性名 = 值

class Student:
    def __init__(self):
        self.name = "Tom"
        self.age = 22

s1 = Student()
s2 = Student()

print(s1.name)
print(s1.age)

print(s1.name)
print(s1.age)

通过 构造方法self ,实现了为实例对象定义属性,其本质上还是为实例对象动态绑定属性,只是动态绑定的时机变了,从实例完对象再绑定属性,变成了在自动调用执行的构造方法中进行动态绑定。

并且,从上面的代码中还发现另外一个问题,就是所有实例的对象出来,都有共同的属性值,这显然也是不符合逻辑的。

带参构造方法

构造方法也可以携带参数,根据类中属性的定义,传入对应的参数对实例属性进行实始化。

格式:__init__(self, args....)

class Student:
    def __init__(self,name, age):
        self.name = name
        self.age = age

s1 = Student("Tom", 22)
s2 = Student("Jack", 23)

print(s1.name)
print(s1.age)

print(s1.name)
print(s1.age)

此时,才通过构造方法,实例了真正的对象。

__str__(self) 方法

在实例对象后,如果直接打印对象,输出该对象的相关信息,发现实际输出的并不是想要的结果。而是该实例对象的类型和地址。

如果想在输出实例对象时,按指定的格式输出,需要实现 __str__(self) 方法

该方法不接收除 self 以外的参数,self 参数自动传入,函数只能返回一个字符串。

class Student:
    def __init__(self,name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return f"Name: {self.name} -- Age: {self.age}"
s1 = Student("Tom", 22)
s2 = Student("Jack", 23)
print(s1)
print(s2)

五、实例方法

实例方法用来定义对象的行为。实例方法本质上就是定义在类中的函数。

实例方法默认携带一个参数self, 在程序执行时,由解释器自动传入调用该方法的实例对象,通过此参数,可以在当前实例方法中调用其它实例方法或属性。

格式:

def 方法名(self):
    pass

# 或

def 方法名(self, args....):
    pass

示例:

class Student:
    def __init__(self,name, age):
        self.name = name
        self.age = age
        self.courses = []
    def __str__(self):
        return f"Name: {self.name} -- Age: {self.age}"

    def select_course(self,courseName):
        self.courses.append(courseName)

    def all_course(self):
        print(f'{self.name} 本学期选课如下:')
        for idx, c in enumerate(self.courses):
            print(f"第{idx+1}门课:{c}")

s1 = Student("Tom", 22)
s2 = Student("jack", 23)
s1.select_course("Python")
s1.select_course("Java")
s1.select_course("PyTest框架")

s2.select_course("C")
s2.select_course("JavaScript")
s2.select_course("HTML")

s1.all_course()
s2.all_course()

print(s1)
print(s2)

image

六、类属性

在 Python 中,一切皆为对象,类也不例外,在程序运行过程中,类也会做为一个对象使用。

类对象与实例对象不同,可以理解为实例对象是由类对象复制而来,每个实例对象之间具有数据独立性。而类对象在程序运行过程中,只有一个。

既然是对象,那么就可以拥有自己的属性,在类中定属性时,属性名有self前缀的是实例属性,而在类中直接定义的属性即为类属性。

# 定义一个饮水机类
class WaterDispenser:
    # 剩余水量
    surplus_water = 1500
    # 出水口
    def water_outlet(self, n):
        WaterDispenser.surplus_water -= n
        print("剩余水量:", WaterDispenser.surplus_water)

wd1 = WaterDispenser()
wd2 = WaterDispenser()

wd1.water_outlet(100)
print(wd1.surplus_water)
wd2.water_outlet(200)
print(wd2.surplus_water)
print(WaterDispenser.surplus_water)

image

类属性特征:

  • 在类中直接定义的变量为类属性
  • 在方法中使用类属性时,需要使用类名做为前缀 类名.类属性名
  • 在类的外部可以通过类名或实例对象名访问类属性
  • 所有的实例对象名共享一个类属性
  • 实例对象只能获取类属性的值,不能直接进行修改,只能通过方法进行修改
    两个类,不同的类有自己不同的行为方法
# 定义一个饮水机类
class WaterDispenser:
    # 剩余水量
    surplus_water = 1500
    # 出水口
    def water_outlet(self, n):
        WaterDispenser.surplus_water -= n
        print("剩余水量:", WaterDispenser.surplus_water)


# 定义一个人的类,持有一个饮水机对象
class Person(object):
    def __init__(self, wd):
        self.wd = wd

        # 人有一个接水的行为(方法)
    def get_water(self, n):
        self.wd.water_outlet(n)

wd = WaterDispenser()

tom = Person(wd)
jack = Person(wd)

tom.get_water(100)
print(wd.surplus_water)
jack.get_water(400)
print(wd.surplus_water)

image

七、类方法

一般类方法用来封装工具类使用,将一些复杂的代码逻辑封装成类方法,由类名直接调用,不需要实例对象,比如时间处理,网络请求处理等。

定义:

  • 使用 @classmethod 装饰器,第一个参数为类本身,所以通常使用cls命名做区分(非强制)
  • 在类内可以直接使用类方法或类变量,无法直接使用实例变量或方法
    调用:
  • 无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用
class MethodsDemo:
    param_a = 0  # 类变量

    # 定义一个类方法,第一个参数必须为self
    @classmethod
    def classmethod_demo(cls):  
        """
        类方法,第一个参数需要改为cls
        :return:
        """
        print('这是一个类方法', cls.param_a)

示例: 封装了一个日期时间获取的工具类

import datetime
class Utils:
    now = datetime.datetime.now()

    @classmethod
    def current_date_time(cls):
        return cls.now

    @classmethod
    def current_date(cls):
        return cls.now.strftime("%Y-%m-%d")

    @classmethod
    def current_time(cls):
        return cls.now.strftime("%H-%M-%S")

    @classmethod
    def getYear(cls):
        return cls.now.year

    @classmethod
    def getMonth(cls):
        return cls.now.month

    @classmethod
    def getDay(cls):
        return cls.now.day


print(Utils.current_date_time())
print(Utils.current_date())
print(Utils.current_time())
print(Utils.getYear())
print(Utils.getMonth())
print(Utils.getDay())

image

需要注意的是,如果类中即定义了实例属性,又定义了类方法,那么在类方法中是不能使用实例属性的,因为在使用类方法的过程中,实例对象不存在,所以不能使用实例属性。

import datetime
class Utils:
    now = datetime.datetime.now()

    @classmethod
    def current_date_time(cls):
        cls.show()
        return cls.now


    def show(self):
        print("show")


print(Utils.current_date_time())

u = Utils()
u.show()

八、静态方法

定义:

  • 使用 @staticmethod 装饰器,没有和类本身有关的参数
  • 无法直接使用任何类变量、类方法或者实例方法、实例变量

调用:

  • 无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用
"""静态方法"""
class StaticMethod:

    @classmethod
    def class_method(cls):
        print('这是一个类方法')

    def demo_method(self):
        print("这是一个普通方法")

    # 定义: 1、要使用staticmethod装饰器 2、这个方法是没有self、cls
    @staticmethod
    def static_demo(param1):

        print('这是一个静态方法')

# 2、调用:类.方法名
StaticMethod.static_demo('zhizhi')
demo = StaticMethod()
demo.static_demo('zhizhi')

普通方法、类方法、静态方法

名称 定义 调用 关键字 使用场景
普通方法 至少需要一个参数self 实例名.方法名() 方法内部涉及到实例对象属性的操作
类方法 至少需要一个cls参数 类名.方法名() 或者实例名.方法名() @classmethod 如果需要对类属性,即静态变量进行限制性操作
静态方法 无默认参数 类名.方法名() 或者实例名.方法名() @staticmethod 无需类或实例参与

静态方法在定义时,需要使用 @staticmethod 装饰器进行装饰,与类方法不同的是,静态方法没有默认参数。

静态方法和普通的函数本质上是一样的,只是定义在了类中。

一般情况下,静态方法同类方法一样,也是在封装工具类时使用,区别在于,静态方法中不需要使用类属性(不是不能使用,只是不建议)。

示例:封装两个数字操作的简单计算器

class Calc:
    @staticmethod
    def add(n1, n2):
        return n1 + n2

    @staticmethod
    def sub(n1, n2):
        return n1 - n2

    @staticmethod
    def mul(n1, n2):
        return n1 * n2

    @staticmethod
    def div(n1, n2):
        return n1 / n2


print(Calc.add(10, 20))
print(Calc.sub(10, 20))
print(Calc.mul(10, 20))
print(Calc.div(10, 20))

九、封装

封装是面向对象编程中三大特征之一,指的是将数据和操作数据的方法打包在一起,形成一个类或对象。

封装的目的是隐藏对象的内部实现细节,提供一个安全且易于使用的接口,使得对象之间的交互更加简单和可靠。

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

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

封装的优势包括:

  1. 数据安全性:封装隐藏了对象的内部实现细节,保护了数据的完整性和安全性。外部无法直接访问或修改对象的数据,必须通过规定的方法进行操作,减少了意外错误的发生。
  2. 代码模块化:封装将对象的数据和操作打包在一起,实现了代码的模块化。不同的对象可以独立开发和测试,降低了代码的耦合性,增加了系统的可维护性和扩展性。
  3. 简化接口:封装将对象的功能通过公共接口暴露给外部,隐藏了内部实现细节。外部使用者只需了解接口的使用方法,而无需关心具体的实现。这降低了外部使用者的使用难度,也提高了代码的可读性和易用性。

定义

  • 隐藏:属性和实现细节,不允许外部直接访问
  • 暴露:公开方法,实现对内部信息的操作与访问

作用

  • 限制安全的访问和操作,提高数据安全性
  • 可进行数据检查,从而有利于保证对象信息的完整性

封装的实现-访问控制

  • 保护属性:_属性名,仅供类体内部使用
  • 私有属性:__属性名
    • 被视作_类名__属性名
class Account:
    """账户"""

    # 普通属性
    bank = 'BOC'

    # 保护属性(内部属性)
    _username = 'zhizhi'

    # 私有属性
    __password = '888'


# 通过类名访问属性
print(Account.bank)  # 打印BOC
print(Account._username)  # 打印zhizhi
print(Account.__password)  # 报错AttributeError

print(Account.__dict__)
print(Account._Account__password)  # 打印888

无下划线前缀(公有权限)

Python 中默认定义的属性和方法,都是公有的方法。无论是在类外,还是在派生的子类中,都可以进行访问,类似其它语言中的 public 修饰符的作用。

class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10


    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")

obj = A()
# 在类外使用公有属性
print(obj.a)

# 在类外使用公有方法
obj.show()

_ 单下划线前缀(保护权限)

Python 在类中使用 单下划线前缀 实现其它语言中 protected 保护权限的功能,在属性或方法(包括类属性和类方法,作用相同)前添加一个单下划线,该属性或方法,在当前类中可以访问,在类外理论上不可访问(使用时不提示,但写出来程序可以运行,但有警告),在通过继承派生的子类中可以访问(继承在后面讲解)。

class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10
        # 保护属性
        self._b = 20

    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")
        # 在类中使用保护属性
        print(f"B: {self._b}")
        # 在类中使用保护权限的方法
        self._display()

    # 保护权限的方法
    def _display(self):
        print(f"B: {self._b}")


obj = A()
# 在类外使用公有属性
print(obj.a)
# 在类外无法使用保护仅限的属性(不建议这样使用)
print(obj._b)
# print(obj.__c)
# 在类外使用公有方法
obj.show()
# 在类外无法使用保护权限的方法(不建议这样使用)
obj._display()

__ 双下划线前缀(私有属性)

Python 在类中使用 双下划线前缀 实现其它语言中 private 私有权限的功能,在属性或方法(包括类属性和类方法,作用相同)前添加一个双下划线,该属性或方法,只能在当前类中可以访问,在类外任何位置不可访问(只是理论上不可访问,通过某些方式,还是可以在类外访问,不建议这样使用)。

class A(object):
    def __init__(self):
        # 公有属性
        self.a = 10
        # 保护属性
        self._b = 20
        # 私有属性
        self.__c = 30

    # 公有方法
    def show(self):
        # 在类中使用公有属性
        print(f"A: {self.a}")
        # 在类中使用保护属性
        print(f"B: {self._b}")
        # 在类中使用私有属性
        print(f"C: {self.__c}")
        # 在类中使用保护权限的方法
        self._display()
        # 在类中使用私有方法
        self.__info()


    # 保护权限的方法
    def _display(self):
        print(f"B: {self._b}")

    # 私有权限的方法
    def __info(self):
        # 在类中使用私有属性
        print(self.__c)
obj = A()
# 在类外使用公有属性
print(obj.a)
# 在类外无法使用保护仅限的属性(不建议这样使用)
print(obj._b)
# 在类外使用私有属性,访问失败
# print(obj.__c)
# 在类外使用公有方法
obj.show()
# 在类外无法使用保护权限的方法(不建议这样使用)
obj._display()
# 在类外访问私有方法,访问失败
# obj.__info()
class Student:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def setName(self, name):
        self.__name = name

    def getName(self):
        return self.__name

    def getAge(self):
        return self.__age


if __name__ == "__main__":
    tom = Student("Tom", 22)
    tom.setName("jack")
    print(tom.getName(), tom.getAge())

image

双下划线前缀与后缀

在 Python 中还有一种同时具有前后双下划线的变量或方法,这些方法是 Python 中的魔法属性或魔法方法,这些属性或方法名被赋予了特殊的作用,比如:初始化方法 __init__(), 对象描述方法 __str__(), 还有 __iter__(), __next__() 方法等,这些都是魔法方法。

封装的实现:暴露

  • 提供数据访问功能(getter)
    • 计算属性,该属性并不真正的存储任何状态,值是通过某种算法计算得到的,当程序对该属性进行赋值时,被赋的值通常存储到实例变量中
    • 语法:使用@property装饰器
    • 调用:实例.方法名
class Account:
    """账户"""

    # 普通属性
    bank = 'BOC'

    # 保护属性(内部属性)
    _username = 'zhizhi'

    # 私有属性
    __password = '888'

    @property
    def password(self):
        return self.__password

# 实例化
obj = Account()


# 访问实例的私有属性
print(obj.password)

修改

  • 提供数据操作功能(setter)
    • 语法:使用@计算属性名.setter装饰器
    • 调用:实例.方法名
class Account:
    """账户"""

    # 普通属性
    bank = 'BOC'

    # 保护属性(内部属性)
    _username = 'zhizhi'

    # 私有属性
    __password = '888'

    @property
    def password(self):
        return self.__password

    @password.setter
    def password(self, value):
        # 增加数据的校验
        if len(value) >= 8:
            self.__password = value
        else:
            print("密码长度最少有8位!")
# 实例化
obj = Account()


# 访问实例的私有属性
print(obj.password) # 888

# 修改私有属性(满足校验条件)
obj.password = 'zhizhi666'
print(obj.password) # zhizhi666

# 修改私有属性(不满足校验条件)
obj.password = '1123'
print(obj.password) #密码长度最少有8位!
# zhizhi666

十、继承(Inheritance)

创建出来的新类称为子类或派生类。被继承的类称为父类或基类。

通过继承,子类可以继承父类的属性和方法,并且可以在此基础上添加新的属性和方法,或者对继承的属性和方法进行修改。

继承的主要特点包括:

  1. 继承关系:继承创建了一个父类和子类之间的关系。子类继承了父类的特性,包括属性和方法。子类可以重用父类的代码,减少了代码的冗余。
  2. 子类的扩展:子类可以在继承父类的基础上,添加新的属性和方法。这样可以对父类进行扩展,使得子类具有更多的功能。
  3. 代码共享和重用:通过继承,子类可以共享父类的代码。父类中通用的属性和方法可以被多个子类继承和使用,提高了代码的重用性,并减少了开发时间和成本。
  4. 继承的层次结构:继承可以形成一个层次结构,其中一个父类可以有多个子类,而子类又可以成为其他子类的父类。这种层次结构可以更好地组织和管理代码,使得代码更加结构化和模块化。

继承的优势包括:

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

继承的实现

  • 语法:class类名(父类列表)
  • 默认父类是object
  • python支持多继承
class Human:
    """人类"""


    # 类属性
    message = '这是Human类的属性'

    # 构造方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        self.age = age

    # 实例方法
    def live(self):
        print('住在地球上')


class Student(Human):

    # 子类实例方法
    def study(self):
        print('正在学习')


# 实例化子类对象
stu = Student('zhizhi', 18)


# 访问属性,获得父类的类属性
print(stu.message)  # 这是Human类的属性
print(stu.name, stu.age)

# 访问实例方法
stu.live()
stu.study()

image

类型检查

  • isinstance(实例, 类名)
    • 检查对象是否是某个类及其派生类的实例
  • issubclass(类名1,类名2)
    • 检查类名1是否是类名2的子类
class Human:
    pass


class Student(Human):
    pass


class Teacher(Human):
    pass


# 检查实例和类
stu = Student()
print(isinstance(stu, Human)) # True

# 检查子类和父类
print(issubclass(Student, Human)) # True
print(issubclass(Student, Teacher)) # False

image

单继承

单继承是指一个子类只继承一个父类。

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

class B(A):
    # B类 继承自 A类
    def display(self):
        print("子类B的方法")

b = B()
# 子类对象使用自己的方法
b.display()
# 子类对象使用父类的方法,如果父类有没有该方法则继续向上查找,直到根类
b.show()

image

方法重写

在子类中,可以对父类中的方法实现进行重写,实现新的功能实现。

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

class B(A):
    # 子类重写父类方法
    def show(self):
        print("子类B的方法")

b = B()
# 当子类方法与父类方法同名时,调用子类方法
b.show()

super()

如果在子类中还要使用父类中的方法,可以使用 super()函数来调用父类中的方法。

比如在重写父类方法时,还要保留父类方法的功能。

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

class B(A):
    # B类 继承自 A类
    def display(self):
        print("子类B的方法")

    # 重写父类方法
    def show(self):
        super().show()
        print("子类B重写了父类A的show方法")

b = B()
# 子类对象使用自己的方法
b.display()
# 子类对象使用父类的方法,如果父类有没有该方法则继续向上查找,直到根类
b.show()

image

单继承的初始化

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

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

    def __str__(self):
        return str(self.a)


class B(A):
    def __init__(self, a, b):
        super().__init__(a)
        self.b = b

if __name__ == "__main__":
    obj = B(10, 20)
    print(obj.a)
    print(obj.b)

image

继承的访问控制

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

比如:如果在初始化时,想在子类中初始化父类的一个私有属性,这是不能实现的,但是可以调用父类的初始化方法对私有属性进行初始化

class A(object):
    # A 继承自 object 根类
    def __init__(self, a, b, c):
        self.a = a
        self._b = b
        self.__c = c

    def show(self):
        print(f"A: {self.a}")
        print(f"B: {self._b}")
        print(f"C: {self.__c}")


class B(A):
    def __init__(self,a,b,c,d):
        super().__init__(a,b,c)
        self.d = d

    def show(self):
        super().show()
        print(f"D: {self.d}")

b = B(1,2,3,4)
b.show()

image

class A(object):
    # A 继承自 object 根类
    def __init__(self):
        self.a = 10
        self._b = 20
        self.__c = 30

    def show(self):
        print(f"A: {self.a}")
        print(f"B: {self._b}")
        print(f"C: {self.__c}")


class B(A):
  
    def display(self):
        self.show()
        print(f"A: {self.a}")
        print(f"B: {self._b}")
        print(f"C: {self.__c}")

if __name__ == "__main__":

    b = B()
    print(b.a)
    print(b._b)
    print(b.__c)

多继承

多继承是指一个子类可以同时继承多个父类,此时子类同时拥有多个父类中的属性和方法

class FA(object):
    def fa_show(self):
        print("FA Show Run...")

class FB(object):
    def fb_show(self):
        print("FB Show Run...")

class S(FA, FB):
    def s_show(self):
        print("S Show Run...")


s = S()
s.s_show()
s.fa_show()
s.fb_show()

image

多继承同名方法查找顺序

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

class FA(object):
    def show(self):
        print("FA Show Run...")

class FB(object):
    def show(self):
        print("FB Show Run...")

class S(FB, FA):
    def s_show(self):
        print("S Show Run...")


s = S()
s.s_show()
s.show()

image

多继承初始化

在多继承中,由于有多个父类,每个父类的属性都需要单独初始化,这时 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)
        FB.__init__(self, b)
        self.c = c


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

image

十一、多态

多态允许不同的对象对同一个消息作出不同的响应。

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

多态性在实际应用中提供了很多好处,包括:

  1. 简化代码:通过以相同的方式处理不同的对象,并使用统一的接口进行编程,可以降低代码的复杂性和重复性。
  2. 可维护性:多态可以提高代码的可维护性。当需要新增一种子类时,不需要修改已有的代码,只需要创建一个新的子类并继承父类,就能够在原有的代码基础上实现新的功能。
  3. 扩展性:由于多态允许在不修改已有的代码的情况下新增功能,因此可以更容易地对系统进行扩展和适应需求的变化。

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

多态的概念

python是弱类型语言,对于弱类型语言来说,变量没有声明类型,因此,同一个变量可以在不同的时间代表不同的对象,当同一个变量调用同一个方法时,可以呈现出多种行为

  • 多态(Polymorphism)
    • 同名方法呈现多种行为

多态的表现

运算符的多态表现

  • +号
    • 加法:数字+数字
    • 拼接:字符串+字符串
    • 合并:列表+列表
print(1+1) # 2
print("hello"+"world") # hello world
print([1,2]+[3])      # [1, 2,3]

函数的多态表现

  • len()函数
    • 可以接收字符串
    • 可以接收列表
print(len("zhizhi"))
print(len([1,2,3]))

方法的多态表现

  • 同名变量调用同名方法呈现多种行为
class China:
    def speak(self):
        print('汉语')
        
class Usa:
    def speak(self):
        print('English')
        

# 实例化对象
cn = China()
us = Usa()

# 遍历
for x in (cn, us):
    x.speak() 
# 汉语
# English

3、多态与继承

  • 方法重写(override):子类的方法名称与父类的相同,子类优先级更高
  • 重写构造方法
    • super().init()
    • 父类名.init(self)
class Human:
    """人类"""


    # 类属性
    message = '这是Human类的属性'

    # 构造方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        self.age = age

    # 实例方法
    def live(self):
        print('住在地球上')


class Student(Human):

    # 重写父类构造方法
    def __init__(self, name, age, school):
        # 访问父类的构造方法
        # super().__init__(name, age)
        # super(Student, self).__init__(name, age)
        Human.__init__(self, name, age)
        self.school = school

    # 重写父类实例方法
    def live(self):
        print(f'住在{self.school}')


# 实例化子类对象
stu = Student('zhizhi', 12, 'gelanfenduo')
print(stu.school)

stu.live()
# 中医
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)

鸭子类型

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

在鸭子类型中,一个对象的适用性不是由它的类或接口决定,而是由它的方法和属性是否与所需的方法和属性匹配来决定。换句话说,只要一个对象具有特定方法和属性,我们就可以将其视为具有相同类型。

举个例子,如果我们需要一个能“叫”的对象,并且某个对象有一个名为quack()的方法,那么我们可以将该对象视为一个“鸭子”,不管它实际上是什么类的对象。换句话说,我们关注的是对象的行为而不是其类型。

鸭子类型在动态语言中特别常见,比如 Python。在 Python 中,不需要显式地继承或实现接口,只要一个对象具有必需的方法和属性,它就可以被认为是某种类型。这使得 Python 具有灵活性和简洁性,可以更自由地处理不同类型的对象。

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

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

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

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


if __name__ == '__main__':
    oldDoctor = Father()
    littleDoctor = Son()
    animalDoctor = AnimalDoctor()

    patient = Patient()

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

image

鸭子类型通常是动态语言的特性,相比于静态类型语言,它在编译时没有类型检查。这意味着无法在编译阶段对类型不匹配或缺失方法和属性进行检测,可能会导致运行时错误。

类型检查

Python 中提供了 isinstance()issubclass() 两个函数,用来对数据进行检查判断。

isinstance()

Python 中使用 isinstance() 来检查一个实例的类型

格式: isinstance(obj, type)

判断 obj 对象是否是 Type 指定类型或其父类类型的实例。

    print(isinstance(littleDoctor, Father))
    print(isinstance(littleDoctor, Son))
    print(isinstance(littleDoctor, AnimalDoctor))

image

前面示例的代码可以进行优化:

# 患者
class Patient:
    def needDoctor(self, doctor):
        if isinstance(doctor, Father):
            doctor.cure()
        else:
            print("此大夫医疗方法不适用病人。。。")

image

issubclass()

Python 中还可以使用 issubclass() 来检查类的继承关系。

格式: issubclass(Type1, Type2)

判断 Type1 是否是 Type2 的子类

    print(issubclass(Father, Father))
    print(issubclass(Son, Father))
    print(issubclass(AnimalDoctor, Father))

image

前面的示例也可优化为:

class Patient:
    def needDoctor(self, doctor):
        if issubclass(doctor.__class__, Father):
            doctor.cure()
        else:
            print("此大夫医疗方法不适用病人。。。")

image

__class__ 是一个魔法属性,用来获取当前实例对象的类