type
status
date
slug
summary
tags
category
icon
password
📝 三大特性—继承补充
1、MRO
1.1、相关概念
MRO 的全称是 Method Resolution Order,即方法解析顺序。
当你在一个对象上调用一个方法(例如
obj.method()
) 时,Python 需要沿着继承链向上搜索,以确定应该调用哪个类中的方法。这个搜索的顺序就是 MRO。对于简单的单继承,这非常直观:从子类到父类,一路向上。
1.2、核心问题
多重继承的歧义 当引入多重继承后,继承图不再是简单的“链”,而可能是一个复杂的“图”,甚至是一个“菱形”。这就产生了歧义:应该以何种顺序搜索父类?
在这个经典的“菱形继承”问题中,搜索顺序可以是:
D -> B -> A -> C -> A
(深度优先,但重复访问 A 是糟糕的)
D -> B -> C -> A
(广度优先)
- 或者其他顺序…
2、C3 算法详解
2.1、相关概念
C3 算法的核心是为一个类生成一个线性化(MRO 列表),这个线性化需要满足以下两个关键约束:
- 继承顺序规则 (局部优先顺序):子类继承列表中父类的顺序被保留。
- 单调性规则 (子类优先于父类):子类的 MRO 必须包含所有父类的 MRO,并保持其内部顺序。
2.2、C3 算法的步骤与公式
C3 算法的操作可以描述为一个递归的合并过程。对于一个类
C
,其继承列表为 [B1, B2, ..., BN]
,C
的 MRO 计算如下:L[C] = [C] + merge(L[B1], L[B2], ..., L[BN], [B1, B2, ..., BN])
这里的
L[Cls]
代表类 Cls
的 MRO 列表,merge
是一个特殊的合并函数。2.3、merge
操作规则
merge
函数接收多个列表(线性化)作为参数,并按以下规则合并它们:- 检查第一个列表的第一个元素。
- 如果这个元素没有出现在任何其他列表的非首位,那么它就是一个“好头”,可以将其从所有列表中移除,并添加到输出结果中。
- 如果这个元素出现在其他列表的非首位,则跳过它,去检查下一个列表的第一个元素。
- 重复这个过程,直到所有列表都被耗尽。
- 如果找不到符合规则的“好头”,则算法无法构建一个一致的线性化,Python 会抛出
TypeError
(这意味着类的继承 hierarchy 本身就有问题)。
📝 内置函数补充
1、callable
1.1、基本介绍
callable()
函数用于检查一个对象是否可以被调用(即是否是可调用对象)。如果对象可以被调用,返回 True
,否则返回 False
。基本语法
1.2、什么是可调用对象?
在 Python 中,可调用对象是指那些可以使用括号
()
来调用的对象。主要包括:- 函数(内置函数、自定义函数)
- 方法(实例方法、类方法、静态方法)
- 类(调用类会创建实例)
- 实现了
__call__
方法的对象
- 生成器函数(但生成器本身不可调用)
- 某些内置类型(如
type
,len
等)
1.3、基本用法和示例
1. 检查函数
2. 检查方法
3. 检查类
4. 检查普通对象和不可调用对象
1.4、实现 __call__
方法的可调用对象
任何实现了
__call__
方法的类的实例都是可调用对象。1. 基本示例
2. 带状态的函数(函数对象)
3. 装饰器类
1.5、实际应用场景
1. 动态调用检查
2. 插件系统和回调机制
3. 配置驱动的函数调用
4. 验证回调函数
1.6、特殊情况和边界案例
1. 生成器函数 vs 生成器对象
2. 内置类型和特殊对象
3. 模块对象
4. 部分函数(functools.partial)
1.7、callable()
的内部实现
1. 检查
__call__
方法的存在callable()
函数本质上检查对象是否有 __call__
方法:2. 实际Python实现更复杂
实际的
callable()
实现需要考虑更多边界情况,比如:- 描述符协议
- 元类
- 特殊的内置类型
1.8、与相关函数的比较
1.
callable()
vs hasattr(obj, '__call__')
2.
callable()
vs isinstance(obj, collections.abc.Callable)
推荐使用
callable()
,因为它更简洁且性能更好。1.9、最佳实践和注意事项
1. 防御性编程
2. 在框架开发中的使用
3. 性能考虑
callable()
是一个很快的操作,通常比实际的函数调用要快得多。1.10、小结
callable()
的核心价值:- 类型检查:快速确定对象是否可以被调用
- 防御性编程:在调用前验证对象的可调用性
- 动态编程:支持插件系统、回调机制等动态行为
- 框架开发:构建灵活可扩展的架构
可调用对象的类型:
- ✅ 函数(内置、自定义)
- ✅ 方法(实例、类、静态)
- ✅ 类
- ✅ 实现了
__call__
的对象
- ✅ 部分函数(functools.partial)
- ❌ 基本数据类型(int, str, list, dict等)
- ❌ 模块
- ❌ 生成器对象
使用建议:
- 在需要动态调用对象前,总是使用
callable()
进行检查
- 对于回调函数和插件系统,强制要求可调用性
- 使用
__call__
方法创建有状态的函数对象
- 在框架开发中充分利用可调用对象的灵活性
callable()
是一个简单但极其有用的函数,它体现了 Python 的动态特性,是编写灵活、健壮代码的重要工具。2、super
它返回一个 代理对象,让你 跳过当前类,去访问 方法解析顺序(MRO)中下一个类的同名属性/方法。
一句话:在继承链里,“借”父类的实现,却 不必硬编码父类名。
2.1、super()
的基本语法
1. 两种使用形式
2. 参数说明
- 无参数形式:
super()
- 自动推断当前类和实例
- 双参数形式:
super(type, obj)
type
:要查找的起始类obj
:要绑定的实例或类
2.2、super()
的工作原理:方法解析顺序(MRO)
1. 什么是 MRO?
MRO(Method Resolution Order)是 Python 中确定方法调用顺序的规则。
2.
super()
按照 MRO 顺序查找方法- 子类永远在父类之前
- 父类出现顺序与写继承列表时一致
- 每个类仅出现一次,可以用
Class.__mro__
查看:
super
就是 沿着这条链 找“下一个”类。2.3、super()
的使用场景
1. 单继承中的使用
2. 多继承中的使用(菱形继承问题)
最经典用例: Cooperative Multiple Inheritance
MRO:
Contact → Person → Email → Phone → object
每个
super().__init__(**kw)
只负责自己关心的参数,最终所有父类都被初始化一次且仅一次。硬编码
Person.__init__(...)
无法做到这一点。3. 调用特定的父类方法
2.4、super()
的高级用法
1. 在类方法中使用
super()
2. 在静态方法中使用
super()
3. 使用
super()
访问属性2.5、实际应用场景
1. 框架开发中的插件系统
2. Django 模型继承
3. 自定义异常类
2.6、常见陷阱和最佳实践
1. 不要忘记调用
super()
2. 确保所有类都协作使用
super()
3. 在多重继承中谨慎设计
2.7、super()
的内部机制
1.
super
对象的结构2. 手动创建
super
对象2.8、小结
super()
的核心价值:- 避免硬编码:不直接使用父类名,提高代码的可维护性
- 支持协作多重继承:按照 MRO 顺序正确调用方法
- 提供一致的接口:统一的方式访问父类功能
使用建议:
- 在 Python 3 中:使用无参数形式
super()
- 确保协作:在继承体系中所有相关方法都使用
super()
- 理解 MRO:了解方法解析顺序,避免意外的调用顺序
- 谨慎设计多重继承:使用 Mixin 模式,确保类之间的协作
适用场景:
- 重写方法但需要保留父类功能时
- 构建框架和库时提供扩展点
- 实现模板方法模式
- 处理多重继承的复杂场景
super()
是 Python 面向对象编程中非常重要的工具,正确理解和使用它可以帮助你编写出更加健壮和可维护的代码。3、type
type()
函数是 Python 中最重要的内置函数之一,它主要有两个用途:- 获取对象的类型(查询功能)
- 动态创建类(元编程功能)
3.1、用法一:获取对象的类型(查询功能)
基本语法
1. 基本使用:查看对象类型
2. 类型检查的实际应用
3. 与
isinstance()
的区别选择建议:
- 用
type()
当你需要精确的类型匹配
- 用
isinstance()
当你需要考虑继承关系
3.2、用法二:动态创建类(元编程功能)
基本语法
name
:字符串,类的名称
bases
:元组,继承的父类
dict
:字典,类的命名空间(属性和方法)
1. 基本示例:动态创建类
2. 更复杂的动态类创建
3. 使用类装饰器简化
3.3、type
与元类(Metaclass)
1. 理解
type
的本质在 Python 中,
type
本身也是一个类,它是所有类的元类(包括它自己)。2. 自定义元类
3.4、实际应用场景
1. 动态创建数据库模型
2. 插件系统
3. 配置驱动的类生成
4. 测试和Mocking
3.5、高级用法和注意事项
1. 与
__class__
的区别2. 性能考虑
3. 类型检查的最佳实践
3.6、小结
type()
函数是 Python 中极其强大的工具:1. 作为查询函数
- 获取对象类型:
type(obj)
返回对象的类
- 类型检查:用于精确的类型匹配
- 调试工具:帮助理解代码中对象的类型
2. 作为类工厂
- 动态创建类:
type(name, bases, dict)
在运行时创建新类
- 元编程基础:理解 Python 的类创建机制
- 框架开发:用于创建插件系统、ORM、配置驱动开发等
3. 关键点
type
本身是一个元类(metaclass)
- 所有类的类型都是
type
(除了某些特殊情况)
- 动态类创建提供了极大的灵活性,但应谨慎使用
- 在大多数情况下,
isinstance()
比直接比较类型更合适
4. 使用建议
- 日常开发:主要用
type()
进行调试和类型检查
- 框架开发:可以利用动态类创建实现灵活的系统架构
- 元编程:深入理解
type
是掌握 Python 高级特性的关键
type()
函数体现了 Python 的"一切皆对象"哲学,是理解 Python 对象模型和元编程的重要入口点。4、isinstance
4.1、基本介绍
isinstance()
函数用于检查一个对象是否是指定类或类型的实例,或者是否是其子类的实例。它考虑了继承关系,是 Python 类型检查的首选方法。基本语法
object
:要检查的对象
classinfo
:类/类型或包含类/类型的元组
4.2、为什么需要 isinstance()
?
1. 与
type()
的区别2. 处理多种类型检查
4.3、基本用法和示例
1. 检查基本数据类型
2. 检查自定义类的实例
3. 使用元组检查多种类型
4.4、isinstance()
的高级用法
1. 检查抽象基类(ABC)
2. 使用
typing
模块(Python 3.5+)3. 检查模块和函数
4.5、实际应用场景
1. 输入验证和类型检查
2. 多态和接口设计
3. 数据序列化和反序列化
4. 插件系统和扩展机制
4.6、特殊情况和边界案例
1. 虚拟子类(Virtual Subclass)
2. 使用
__instancecheck__
自定义行为3. 处理旧式类(Python 2 风格)
4.7、与相关函数的比较
1.
isinstance()
vs type()
2.
isinstance()
vs issubclass()
3.
isinstance()
vs hasattr()
4.8、性能考虑和最佳实践
1. 性能测试
2. 最佳实践
3. 错误处理模式
4.9、小结
isinstance()
的核心价值:- 继承感知:考虑类继承关系,比
type()
更灵活
- 多类型支持:可以一次检查多种类型
- 抽象支持:支持抽象基类和虚拟子类
- 防御性编程:帮助编写健壮的代码
适用场景:
- ✅ 输入验证和参数检查
- ✅ 多态和接口实现
- ✅ 插件系统和扩展机制
- ✅ 数据序列化和处理
- ✅ 框架和库开发
不适用场景:
- ❌ 需要精确类型匹配时(使用
type()
)
- ❌ 鸭子类型足够时(优先尝试操作而不是检查类型)
- ❌ 性能极度敏感的代码(但差异通常很小)
使用建议:
- 优先选择:在需要类型检查时优先使用
isinstance()
- 组合类型:使用元组一次检查多种类型
- 抽象基类:利用
collections.abc
进行接口检查
- 适度使用:避免过度类型检查,优先使用鸭子类型
isinstance()
是 Python 类型系统中非常重要的工具,正确使用它可以编写出既灵活又健壮的代码。它体现了 Python 的"请求宽恕比请求许可更容易"(EAFP)哲学,但在需要确保类型安全时提供了必要的保障。5、issubclass
5.1、基本介绍
issubclass()
函数用于检查一个类是否是另一个类的子类(派生类)。它检查类之间的继承关系,是 Python 类型系统中非常重要的工具。基本语法
class
:要检查的类
classinfo
:类或包含类的元组
5.2、为什么需要 issubclass()
?
1. 检查类继承关系
2. 与
isinstance()
的区别5.3、基本用法和示例
1. 检查基本数据类型的继承关系
2. 检查自定义类的继承关系
3. 使用元组检查多个父类
5.4、issubclass()
的高级用法
1. 检查抽象基类(ABC)
2. 虚拟子类(Virtual Subclass)
3. 使用
__subclasscheck__
自定义行为5.5、实际应用场景
1. 插件系统验证
2. 工厂模式中的类验证
3. 序列化框架中的类型检查
4. 验证框架中的约束检查
5.6、特殊情况和边界案例
1. 自反性(Reflexivity)
2. 传递性(Transitivity)
3. 多重继承
4. 处理非类参数
5.7、与相关函数的比较
1.
issubclass()
vs isinstance()
2.
issubclass()
vs type()
3.
issubclass()
vs hasattr()
5.8、性能考虑和最佳实践
1. 性能考虑
2. 最佳实践
3. 错误处理模式
5.9、小结
issubclass()
的核心价值:- 继承关系检查:专门用于检查类之间的继承关系
- 类型系统支持:是Python类型系统的重要组成部分
- 框架开发:在插件系统、验证框架中非常有用
- 抽象基类:支持抽象基类和虚拟子类的检查
适用场景:
- ✅ 插件系统和扩展机制的类验证
- ✅ 工厂模式中的类注册和验证
- ✅ 框架开发中的类型约束检查
- ✅ 抽象基类和接口实现验证
- ✅ 序列化和验证框架
不适用场景:
- ❌ 对象类型检查(使用
isinstance()
)
- ❌ 精确类型匹配(使用
type()
)
- ❌ 属性存在性检查(使用
hasattr()
)
使用建议:
- 优先选择:在需要检查类继承关系时使用
issubclass()
- 组合检查:使用元组一次检查多个父类
- 抽象基类:利用抽象基类进行接口检查
- 错误处理:处理可能的
TypeError
异常
- 性能考虑:在性能敏感代码中考虑缓存结果
issubclass()
是 Python 面向对象编程中非常重要的工具,它帮助开发者构建基于继承的灵活架构,确保类型的正确性和一致性。正确使用它可以大大提高代码的健壮性和可维护性。📝 异常处理
1、什么是异常?
异常是指在程序执行过程中发生的一个事件,该事件会中断正常的指令流。当 Python 脚本遇到一个无法处理的情况时,它会“抛出”一个异常。例如:
- 打开一个不存在的文件 (
FileNotFoundError
)
- 用零除一个数 (
ZeroDivisionError
)
- 访问一个不存在的列表索引 (
IndexError
)
- 将一个字符串与一个整数相加 (
TypeError
)
如果不处理这些异常,程序会崩溃并打印一个错误信息(Traceback)。
2、异常处理的基本结构:try...except
try
和 except
语句是处理异常的核心。2.1、基本语法
2.2、工作原理
- 首先,执行
try
子句中的代码。
- 如果没有异常发生,则跳过
except
子句。
- 如果在执行
try
子句时发生了异常,则立即跳转到except
子句。
- 如果发生的异常类型与
except
关键字后面指定的异常匹配(或是其子类),则执行该except
子句。
- 执行完
except
子句后,程序会继续执行try...except
块之后的代码,而不会崩溃。
2.3、一个简单的例子
输出示例 1:
输出示例 2:
输出示例 3:
3、更复杂的异常处理结构
一个完整的
try
语句可以有多个子句。3.1、else
子句
- 可选。必须放在所有
except
子句之后。
- 只有在
try
子句没有发生任何异常时才会执行。
- 用于放置那些依赖于
try
块成功执行的代码,将它们与try
块本身分开,逻辑更清晰。
3.2、finally
子句
- 可选。必须放在所有子句的最后。
- 无论
try
块中是否发生异常,最终都会执行。
- 常用于清理资源,例如关闭文件、释放网络连接等,确保这些操作一定发生。
注意:
finally
的执行时机甚至比 return
语句还早。如果在 try
或 except
块中有 return
,会先执行 finally
块,然后再返回。4、捕获所有异常
4.1、使用空的 except
(不推荐)
except:
会捕获所有异常,包括系统退出请求 (KeyboardInterrupt
, SystemExit
),这通常不是我们想要的,可能会让程序无法用 Ctrl+C
正常终止。4.2、使用 Exception
基类 (推荐)
Exception
是所有内置、用户定义的非系统退出异常的基类。捕获它可以抓到几乎所有你关心的错误。5、获取异常信息
可以使用
as
关键字将捕获的异常赋值给一个变量,从而访问异常的具体信息。输出:
6、主动抛出异常:raise
语句
你可以使用
raise
语句主动触发异常。6.1、基本用法
6.2、重新抛出异常
在
except
块中,你可以使用空的 raise
语句将当前捕获的异常原样继续向上抛出。7、自定义异常
通过创建一个继承自
Exception
类的新类,你可以定义自己的异常类型,以便更精确地描述程序中可能出现的特定错误。8、最佳实践和总结
- 只捕获你能处理的异常:不要用一个大
except Exception
捕获所有异常然后忽略它(俗称“异常吞噬”)。这会让调试变得极其困难。
- 尽量指定具体的异常类型:越具体越好,这样可以针对不同的错误做出不同的处理。
- 保持
try
块精简:只将可能出错的代码放入try
块,避免隐藏其他无关的错误。
- 善用
else
和finally
: else
用于成功后的逻辑,使代码更清晰。finally
用于必须执行的清理动作。
- 使用内置的异常类型:优先使用 Python 内置的异常(如
ValueError
,TypeError
),而不是自己创建,除非你有非常特殊的需要。
- 异常用于处理“异常”情况:不要用异常来控制正常的程序流程(例如,用异常来判断文件是否结束,应该用
if
检查)。异常处理的成本比条件判断高。
📝 反射
1、什么是反射?
反射 是指程序在运行时(Runtime)能够检查、访问、修改自身状态和行为的能力。简单来说,就是通过字符串来操作对象(模块、类、函数、变量等)的成员。
Python 中的反射机制非常强大,它允许你动态地:
- 检查一个对象有哪些属性和方法
- 获取/设置对象的属性值
- 动态调用对象的方法
- 动态导入模块
- 判断对象类型
2、核心内置函数
Python 提供了以下几个内置函数来实现反射功能:
2.1、getattr(object, name[, default])
作用:获取对象中指定名称的属性或方法。
object
:要操作的对象
name
:字符串形式的属性名
default
:可选,如果属性不存在时返回的默认值
2.2、setattr(object, name, value)
作用:设置对象的属性值,如果属性不存在则创建它。
2.3、hasattr(object, name)
作用:检查对象是否包含指定的属性或方法。
2.4、delattr(object, name)
作用:删除对象的指定属性。
2.5、isinstance(object, classinfo)
和 issubclass(class, classinfo)
虽然不是严格的反射函数,但常用于类型检查:
3、dir()
函数
dir()
函数返回对象的所有属性和方法名称列表,是探索对象结构的强大工具4、__dict__
属性
大多数对象都有一个
__dict__
属性,它是一个字典,包含对象的所有实例属性。5、动态导入模块
使用
importlib
模块可以实现动态导入:6、实际应用场景
6.1、插件系统
6.2、Web 框架的路由系统
6.3、配置管理器
6.4、数据验证和转换
7、高级反射:inspect
模块
inspect
模块提供了更强大的内省功能:8、注意事项和最佳实践
- 性能考虑:反射操作比直接访问稍慢,在性能关键代码中慎用。
- 安全性:动态执行代码可能存在安全风险,特别是使用
exec()
或eval()
时。
- 可读性:过度使用反射会降低代码的可读性和可维护性。
- 错误处理:使用反射时要做好异常处理:
- 优先使用直接访问:如果知道具体的属性名,应该优先使用直接的点号访问。
9、总结
Python 的反射机制非常强大,它通过以下几个核心功能实现:
函数/特性 | 作用 | 示例 |
getattr() | 获取属性/方法 | getattr(obj, 'name') |
setattr() | 设置属性 | setattr(obj, 'name', value) |
hasattr() | 检查属性是否存在 | hasattr(obj, 'name') |
delattr() | 删除属性 | delattr(obj, 'name') |
dir() | 列出所有成员 | dir(obj) |
__dict__ | 访问属性字典 | obj.__dict__ |
importlib | 动态导入模块 | importlib.import_module('math') |
反射在以下场景中特别有用:
- 插件系统和扩展机制
- Web 框架的路由和控制器
- 配置管理和数据绑定
- 测试框架和 Mock 对象
- 序列化和反序列化
合理使用反射可以让代码更加灵活和动态,但也要注意不要过度使用,以免影响代码的清晰度和性能。