一、多重继承:一个类为什么可以有多个"爸爸"?
上一篇文章,我们学习了继承。
例如:
class Animal: def eat(self): print("动物都会吃东西")创建狗类:
class Dog(Animal): pass这时候:
dog = Dog() dog.eat()输出:
动物都会吃东西是不是很简单?Dog 继承了 Animal,所以自动拥有了eat()方法,这种继承方式叫做:
单继承
也就是说:
一个子类只有一个父类。
关系可以画成这样:
Animal │ │ Dog1.1 那如果一个类想同时拥有两种能力怎么办?
来看一个例子: 假设现在开发一款游戏,游戏里有很多角色,有的角色会跑:
class Run: def run(self): print("我会跑")有的角色会飞:
class Fly: def fly(self): print("我会飞")现在我们想创建一个超级英雄:他既会跑又会飞,怎么办?如果按照以前的方法,是不是得重新写一遍?
class SuperMan: def run(self): print("我会跑") def fly(self): print("我会飞")当然可以。但是这样做有一个问题,如果以后:
会游泳 会隐身 会发射激光 会瞬移每增加一种能力了,都得重新复制代码。
程序员最讨厌的就是:
复制、粘贴、复制、粘贴……
因为复制的时候很开心,后面改代码的时候就会发现:
哪里都要改,非常痛苦!!
1.2 Python 提供了多重继承
Python 可以同时继承多个父类。
例如:
class Run: def run(self): print("我会跑") class Fly: def fly(self): print("我会飞") class SuperMan(Run, Fly): pass创建对象:
hero = SuperMan() hero.run() hero.fly()输出:
我会跑 我会飞有没有发现?SuperMan并没有写:
run() fly()但是却可以直接调用,因为它同时继承了:
- Run
- Fly
所以两个类的方法都拥有了。
1.3 为什么叫"多重继承"?
因为,以前:
一个爸爸 │ ▼ 儿子现在变成了:
Run Fly │ │ \ / \ / SuperMan一个类可以同时学习多个父类的能力,这就是:
多重继承。
1.4 多重继承什么时候会用到?
举一个现实中的例子:假设开发一个机器人。
机器人,可以说话:
class Speak:可以跳舞:
class Dance:可以拍照:
class Camera:如果需要"会说话还能拍照" 的机器人。直接:
class Robot(Speak, Camera):是不是比重新写一遍方便得多?所以:
多重继承最大的作用就是复用代码。
1.5 多重继承有没有缺点?
当然有,假设两个父类都有同一个方法:
class A: def hello(self): print("A")class B: def hello(self): print("B")子类:
class C(A, B): pass执行:
c = C() c.hello()输出:
A为什么不是 B?因为 Python 有自己的查找顺序,它会按照一定规则寻找方法。
对于初学者来说,不用去死记这个规则,只需要知道:
如果多个父类有同名方法,Python 会按照继承顺序查找。
所以实际开发中不要随便使用多重继承,否则后期自己都会绕晕。
1.6 Python 为什么还能支持多重继承?
很多编程语言,例如 Java并不支持类的多重继承。为什么?因为继承关系太复杂,容易产生歧义。
Python 之所以支持,是因为它设计了一套完整的方法查找规则(MRO),不过这属于比较深入的知识。对于初学者来说,暂时知道:
Python 可以支持多个父类即可。
以后学习框架的时候,自然会接触到。
本节小结
多重继承就是:
一个类,同时继承多个父类。
它最大的优点:
✅ 复用代码。
✅ 一个类拥有多个能力。
但也不要滥用,因为继承关系太复杂,维护起来会越来越困难。
对于初学者来说,记住一句话:
能用单继承解决,就不要为了炫技写多重继承。
二、定制类:让自己的对象变得和 Python 内置对象一样聪明
不知道大家有没有想过一个问题。为什么列表可以这样写:
nums = [1, 2, 3] print(len(nums))字符串也可以:
print(len("Python"))甚至字典:
data = {"name": "张三"} print(len(data))为什么它们都支持:
len()再比如,打印对象:
print(nums)输出:
[1, 2, 3]为什么打印自己写的类:
print(student)却变成:
<__main__.Student object at 0x000001F4...>这到底是为什么,答案就是:
Python 内置对象实现了很多特殊方法。
如果我们自己也实现这些方法,自己的对象也会拥有这些能力。
这就是:
定制类。
简单来说:
按照 Python 的规则,给自己的类增加特殊能力。
2.1 第一个魔法方法:__str__()
先来看一个例子。
class Student: def __init__(self, name): self.name = name创建对象:
s = Student("张三") print(s)输出:
<__main__.Student object at 0x000001F4...>很多小伙伴第一次看到这里都会说:
这是什么乱码?
其实这不是乱码,这是 Python 默认打印出来的对象信息。对于程序来说没有问题,但是对于人来说,几乎没有任何意义。
2.2 怎么让它变好看?
只需要重写:
__str__()例如:
class Student: def __init__(self, name): self.name = name def __str__(self): return f"学生姓名:{self.name}"再次打印:
s = Student("张三") print(s)输出:
学生姓名:张三是不是一下舒服多了?以后只要:
print(对象)Python 就会自动调用:
__str__()2.3 __repr__()又是什么?
很多同学会发现,网上还有:
__repr__()它和:
__str__()长得几乎一样,有什么区别?
简单来说:
__str__():给用户看的。__repr__():给程序员看的。
对于初学者来说,很多时候可以直接让:
__repr__ = __str__这样两个效果一样,等以后接触大型项目再深入学习即可。
2.3 __len__():让对象支持 len()
大家都知道:
len([1,2,3])输出:
3其实,Python 背后调用的是:
__len__()所以我们自己的对象也可以。
class Student: def __len__(self): return 10创建对象:
s = Student() print(len(s))输出:
10是不是很神奇?以后只要别人调用:
len(对象)Python 就会自动执行:
__len__()2.4 __call__():对象也能像函数一样调用
函数:
print()可以直接:
print()但是对象呢?正常情况下:
s()会报错,如果实现:
class Student: def __call__(self): print("对象被调用了")那么:
s = Student() s()输出:
对象被调用了是不是很神奇?对象居然可以像函数一样调用。
2.5 定制类到底是在定制什么?
很多小白看到这里可能会觉得:魔法方法越来越多。
其实不用紧张,它们本质上都是同一个思想:
告诉 Python:"如果别人这样操作我的对象,你应该执行什么代码。"
例如:
print(对象)→ 调用__str__()len(对象)→ 调用__len__()对象()→ 调用__call__()
这就是所谓的定制类,你不是在学习一堆新的语法,而是在学习如何让自己的对象,拥有和 Python 内置对象一样的能力。
本节小结
定制类并不是一个新的类,而是通过实现一些魔法方法,让自己的对象支持更多操作。
本节我们认识了几个最常用的方法:
| 魔法方法 | 作用 |
|---|---|
__str__() | 自定义打印对象时显示的内容 |
__repr__() | 对象的开发者表示形式 |
__len__() | 让对象支持len() |
__call__() | 让对象可以像函数一样调用 |
对于初学者来说,不需要把所有魔法方法都记住,记住一句话就够了:
Python 很多看似神奇的功能,背后其实都是这些魔法方法在默默工作。
三、使用枚举类:固定的数据,别再用字符串乱写了
相信大家平时写代码的时候,经常会这样定义一些数据。
例如表示性别:
gender = "男"或者:
gender = "女"看起来没有任何问题。再比如表示订单状态:
status = "待付款"付款之后:
status = "已付款"发货以后:
status = "已发货"最后:
status = "已完成"很多刚学习 Python 的同学都会这样写。因为简单直接,容易理解。但是,如果项目越来越大,就会慢慢出现问题。
3.1 字符串真的安全吗?
来看一个例子,假设我们判断订单是否已经付款。
status = "已付款" if status == "已付款": print("开始发货")没有问题,但是如果某一天,你不小心写成了:
status = "以付款"注意这里不是:
已付款而是:
以付款只差了一个字,程序不会报错,但是:
if status == "已付款":条件永远不成立,程序也不会提醒你。因为对于 Python 来说,它只是一个普通字符串,是真是假它不知道。
3.2 如果一个项目有几百个字符串呢?
例如,订单状态:
待付款 已付款 已发货 运输中 已签收 已完成如果每个人都自己写,可能出现:
已付款 付款成功 已经付款 支付成功虽然表达的是同一个意思,但是程序根本不知道,这样就很容易产生 Bug。
怎么办?Python 提供了一个更加规范的方法:
枚举类(Enum)
3.3 什么是枚举类?
一句话理解:
把固定不变的选项,统一管理起来。
例如:
一年有四季。
交通灯只有三种颜色。
一周只有七天。
订单状态只有几个固定值。
这些都是:
枚举。
也就是说,数量固定,不会随便增加。
3.4 创建一个枚举类
Python 内置了:
Enum我们先导入:
from enum import Enum创建一个性别枚举:
from enum import Enum Gender = Enum("Gender", ("男", "女"))现在我们不能随便写字符串了,而是这样使用:
print(Gender.男)输出:
Gender.男如果获取值:
print(Gender.男.value)输出:
1再看:
print(Gender.女.value)输出:
2Python 会自动给每一个成员分配一个编号。当然,编号一般不是重点。
真正重要的是:
以后所有地方都统一使用Gender.男、Gender.女,而不是随便写字符串。
3.5 为什么要用枚举?
还是拿交通灯举例,以前:
light = "红灯"现在:
Light.RED看到这里很多同学会觉得,"写字符串不是更简单吗?"刚开始确实如此,但是如果一个项目有几十万行代码,到处都是:
红灯 红色 停止 stop你根本不知道哪个才是正确的,而枚举只有一种写法,大家统一使用,代码会更加规范。
3.6 实际开发中哪些地方经常使用枚举?
枚举在项目中非常常见。例如,订单状态:
待付款 已付款 已发货 已完成用户身份:
管理员 普通用户 游客星期:
星期一 星期二 …… 星期日交通灯:
红灯 黄灯 绿灯游戏角色职业:
战士 法师 射手 辅助这些固定的数据都非常适合使用枚举类。
本节小结
枚举类就是:
把固定的数据统一管理起来。
它最大的优点就是:
- 不容易写错。
- 代码更加规范。
- 大型项目更容易维护。
以后只要看到:
Enum你就可以理解成:
这是一个固定选项的集合。
四、元类:类到底是谁创建出来的?
终于来到第六章最后一个知识点,也是很多人觉得最神秘的一个知识点。
元类(Metaclass)
网上很多教程一上来就是:
class MyMeta(type):相信很多同学看到这里,直接关闭网页。因为根本不知道在写什么。其实,元类并没有那么可怕。
我们先回顾一下之前学习过的内容。
4.1 第一步:对象是谁创建的?
上一篇文章,我们一直在写:
class Student: pass创建对象:
s = Student()这里:
Student就是一个类,而:
s就是对象,所以对象是谁创建出来的?答案当然是:
类。
关系如下:
类 │ │ 创建 ▼ 对象这一点大家应该都能理解。
4.2 第二步:那类是谁创建出来的?
很多同学可能会回答:
Python 创建的。
回答没错。但是更准确一点,应该是:
元类创建的。
也就是说,实际上还有这样一层关系:
元类 │ │ 创建 ▼ 类 │ │ 创建 ▼ 对象是不是一下子清楚多了?
4.3 元类可以理解成"造类的工厂"
举个生活中的例子。
假设一家汽车工厂,它的流程是这样的:
第一步:工程师设计图纸。
第二步:工厂根据图纸生产汽车。
关系如下:
图纸 │ ▼ 汽车Python 也类似,不过多了一层。
元类 │ ▼ 类 │ ▼ 对象如果把对象比作汽车,类就是汽车图纸。元类就是制造图纸的工厂。
所以很多人说:
元类就是创建类的类。
虽然听起来有点绕,但是意思就是:
专门负责创建类。
4.4 那我们什么时候会写元类?
答案是:几乎不会。
是不是有点意外?事实上,对于绝大多数 Python 开发者来说。
工作几年,可能都不会自己写一个元类。因为Python 已经帮我们做好了。
例如,当我们写:
class Student: pass实际上。
Python 背后已经使用默认元类:
type帮我们创建好了这个类。所以我们平时根本感觉不到它的存在。
4.5 那为什么还要学习元类?
因为很多优秀的 Python 框架都会使用元类。
例如:
- Django
- SQLAlchemy
- Django ORM
- 一些大型框架
虽然你不会自己写,但是以后阅读源码的时候经常会遇到:
metaclass=如果不知道元类是什么就会一脸懵,所以学习元类主要目的是:
知道它是什么。
而不是:
必须会写。
本节小结
元类只有一句话需要记住:
对象是类创建的,类是元类创建的。
对于初学者来说知道这一点就已经足够了,等以后学习框架源码再深入学习也不迟。
全文总结
恭喜你!如果能够坚持看到这里,说明你已经掌握了 Python 面向对象编程的大部分进阶知识。
这次我们学习了六个重要知识点。
__slots__
限制对象可以拥有的属性,让代码更加规范,还能节省一定内存。
@property
把方法变成属性,既保证数据安全,又让代码更加优雅。这是实际开发中使用频率非常高的一个知识点。
多重继承
一个类可以同时继承多个父类,实现代码复用,不过不要为了炫技而滥用。
定制类
通过实现魔法方法,让自己的对象支持:
print()len()对象()
等各种操作。让对象拥有和 Python 内置对象一样的能力。
枚举类
适合管理固定的数据。
例如:
- 星期
- 性别
- 订单状态
- 用户角色
让代码更加规范,减少因为字符串写错而导致的 Bug。
元类
知道即可,记住一句话:
元类创建类,类创建对象。
以后学习 Django、Flask、ORM 等框架时,你还会再次遇到它。
不知不觉,我们已经从最开始的变量、列表、字典,一路学习到了 Python 面向对象编程的进阶内容。你可能会发现,现在学的很多知识,平时写几十行小程序未必都能用到。但它们存在的意义,并不是为了让代码变得更复杂,而是为了让程序在规模越来越大、参与开发的人越来越多时,依然能够保持清晰、规范、易维护。学习编程就像搭积木,基础知识决定你能不能把积木搭起来,而这些进阶知识,则决定你的积木能搭多高、搭得稳不稳。
希望通过这一系列文章,你不仅学会了 Python 的语法,更能够逐渐理解 Python 的编程思想。当以后再次看到@property、__slots__、Enum或者元类时,不会再觉得陌生,而是能够明白它们为什么会存在,又该在什么场景下使用。