Skip to content

前言 ​

  • 面向过程编程核心思想:把一个需求分解成一系列要执行的步骤,然后按照步骤依次执行这些任务(关注的是流程、步骤)。

  • 面向对象编程:这里的对象,可以理解为现实生活中具体的人/物在程序中的数字化身(万物皆对象)。在面向对象编程思想中,它把一个人/物的特征和功能打包到一起,是面向对象编程的基本单元。而这里提到的人/物的特征,在面向对象编程中,称之为 对象的属性;人/物的功能,称之为的 对象的方法。在面向对象的思想中,关注的是谁有什么样的能力,能做什么事儿 ,然后获取到该对象,让它帮我做这件事儿就可以了。

面向对象基础 ​

而在面向对象中,有两个非常重要的概念:类 与 对象。

  • 类:描述的是一组具有相同属性(特征)和方法(功能/行为)的模板。它只定义东西有什么属性、能做什么,但不是真实存在的东西。
  • 对象:对象是类的实例,是基于类创建出来的 (实例对象)。

提示

对象是由类创建出来的,创建对象的过程,也称为对象的实例化。一个类可以创建无数个对象。

面向对象定义语法1 ​

python
# 第一步:定义类
class 类名:
    pass

# 第二步:创建对象
对象名 = 类名()
对象名.属性名1 = 属性值1
对象名.属性名2 = 属性值2

# 定义类
class Car:
    pass

# 创建对象
car = Car()
# 给对象添加属性
car.brand="BMW"
car.name="X5"
car.price=500000
# 访问对象以及对象的属性
print(car) # <__main__.Car object at 0x0000021D7065B620> 对象的地址值,后面讲
print(car.brand, car.name, car.price) # BMW X5 500000
# 给对象的属性重新赋值
car.name="X3"
car.price=300000
print(car.brand, car.name, car.price) # BMW X3 300000

# 使用对象的__dict__特殊属性:以字典的形式打印对象中的属性
print(car.__dict__) # {'brand': 'BMW', 'name': 'X3', 'price': 300000}

这种方式在定义类的时候,并未明确指定类中有哪些属性,都是在基于类创建对象之后,动态添加的对象属性。【不建议】

说明

__dict__是Python中用户自定义类实例的一个特殊属性,用于以字典形式存储对象的属性 。

注意

类名的命名规范,遵循大驼峰命名法,每个单词的首字母都是大写,单词之间没有分隔符,比如:UserInfo,UserAccount 。

定义类的语法2:【推荐】 ​

python
# 定义类
class 类名:
    # __init__ 方法是初始化的方法, 会在对象创建时自动调用, 可以在该方法中为对象设置对应的属性 ;
    # self: 是类中方法的第一个参数,表示当前创建的实例对象,用于访问当前对象的属性,创建对象时不用给self赋值
    def __init__(self, 参数列表):
       self.属性名 = 参数值
       self.属性名 = 参数值

# 创建对象
对象名 = 类名(参数列表)

__init__ 方法是对象初始化的方法,也称之为构造方法,在我们基于类创建对象的时候,会自动调用该方法,主要用于设置对象的初始状态(设置对象属性)。

self 是类中方法的第一个参数,表示当前创建的实例对象,用于访问当前对象的属性。

代码示例如下:

python
"""
# 第一步:定义类
class 类名:
    pass

# 第二步:创建对象
对象名 = 类名()
对象名.属性名1 = 属性值1
对象名.属性名2 = 属性值2
"""
# 第一步:定义类
class Car:
    # 在__init__方法中定义属性,方法中的形参用来接收属性值。参数1:self表示该类的对象,不用赋值。
    # __init__方法在创建对象时自动执行。c1 = Car("BMW","X5",500000)
    def __init__(self,c_brand,c_name,c_price):
        self.brand=c_brand  # 将c_brand变量的值赋值给等号左边对象的brand属性
        self.name=c_name
        self.price=c_price


# 第二步:创建对象   对象名 = 类名()
c1 = Car("BMW","X5",500000)
# 打印对象
print(c1)  # <__main__.Car object at 0x000002591009B620> 对象在内存中的地址值,后面讲
# 获取属性值:对象名.属性名1
print(c1.brand,c1.name,c1.price) # BMW X5 500000
# 给对象的属性重新赋值
c1.name="X3"
c1.price=300000
# 对象的__dict__:以字典的方式输出所有属性名和属性值
print(c1.__dict__)  # {'brand': 'BMW', 'name': 'X5', 'price': 500000}

# 再创建一个对象
c2 = Car("奔驰","GLC",400000)
print(c2.__dict__)

说明:

定义在类的外面的称之为函数,定义在类中的函数称之为方法 。

实例方法 ​

  • 实例方法:就是属于 "对象/实例"的方法,必须通过对象来调用。在类中定义实例方法时,定义语法与之前学习的函数定义的方式是一致的。
python
# 定义类
class 类名:
    def __init__(self, 形参列表):
       self.属性名 = 参数值
       self.属性名 = 参数值
    
    def 方法名(self, 形参列表):
       ...
       
    def 方法名(self, 形参列表):
       ...


# 创建对象
对象名 = 类名(参数列表)
对象名.方法名(实参)

在声明实例方法时参数列表中的self表示当前实例对象,方法调用时就是基于创建出来的实例对象调用的,所以是无需传递self的 。

演示代码如下:

python
# 第一步:定义类
class Car:
    # 在__init__方法中定义属性,方法中的形参用来接收属性值。参数1:self表示该类的对象,不用赋值。
    # __init__方法在创建对象时自动执行。c1 = Car("BMW","X5",500000)
    def __init__(self,c_brand,c_name,c_price):
        self.brand=c_brand  # 将c_brand变量的值赋值给等号左边对象的brand属性
        self.name=c_name
        self.price=c_price

    # 定义实例方法:属于对象的,使用对象来调用,实例方法的定义格式参考之前的函数定义格式。
    def running(self,road): # 用哪个对象调用running方法,self就表示哪个对象
        """
        定义汽车在哪条路上跑
        :param road: 路名
        :return: 无
        """
        print(f"{self.brand} {self.name}正在{road}行驶...")

# 创建对象,调用方法
c1 = Car("宝马","X5",500000)
# 调用方法:结果变量 = 对象名.方法名(参数值)
c1.running("关山大道")

# 创建对象,调用方法
c2 = Car("奔驰","GLC",300000)
# 调用方法:结果变量 = 对象名.方法名(参数值)
c2.running("高新大道")

魔法方法 ​

  • 魔法方法是指Python中提供的以双下划线开头和结尾的特殊方法,用于定义类的特殊行为,比如:__init__。魔法方法是不需要我们手动调用的,Python会在合适的时机自动调用。
  • 常见魔法方法有下面这些:
魔法方法描述
init初始化方法
str以字符串的形式返回对象中的内容(打印单个对象会执行, 打印装有对象的容器不会执行)
repr以字符串的形式返回对象中的内容(打印单个对象会执行, 打印装有对象的容器会执行)
eq比较两个对象是否相等 (equal)
lt支持比较两个对象的大小,小于(less than)
le支持比较两个对象的大小, 小于等于(less than or equal)
gt支持比较两个对象的大小, 大于(greater than)
ge支持比较两个对象的大小, 大于等于(greater than or equal))

注意!!!

当 __str__ 未定义时,print / str 会使用 __repr__

代码示例:

python
# 定义类
class Car:
    # __init__ 方法是初始化的方法, 会在对象创建时自动调用, 可以在该方法中为对象设置对应的属性 ;
    # self: 是第一个参数, 表示当前所创建出来的实例对象,创建对象时不用给self赋值
    def __init__(self, c_brand, c_name, c_price):  # self表示后期创建的当前Car类的对象
        self.brand = c_brand
        self.name = c_name
        self.price = c_price

    # 定义实例方法
    def running(self, road):
        print(f"价格为{self.price}元的{self.brand} {self.name}正在{road}上高速行驶...")


# 创建Car对象,调用实例方式
c1 = Car("BMW", "X5", 500000)
print(c1) # <__main__.Car object at 0x00000216F22E46E0>

c2 = Car("BMW", "X5", 500000)
print(c2) # <__main__.Car object at 0x00000216F049D450>

# 比较两个对象是否相等和大小
print(c1 == c2)
print(c1 > c2)

通过测试,我们可以看到,我们直接将对象 c1,c2输出到控制台,输出出来的其实是对象在内容中的地址值(<main.Car object at 0x0000022C4A5C0590>和<main.Car object at 0x00000216F049D450>)。

而由于创建了两个对象,两个对象的地址值是不一样的,所以 c1==c2 结果是False。 而对象与对象之间是不能直接进行比较大小的,所以直接报错了。

如果说我们在定义类的时候,在类中声明了魔法方法 __str__,__repr__,__eq__,__lt__ ,那么此时在进行输出、比较的时候,又会是什么情况呢?我们一起来看。

python
# 第一步:定义类
class Car:
    # 在__init__方法中定义属性,方法中的形参用来接收属性值。参数1:self表示该类的对象,不用赋值。
    # __init__方法在创建对象时自动执行。c1 = Car("BMW","X5",500000)
    def __init__(self, c_brand, c_name, c_price):
        self.brand = c_brand  # 将c_brand变量的值赋值给等号左边对象的brand属性
        self.name = c_name
        self.price = c_price

    # 定义实例方法:属于对象的,使用对象来调用,实例方法的定义格式参考之前的函数定义格式。
    def running(self, road):  # 用哪个对象调用running方法,self就表示哪个对象
        """
        定义汽车在哪条路上跑
        :param road: 路名
        :return: 无
        """
        print(f"{self.brand} {self.name}正在{road}行驶...")

    # __str__:以字符串的形式返回对象中的内容(打印单个对象会执行, 打印装有对象的容器不会执行)
    def __str__(self):
        return f"品牌:{self.brand} 名称:{self.name} 价格:{self.price}元666"

    # __repr__:以字符串的形式返回对象中的内容(打印单个对象会执行, 打印装有对象的容器会执行)
    def __repr__(self):
        return f"品牌:{self.brand} 名称:{self.name} 价格:{self.price}元"

    # 提示:当 __str__ 未定义时,print / str 会使用 __repr__
    # __eq__:比较两个对象是否相等   (equal)
    def __eq__(self, other: Car):
        return self.brand == other.brand and self.name == other.name and self.price == other.price

    # __lt__:支持比较两个对象的大小,小于(less   than)
    # __le__ :支持比较两个对象的大小,   小于等于(less   than or equal)
    # __gt__ :支持比较两个对象的大小,   大于(greater than)
    def __gt__(self, other):
        return self.price > other.price
    # __ge__:支持比较两个对象的大小,  大于等于(greater than or equal))


# 创建对象
c1 = Car("宝马", "X5", 550000)
c2 = Car("宝马", "X5", 500000)
# c2 = c1 # 将c1的值赋值c2,此时c2的地址值就和c1一样
# 打印对象
print(c1)  # 优先自动执行__str__魔法方法,如果没有就执行__repr__魔法方法
print(c2)  # 优先自动执行__str__魔法方法,如果没有就执行__repr__魔法方法

# 将c1和c2保存到列表中,打印列表,不会执行对象的__str__魔法方法,但是会执行__repr__魔法方法
car_list = [c1, c2]
print(car_list)

# 比较对象的大小
print(c1 == c2)  # 会自动执行__eq__魔法方法,没有就根据地址值来比较对象的,地址值一样,就为True。
print(c1 > c2)  # 会自动执行__gt__魔法方法,没有这个方法就报错

此时,我们可以看到 输出的c1,c2对象不再是对象的内存地址值了,而是可视化的对象中的属性值。 并且对象也可以比较大小了,可以使用 < 或 > 来比较,这里其实就是比较对象中的price属性值的大小。

提示

Python中提供的__xxx__形式的特殊方法。魔法方法无需手动调用,Python会在合适的时机自动调用

属性详解 ​

在前面我们讲解 类 与 对象 的时候,已经讲到了如何在定义类的时候,声明对象的属性。接下来,我们来详细介绍一下类中的属性,属性分为两份:

  • 实例属性:属于每个具体对象的属性,每个对象都是独立的,在__init__中定义。(每个对象特有的数据)
  • 类属性:属于类本身的属性,所有实例共享的,在类中__init__方法外定义。(所有对象共享的数据或配置)

注意

通过实例查找属性时,会先查找实例属性,实例属性不存在时,再查找类属性 。

python
"""
实例属性:属于每个具体对象的属性,每个对象都是独立的,在__init__中定义。(每个对象特有的数据)
类属性:属于类本身的属性,所有实例共享的,在类中__init__方法外定义。(所有对象共享的数据或配置)
"""
# 第一步:定义类
class Car:
    # 类属性:属于类本身的属性,所有实例共享的,在类中__init__方法外定义。(所有对象共享的数据或配置)
    wheel = 4
    # 在__init__方法中定义属性,方法中的形参用来接收属性值。参数1:self表示该类的对象,不用赋值。
    # __init__方法在创建对象时自动执行。c1 = Car("BMW","X5",500000)
    def __init__(self, c_brand, c_name, c_price):
        # 实例属性:属于每个具体对象的属性,每个对象都是独立的,在__init__中定义。(每个对象特有的数据)
        self.brand = c_brand  # 将c_brand变量的值赋值给等号左边对象的brand属性
        self.name = c_name
        self.price = c_price

    # 定义实例方法:属于对象的,使用对象来调用,实例方法的定义格式参考之前的函数定义格式。
    def running(self, road):  # 用哪个对象调用running方法,self就表示哪个对象
        """
        定义汽车在哪条路上跑
        :param road: 路名
        :return: 无
        """
        print(f"{self.brand} {self.name}正在{road}行驶...")

    # __repr__:以字符串的形式返回对象中的内容(打印单个对象会执行, 打印装有对象的容器会执行)
    def __repr__(self):
        return f"品牌:{self.brand} 名称:{self.name} 价格:{self.price}元"

c1 = Car("宝马", "X5", 550000)
c2 = Car("奔驰", "GLE", 500000)

# 访问实例属性:对象名.属性名
print(c1.brand,c1.name,c1.price)
print(c2.brand,c2.name,c2.price)

# 访问类属性:类名.属性名
print(Car.wheel)
print(c1.wheel) # 通过对象访问属性时,会先查找实例属性,实例属性不存在时,再查找类属性
print(c2.wheel) # 通过对象访问属性时,会先查找实例属性,实例属性不存在时,再查找类属性

案例:图书管理系统 ​

采用面向对象的编程思想,完成图书管理系统的开发。图书管理系统可以有借书、还书、我的图书三大功能,通过控制台菜单与用户交互,具体的功能如下:

  1. 借书:输入图书的编号后,就可以借阅图书 (借阅前, 先将所有图书信息展示出来)。
  2. 还书:输入图书编号后,就可以归还该图书 (还书前, 先将当前已借阅的图书信息展示出来)
  3. 我的图书:展示已经借阅的图书列表,展示出书籍的编号、书名等信息
  4. 退出系统:结束程序
python
"""
采用面向对象的编程思想,完成图书管理系统的开发。图书管理系统可以有借书、还书、我的图书三大功能,通过控制台菜单与用户交互,具体的功能如下:
    一、图书类:Book
        1. 属性:书籍编号(book_id)、书籍标题(title)、作者(author)、总数量(total_num)、可用数量(available_num)
        2. 实例方法:
            decrease_stock(): 借阅书籍时, 扣减库存。
            increase_stock(): 归还书籍时, 增加库存。
        3. __repr__魔法方法,方便打印图书信息
    二、图书管理系统类 LibrarySystem:
        1. 在__init__中初始化两个字典,分别保存所有图书和我借的图书
        2. 定义系统的入口方法,入口方法中打印菜单信息并判断用户的选择
        3. 定义借书的方法
            3.1 打印所有图书信息
            3.2 获取键盘录入的要接的图书编号
            3.3 如果不存在,就提示错误信息
            3.4 如果已经借了,就不允许再借
            3.5 没有借过,调用图书的借书方法, 扣减库存
            3.6 将借阅的图书保存到"我的图书"字典中
        4. 定义还书的方法
            4.1 打印我已借的图书
            4.2 获取键盘录入的要归还图书编号
            4.3 如果不存在,就提示错误信息
            4.4 如果存在,调用图书的归还方法, 增加库存
            4.5 将图书从"我的图书"字典中移除
        5. 展示我借的图书
    三、调用图书管理系统类LibrarySystem的入口方法,测试系统各个功能
"""
# 一、图书类:Book
class Book:
    # 1. 属性:书籍编号(book_id)、书籍标题(title)、作者(author)、总数量(total_num)、可用数量(available_num)
    def __init__(self, book_id, title, author, total_num):
        self.book_id = book_id  # 书籍编号(book_id)
        self.title = title  #书籍标题(title)
        self.author = author    #作者(author)
        self.total_num = total_num  #总数量(total_num)
        self.available_num = total_num  #可用数量(available_num),默认等于总数量
    #2. 实例方法:
    # decrease_stock(): 借阅书籍时, 扣减库存(可用数量-1)。
    def decrease_stock(self):
        # 如果可用数量大于0,才能借书
        if self.available_num>0:
            self.available_num-=1
            return True # 借书成功
        return False  # 库存不足,借书失败

    # increase_stock(): 归还书籍时, 增加库存。
    def increase_stock(self):
        self.available_num += 1

    #3. __repr__魔法方法,方便打印图书信息
    def __repr__(self):
        return f"编号:{self.book_id} 标题:{self.title} 作者:{self.author} 总数量:{self.total_num} 可用数量:{self.available_num}"

# 二、图书管理系统类 LibrarySystem:
class LibrarySystem:
    # 1.在__init__中初始化两个字典,分别保存所有图书和我借的图书
    def __init__(self):
        # 定义字典,存储所有图书
        self.books_dict={
            "1001": Book("1001", "《Python 教程》", "张益达", 5),
            "1002": Book("1002", "《C++ 教程》", "张伟", 3),
            "1003": Book("1003", "《Java 教程》", "李磊", 2),
            "1004": Book("1004", "《C 教程》", "张伟", 1),
            "1005": Book("1005", "《JavaScript 教程》", "莫山山", 4)
        }
        # 定义字典,存储用户借的图书
        self.borrowed_books_dict:dict[str,Book]={}

    # 2. 定义系统的入口方法,入口方法中打印菜单信息并判断用户的选择
    def run(self):
        # 1 打印欢迎词
        print("\t\t\t\t\t o(* ̄▽ ̄*)ブ欢迎使用图书管理系统o(* ̄▽ ̄*)ブ")
        menu = """
                # # # # # # # # 【菜单】 # # # # # # # # # # ##  
                #  1. 借书   2. 还书   3. 我的图书  4. 退出系统 #
                # # # # # # # # # # # # # # # # # # # # # # ## 
               """
        # 2 定义while死循环,打印菜单
        while True:
            print(menu)
            # 3 获取用户键盘输入的选项
            choose = input("请输入你的选择:")
            # 4 判断用户的选择
            match choose:
                # 5 执行对应的功能
                case "1":
                    self.handle_borrowed_book()
                case "2":
                    self.handle_return_book()
                case "3":
                    self.show_borrowed_books()
                case "4":
                    print("感谢使用,么么哒,退出系统")
                    break
                case _:
                    print("输入错误,请重新输入")

    # 3. 定义借书的方法
    def handle_borrowed_book(self):
        # 1 展示所有图书-->遍历books_dict字典并打印图书
        print("所有图书信息如下:")
        for book in self.books_dict.values():
            print("\t",book)
        # 2 接收用户输入的要借的图书编号
        book_id = input("请输入要借的图书编号:")
        # 3 判断编号对应的图书是否存在,如果不存在,就提示错误信息
        if book_id not in self.books_dict:
            print(f"你要借的编号为【{book_id}】的图书不存在!")
            return
        # 4 判断该图书是否已经借过,如果借过,就提示错误信息
        if book_id in self.borrowed_books_dict:
            print(f"你已经借过编号为【{book_id}】的图书,无法再借!")
            return
        # 5 开始借书,调用图书的方法扣库存。
        book = self.books_dict[book_id] #获取要借的图书
        is_success = book.decrease_stock() # 扣库存
        # 6 判断借书是否成功,如果成功了,将借的图书保存到borrowed_books_dict,打印对应的提示信息
        if is_success:
            # 将借的图书保存到borrowed_books_dict
            self.borrowed_books_dict[book_id]=book
            print(f"你已成功借阅编号为【{book_id}】的图书!")
        else:
            print(f"编号为【{book_id}】的图书库存不足,无法借阅!")

    # 4. 定义还书的方法
    def handle_return_book(self):
        # 1 展示我借的图书
        self.show_borrowed_books()
        # 2 输入要归还的图书编号
        book_id = input("请输入要归还的图书编号:")
        # 3 判断该编号的图书是否借过,如果没有借过,给出提示信息
        if book_id not in self.borrowed_books_dict:
            print(f"你没有借过编号为【{book_id}】的图书!")
            return
        # 4 调用图书方法,归还图书,加库存
        book = self.borrowed_books_dict[book_id] # 获取借的图书
        book.increase_stock() # 用图书方法,加库存
        # 5 将图书从"我的图书"字典中移除
        del self.borrowed_books_dict[book_id]
        print(f"你已成功归还编号为【{book_id}】的图书!")

    # 5. 展示我借的图书
    def show_borrowed_books(self):
        # 1 判断用户借的图书的字典是否为空,如果为空提示错误信息
        if not self.borrowed_books_dict:
            print("你还没有借阅任何图书!")
            return
        # 2 遍历用户借的图书的字典并打印
        print("你借阅的图书如下:")
        for book in self.borrowed_books_dict.values():
            print("\t",book)

#-------------------启动程序--------------------
if __name__=="__main__":
    # ls = LibrarySystem()
    # ls.run()
    LibrarySystem().run()  # 创建LibrarySystem对象立马调用run()方法。由上面两行代码合并而来。

面向对象进阶 ​

面向对象的三大特性:封装、继承、多态 。

封装 ​

  • 封装:把某些属性和方法隐藏在类的内部,不允许外部直接访问。一个类只对外暴露必要的属性和方法(功能)。
  • 被隐藏的属性和方法叫私有属性和私有方法,对外暴露的属性和方法叫公共属性和公共方法。

注意

私有的属性和方法只能在类的内部使用; Python中并没有真正的私有机制,约定在私有属性名和方法名前加__(两个下划线)。

  • 语法:
    • 定义私有属性:在__init__方法中:self.__属性名=值
    • 定义私有方法:def __方法名(self):
python
"""
封装:把某些属性和方法隐藏在类的内部,不允许外部直接访问。一个类只对外暴露必要的属性和方法(功能)。
     1. 私有属性: 在属性名前加双下划线__
     2. 私有方法: 在方法名前加双下划线__
注意事项: Python中是没有真正的私有机制 ;
"""


class Car:
    # 类属性 (所有实例对象共享的)
    wheel = 4  # 轮胎数量

    def __init__(self, c_brand, c_name, c_color,c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        # 1. 私有属性: 在属性名前加双下划线__
        self.__ower = c_ower

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # print(f'{self.color}的{self.brand} {self.name} 正在行驶...')
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')
        self.__control_fuel()

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')

    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')
        
    # 如果外部需要获取__ower的值,可以定义一个公共的get_xxx方法返回
    def get_ower(self):
        return self.__ower

if __name__ =="__main__":
    # 测试,创建对象访问属性
    car =Car("BMW","X5","黑色","阳哥")
    # 访问公共属性
    print(car.brand,car.name, car.color)
    # 访问私有属性
    # print(car.__ower) # AttributeError: 'Car' object has no attribute '__ower' 没有__ower属性
    
    # 访问方法
    # 访问公共方法
    car.start()
    car.run()
    car.stop()
    # 访问私有方法
    # AttributeError: 'Car' object has no attribute '__control_fuel'. Did you mean: '_Car__control_fuel'?
    # car.__control_fuel()
    # 在外部通过调用公共方法间接或者私有属性的值
    print(car.get_ower())

私有属性、方法,在类的内部是可以访问的,我们可以通过 self.__owner,self.__control_fuel() 这种形式来访问。 但是私有属性和方法,在外部是无法方法的,此时我们可以在类的内部定义一个公共的方法,在内部的公共方法中再来访问私有的属性 或 方法,这样我们将来调用这个公有的方法,就可以访问到私有的属性 或 方法了,类似于上述的 get_owner 方法。

提示

  • Python中是没有真正的私有化机制的
  • 我们在类中定义的私有属性、私有方法名,其实最终的属性名和方法名,被自动改写了,使我们不能通过原来的名字调用而已 。
  • Python 对以 双下划线 __ 开头 的属性 / 方法,会自动执行 名称改写(name mangling)
    • __属性名 → _类名__属性名
    • __方法名 → _类名__方法名
    • 实际的属性名、方法名规则为:_类名 + 私有属性名/私有方法名,比如:_Car__owner,_Car__control_fuel()

继承 ​

描述的是让两个类产生父子关系,子类就可以获取到父类非私有的属性和方法,提高代码复用性。

比如Car描述的是汽车类,而无论是燃油车FuelCar,还是电车 ElectricCar ,其实也都算是汽车 Car 这个类型的,都有 Car 中公有的一些属性和方法 。因此呢,我们在设计类的时候,就可以让FuelCar与ElectricCar都继承自Car这个类,那么Car这个类中非私有的属性和方法都会直接继承下来,就不用在FuelCar、ElectricCar中再定义了。

而在这种继承关系中,Car 称之为父类,FuelCar、ElectricCar称之为子类,子类继承父类后,就可以获取到父类非私有的属性和方法,这样就可以大大简化我们代码的书写。

父类Car:

python
"""
  继承:描述的是让两个类产生父子关系,子类就可以获取到父类非私有的属性和方法,提高代码复用性。
  语法:
    class 子类(父类):
"""
class Car:
    def __init__(self, c_brand, c_name, c_color,c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        self.__ower = c_ower # 私有属性

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')
        
    # 私有方法
    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')
        
        # 定义方法,返回私有属性的值
    def get_ower(self):
        return self.__ower

子类FuelCar:

python
# 定义油车类,继承Car类  class 子类(父类):
class FuelCar(Car):
    def __repr__(self):
        # 演示私有方法是否被继承下来?
        # AttributeError: 'FuelCar' object has no attribute '_FuelCar__control_fuel'. Did you mean: '_Car__control_fuel'?
        # 报错:说明父类的__control_fuel私有方法没有被继承下来
        # self.__control_fuel()

        # 演示私有属性是否被继承下来?
        # AttributeError: 'FuelCar' object has no attribute '_FuelCar__ower'. Did you mean: '_Car__ower'?
        # 报错:说明父类的__ower私有属性没有被继承下来
        # return f"品牌:{self.brand} 名称:{self.name} 颜色:{self.color} 所有者:{self.__ower}"
        return f"品牌:{self.brand} 名称:{self.name} 颜色:{self.color} 所属者:{self.get_ower()}"

在FuelCar(Car)中括号里面写的就是父类的类名,子类会自动继承父类的非私有属性和方法,也就意味着子类现在已经有了color、brand、model这些属性了,也拥有了start、stop、run这些方法了。

子类ElectricCar:

python
# 定义电车类,继承Car类  class 子类(父类):
class ElectricCar(Car):
    pass

在ElectricCar(Car)中括号里面写的就是父类的类名,子类会自动继承父类的非私有属性和方法,也就意味着子类现在已经有了color、brand、model这些属性了,也拥有了start、stop、run这些方法了。

创建子类对象,调用方法测试:

python
# 创建子类对象,访问属性和方法
fc = FuelCar("BMW","X5","黑色","阳哥")
# 访问继承下来的公共方法
fc.start()
fc.run()
fc.stop()
# 打印对象
print(fc)  # 会自动执行__repr__魔法方法

完整代码演示:

python
"""
  继承:描述的是让两个类产生父子关系,子类就可以获取到父类非私有的属性和方法,提高代码复用性。
  语法:
    class 子类(父类):
"""
class Car:
    def __init__(self, c_brand, c_name, c_color, c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        self.__ower = c_ower  # 私有属性

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')

    # 私有方法
    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')
        
    # 定义方法,返回私有属性的值
    def get_ower(self):
        return self.__ower

# 定义油车类,继承Car类  class 子类(父类):
class FuelCar(Car):
    def __repr__(self):
        # 演示私有方法是否被继承下来?
        # AttributeError: 'FuelCar' object has no attribute '_FuelCar__control_fuel'. Did you mean: '_Car__control_fuel'?
        # 报错:说明父类的__control_fuel私有方法没有被继承下来
        # self.__control_fuel()

        # 演示私有属性是否被继承下来?
        # AttributeError: 'FuelCar' object has no attribute '_FuelCar__ower'. Did you mean: '_Car__ower'?
        # 报错:说明父类的__ower私有属性没有被继承下来
        # return f"品牌:{self.brand} 名称:{self.name} 颜色:{self.color} 所有者:{self.__ower}"
        return f"品牌:{self.brand} 名称:{self.name} 颜色:{self.color} 所属者:{self.get_ower()}"

# 定义电车类,继承Car类  class 子类(父类):
class ElectricCar(Car):
    pass

# 创建子类对象,访问属性和方法
fc = FuelCar("BMW","X5","黑色","阳哥")
# 访问继承下来的公共方法
fc.start()
fc.run()
fc.stop()
# 打印对象
print(fc)  # 会自动执行__repr__魔法方法

重写 ​

是指子类继承父类后,如果父类中的方法不满足需求,可以在子类中重新定义父类中已有的方法(方法名相同),从而用子类的实现替换父类的实现。语法如下:

python
"""
继承: 描述的是两个类之间的关系, 子类继承父类, 就可以获取到父类中的属性和方法 (非私有)
重写: 是指子类继承父类后,如果父类中的方法不满足需求,可以在子类中重新定义父类中已有的方法(方法名相同),从而用子类的实现替换父类的实现。
"""
# 定义父类
class Car:
    def __init__(self, c_brand, c_name, c_color,c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        self.__ower = c_ower # 私有属性

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')
    # 私有方法
    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')

    def charge(self):
        print(f'{self.brand} {self.name} 正在补充燃料...')

# 定义子类,继承Car,重写charge方法
class FuelCar(Car):
    def charge(self):
        print(f"{self.brand} {self.name} 正在加油!")

# 创建子类对象,调用charge方法
fc = FuelCar("BMW","X5","黑色","阳哥")
fc.charge()  # 子类有重写就执行重写的方法

如果在子类重写父类的方法时,需要调用父类的方法,可以通过 父类名.方法名(self) / super().方法名() 方式来调用。如下:

python
"""
继承: 描述的是两个类之间的关系, 子类继承父类, 就可以获取到父类中的属性和方法 (非私有)
重写: 是指子类继承父类后,如果父类中的方法不满足需求,可以在子类中重新定义父类中已有的方法(方法名相同),从而用子类的实现替换父类的实现。
"""
# 定义父类
class Car:
    def __init__(self, c_brand, c_name, c_color,c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        self.__ower = c_ower # 私有属性

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')
    # 私有方法
    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')

    def charge(self):
        print(f'{self.brand} {self.name} 正在补充燃料...')

# 定义FuelCar子类(油车)
class FuelCar(Car):

    def charge(self):
        # 访问父类中的charge()方法
        # 方式一: super().方法名()
        # super().charge()

        # 方式二: 类名.方法名(self)
        Car.charge(self)
        print(f'{self.brand} {self.name} 正在加油!')

# 定义ElectricCar子类(电车)
class ElectricCar(Car):

    def charge(self):
        Car.charge(self)
        print(f'{self.brand} {self.name} 正在充电...')

if __name__ == "__main__":
    car = FuelCar("BMW","X5","黑色","阳哥")
    car.charge()

总结

  1. 什么是方法重写? 重写的语法?

    1. 在继承体系中,如果父类的方法不能满足需求,就可以在子类中重新定义一个与父类同名的方法,从而用子类自己的实现来替换父类的实现,这个就称之为重写
  2. 在子类中,如何调用父类中的方法?

    1. 方式一:super().方法名()
    2. 方式二:父类名.方法名(self)

多继承 ​

多继承 指的是一个子类,同时继承了多个父类的情况(会将多个父类中的非私有的属性和方法都继承下来)。

python
class 子类名(父类名1, 父类名2, 父类名3, ...):
    代码
    ...

可以看到,如果一个子类继承了多个父类,多个父类的类名只需要逗号分隔即可。

python
"""
代码案例:
比如现在有一个汽车类Car,里面维护的是汽车的基本信息(属性、方法),还有一个是华为智驾类HuaweiAiDriving,里面维护的是华为智驾的基本信息(属性、方法),那还有一个类就是问界汽车类 WenJieCar,问界汽车既具有汽车的属性和方法,也具有华为智驾的属性和方法,因此呢,我们可以让问界汽车类 WenJieCar 继承汽车类 Car 和 华为智驾类 HuaweiAiDriving 就可以了。
多继承: 一个子类继承了多个父类。
语法:class 子类(父类,父类,...)
"""
# 定义父类
class Car:
    def __init__(self, c_brand, c_name, c_color,c_ower):
        # 实例属性
        self.brand = c_brand
        self.name = c_name
        self.color = c_color
        self.__ower = c_ower # 私有属性

    def start(self):  # 启动
        print(f'{self.color}的{self.brand} {self.name} 已启动...')

    def run(self):  # 行驶
        # 私有属性和方法可以在类内部被访问
        print(f'{self.__ower}驾驶着{self.color}的{self.brand} {self.name} 正在行驶...')

    def stop(self):  # 停止
        print(f'{self.color}的{self.brand} {self.name} 已停止...')
    # 私有方法
    def __control_fuel(self):
        print(f'{self.color}的{self.brand} {self.name} 油门正在被控制...')

# 华为智驾
class HuaweiAiDriving:
    """华为AI智能驾驶"""
    def __init__(self, version="v1.0"):
        self.version = version

    def run(self):
        print(f'使用华为AI智能驾驶系统{self.version}正在行驶...')
        
# ------------------------------------------------------------------------------------
# 定义问界汽车类,继承Car类和HuaweiAiDriving智驾类
class WenJieCar(Car, HuaweiAiDriving):
    # 父类中的__init__方法无法满足子类需要,所以子类要重写
    def __init__(self, c_brand, c_name, c_color,c_ower, version="v1.0"):
        super().__init__(c_brand, c_name, c_color,c_ower) # 优先访问第一个父类中的__init__方法
        HuaweiAiDriving.__init__(self,version) # 指定访问HuaweiAiDriving类中的__init__方法

    def run(self):
        # 访问父类中的run方法
        Car.run(self)
        HuaweiAiDriving.run(self)

if __name__ == '__main__':
    car = WenJieCar("BMW","X5","黑色","阳哥")
    car.run() #优先访问本类的run()方法,本类没有就访问第一个父类的,第一个父类没有就访问第二个父类的,依次类推

    # 注意:当一个类继承了多个父类时,默认优先使用第一个父类中的同名属性或方法,可以使用 类名.__mro__属性 或 类名.mro() 方法查看调用顺序。结果如下:
    # (<class '__main__.WenJieCar'>, <class '__main__.Car'>, <class '__main__.HuaweiAiDriving'>, <class 'object'>)
    print(WenJieCar.__mro__)
    print(WenJieCar.mro())

注意:

当一个类继承了多个父类时,默认优先使用第一个父类中的同名属性或方法,可以使用 类名.__mro__属性 或 类名.mro() 方法查看调用顺序。(MRO : Method Resolution Order(方法解析顺序))。

多态 ​

多态 是指同一个父类中的方法,在不同的子类里重写后,呈现出不同的形态、行为、表现 。

python
"""
多态 是指同一个父类方法,在不同的子类里重写后,呈现出不同的形态、行为、表现。
应用场景:在定义方法时,形参尽量定义父类类型,调用方法时可以传递任意子类对象。
"""
class Car:
    def charge(self):
        print('正在补充燃料...')

class FuelCar(Car):
    def charge(self):
        print('燃油汽车正在加油 ...')

class ElectricCar(Car):
    def charge(self):
        print('电动汽车正在充电...')

上面我们定义了三个类,Car是父类,还有两个子类,分别是:FuelCar 与 ElectricCar。

python
# 应用场景:在定义方法时,形参尽量定义父类类型,调用方法时可以传递任意子类对象。
def handle_charge(car: Car):
    car.charge() # 将来传递哪个子类对象进来,此处就调用哪个子类对象的charge方法

if __name__ == '__main__':
    # 传递FuelCar对象,handle_charge内部就调用FuelCar对象的charge()方法
    handle_charge(FuelCar()) 
    
    # 传递ElectricCar对象,handle_charge内部就调用ElectricCar对象的charge()方法
    handle_charge(ElectricCar())

然后在这里我们定义了一个函数 handle_charge 补充燃料的函数,而这个函数中我们指定了参数car,这个参数的类型我们指定为父类类型Car。那么将来在调用函数时,可以传递Car类型的对象,也可以传递Car子类类型(FuelCar、ElectricCar)的对象,如果传入的是子类对象,最终调用就是该子类对象对应的charge方法。【同一个调用语句,传入对象不同,触发不同的行为】

完整的案例代码如下:

python
"""
多态 是指同一个父类方法,在不同的子类里重写后,呈现出不同的形态、行为、表现。
应用场景:在定义方法时,形参尽量定义父类类型,调用方法时可以传递任意子类对象。
"""
class Car:
    def charge(self):
        print('正在补充燃料...')

class FuelCar(Car):
    def charge(self):
        print('燃油汽车正在加油 ...')

class ElectricCar(Car):
    def charge(self):
        print('电动汽车正在充电...')
        
# 应用场景:在定义方法时,形参尽量定义父类类型,调用方法时可以传递任意子类对象。
def handle_charge(car: Car):
    car.charge() # 将来传递哪个子类对象进来,此处就调用哪个子类对象的charge方法

if __name__ == '__main__':
    # 传递FuelCar对象,handle_charge内部就调用FuelCar对象的charge()方法
    handle_charge(FuelCar()) 
    
    # 传递ElectricCar对象,handle_charge内部就调用ElectricCar对象的charge()方法
    handle_charge(ElectricCar())

鸭子类型 ​

  • 鸭子类型(Duck Typing) 是指如果它走路像鸭子,叫起来叫鸭子,那么它就是鸭子。
  • 在鸭子类型中,我们关注的是对象的行为(它有什么方法),而不是对象的类型(它是什么类)。
  • 在若类型语言如JavaScript和Python中都有鸭子类型的实现
python
# 鸭子类型 - 多态体现
class Dog:
    def __init__(self, age, name):
        self.age = age
        self.name = name

    def swimming(self):
        print(f'{self.age} 岁的 {self.name} 正在游泳...')

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

    def swimming(self):
        print(f'{self.age} 岁的 {self.name} 正在游泳...')

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

    def swimming(self):
        print(f'{self.age} 岁的 {self.name} 正在游泳...')

注意这三个类,并没有任何的继承关系,但是呢,三个类中都有一个方法 swimming。

python
def go_swimming(duck):
    duck.swimming()


# 测试代码
go_swimming(Duck("唐老鸭", 2))
go_swimming(Dog("旺财", 4))
go_swimming(Pig("佩奇", 1))

那最终在调用 go_swimming 函数的时候,既可以传入Dog对象,也可以传递Duck对象,也可以传递Pig对象 ,因为这三类对象中,都有swimming方法,这就是鸭子类型的特点,关注的是对象的行为(具有什么样的方法),而不是对象的类型。

注意

鸭子类型的优势是不需要存在继承关系,只要对象有相应的方法就能使用。

抽象类 ​

  • 抽象类 是一种不能被实例化的模板类,里面可以有抽象方法(只有声明、没有实现的方法),子类必须重写(实现)所有的抽象方法,才可以使用。
  • 核心作用:
    • 强制规范:规定子类必须实现哪些方法
    • 统一接口:让所有子类保持相同的调用方式
    • 使用抽象类后,忘记实现方法就无法创建对象,把错误提前到开发阶段发现。
  • Python 没有专门的 abstract 关键字,而是用 abc 模块(Abstract Base Classes)实现 。使用抽象类的两个核心:
    • ABC:抽象类的父类
    • @abstractmethod:python装饰器,标记抽象方法(直接将其加在方法上就可以了)

提示

可以把抽象类理解为一张蓝图或合同模板,它规定了子类必须完成的工作(抽象方法)。但它本身不完整,不能被直接用来创建对象,只有子类实现了抽象方法后,才可以创建子类对象。

python
# 从python核心库abc中引入 ABC 这个抽象类的父类, 以及 abstractmethod 装饰器
from abc import ABC, abstractmethod

# 2、定义会员父类Member类,定义借书、还书、我的借阅逻辑
# 抽象类: 是一种只能被继承, 不能被直接实例化的类, 作用就是规定子类必须要实现哪些方法, 强制子类必须遵守统一的代码规范
# Python中的抽象类, 需要继承 abc 模块中的ABC类 ---> ABC: Abstract Base Class
# 会员类
class Member(ABC): # ABC是Abstract Base Classes简写,表示抽象基类
    def __init__(self, member_id, name, password):
        self.member_id = member_id
        self.name = name
        self.password = password
        # 保存我借的图书
        self.borrowed_books_dict: dict[str, Book] = {}

    # 定义一个借阅书籍最大数量的方法,子类需要重写
    @abstractmethod  #装饰器,给方法做标记/配置,这是告诉python解释器,这个方法是一个抽象方法,子类必须重写,否则子类无法被创建对象。
    def get_max_borrowed_num(self) -> int:
        pass


class NormalMember(Member): 
    # 必须重写方法,返回普通会员的最多可借数量
    def get_max_borrowed_number(self): 
        return 4


class VipMember(Member):
    def __init__(self, member_id, name, password, vip_level):
        Member.__init__(self, member_id, name, password)
        self.vip_level = vip_level

    # 必须重写方法,返回普通会员的最多可借数量
    def get_max_borrowed_number(self):
        return 6 + self.vip_level

接下来,我们再进行测试,就会发现,由于Member类是一个抽象类,是不完整的,我们是无法创建Member类对象的。 而如果子类继承了Member这个父类,如果子类不重写父类中的抽象方法,子类也是无法进行实例化的。那这样,我们就从代码层面,做了强制性的约束,要求开发人员一旦子类继承了Member,就必须要实现Member中的抽象方法,这样就会使我们的代码更加严谨,更加规范。