Python学习历程——Python面向对象编程详解
Python学习历程——Python面向对象编程详解
- 简介
- 注意事项
- 一、类的基本概念与定义
- 1.1 类与实例的基本语法
- 1. class关键字与类定义
- 2. 实例化对象
- 3. Java对比
- 1.2 实例变量与类变量
- 1. 实例变量
- 2. 类变量
- 3. 变量查找顺序(重点:理解类变量和实例变量)
- Java对比
- 二、方法详解
- 2.1 实例方法
- 1. self参数详解
- 2. 方法的定义和调用
- 2.2 类方法(`@classmethod`)
- 1. 类方法的定义和使用
- 2. 类方法的用途
- 3. 和Java的区别
- 2.3 静态方法(`@staticmethod`)
- 三、核心特殊方法(魔术方法)
- 3.1 对象生命周期相关
- 1. `__new__(cls, ...)`:真正的构造函数
- 2. `__init__(self, ...)`:初始化器(非构造函数)
- 3. `__del__(self)`:析构器(了解即可,通常不推荐使用)
- 4. jquery和js中类似的生命周期函数(有点儿意思)
- 3.2 对象表示相关
- 1. `__str__(self)`:面向用户的字符串表示
- 2. `__repr__(self)`:面向开发者的精确字符串表示
- 3. 理解两者的区别
- 3.3 比较与排序相关
- 1. 比较:`__eq__`
- 2. 排序
- 3. 完整示例
- 3.4 属性访问与控制
- 1. 基本的属性访问
- 1. 1 _single_underscore (单下划线前缀): “内部使用”的提示
- 1.2. __double_underscore (双下划线前缀): 名称改写 (Name Mangling)
- 2. "Getter/Setter" 的 Pythonic 方式 (@property装饰器)
- 3. 动态和高级属性控制 (Magic Methods)
- 3.1 `__getattr__(self, name)`
- 3.2 `__getattribute__(self, name)`
- 3.3 `__setattr__(self, name, value)` ——属性赋值守卫(重点)
- 3.4 `__delattr__(self, name):` 属性删除的守卫
- 四、继承与多态
- 4.1 基本继承语法
- 4.2 方法重写 与 super()函数
- 4.3 方法解析顺序(MRO——Method Resolution Order)
- 4.4 `__init_subclass__`钩子(重点)
- 五、组合 、Mixin模式、装饰器
- 5.1 组合
- 5.2 Mixin
- 5.3 装饰器
- 六、鸭子类型与协议
- 6.1 鸭子类型哲学
- 6.2 结构子类型
- 七、高级类特性
- 7.1 抽象基类(ABCs)
- 7.2 数据类(`@dataclass`)(Python 3.7+)
- 7.3 枚举类(Enum)
- 7.4 `__slots__`属性
- 八、元编程基础
- 8.1 元类(Metaclasses)(了解)
- 1. type函数
- 2. 使用type()动态创建类
- 3. 自定义元类来控制类的创建
- 8.2 类装饰器
简介

注意事项
💡 本文还是和之前的风格一致,最贴合Java开发人员,重点知识点会详细讲解。
💡 啊哈哈,要是觉得可以那就点点赞和收藏吧,相信我,质量有那么那么高。
一、类的基本概念与定义
1.1 类与实例的基本语法
1. class关键字与类定义
class Person:"""一个简单的Person类"""pass # 使用pass表示一个空类块
- Python中类的命名也采用大驼峰命名,但是方法是蛇形命名。
- Python中类本身也是一个对象,是
type类的实例。
思考:如何理解
类本身是type类的实例?
💡 最佳答案:这是Python元编程的核心基础。
class Person:pass# 实例化关系
person_instance = Person() # person_instance 是 Person 类的实例
print(isinstance(person_instance, Person)) # True# 关键的元关系
print(isinstance(Person, type)) # True - Person类本身是type类的实例
print(type(Person)) # <class 'type'>
关系图
person_instance → Person类 → type类对象 对象 元类
类创建过程的本质
首先要注意type作为普通的函数调用是返回对象的类型,也可以用来创建类,语法如下:
type(name, bases, dict)参数:name:类名(字符串)
bases:基类元组(继承关系)
dict:类的命名空间字典(包含属性和方法)
# 传统类定义(语法糖)
class MyClass:x = 10def method(self):return self.x# 等价于使用type动态创建类
MyClassDynamic = type('MyClassDynamic', (), {'x': 10, 'method': lambda self: self.x})# 两者完全等价
print(MyClass().method()) # 10
print(MyClassDynamic().method()) # 10
💡 这也是Python作为动态类型语言的一大特点,可以动态的创建类。
自定义元组的使用
💡 既然类是type的实例,我们可以创建type的子类来定制类的创建行为:
class Meta(type):def __new__(cls, name, bases, dct):# 在类创建时添加自定义逻辑dct['created_by_meta'] = Truereturn super().__new__(cls, name, bases, dct)class MyClass(metaclass=Meta):passprint(MyClass.created_by_meta) # True - 由元类动态添加
print(type(MyClass)) # <class '__main__.Meta'>
💡 这是框架使用中常见的手段,对于分析用户的使用习惯和跟踪用户的操作流程是非常有用的。
2. 实例化对象
# 创建Person类的实例
person1 = Person() # 注意:没有new关键字
person2 = Person()print(type(person1)) # <class '__main__.Person'>
print(person1 is person2) # False - 两个不同的实例
上面是我们最常见的实例化对象的手段,当然也可以通过上述的元组进行实例化。
3. Java对比
| 特性 | Python的__init__ | Java构造函数 |
|---|---|---|
| 名称 | 固定为__init__ | 与类名相同 |
| 返回值 | 不能返回任何值(隐式返回None) | 没有返回类型(连void都没有) |
| 实际构造者 | __new__方法真正创建实例 | new关键字创建实例 |
| 调用时机 | 在实例已被创建后调用,用于初始化 | 在实例创建过程中调用 |
1.2 实例变量与类变量
💡 实例变量属于单个对象实例,类变量属于类本身并被所有实例共享。
1. 实例变量
class Person:def __init__(self, name, age):# 实例变量 - 通过self.前缀定义self.name = name # 实例变量self.age = age # 实例变量self.email = f"{name}@example.com" # 基于参数计算的实例变量def update_email(self, new_email):self.email = new_email # 在方法中修改实例变量# 使用
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)print(person1.name) # "Alice" - 每个实例有自己的name
print(person2.name) # "Bob" - 互不影响person1.age = 26 # 修改实例变量
-
作用:存储每个对象实例特有的数据。
-
生命周期:与对象实例共存亡,实例被垃圾回收时,其实例变量也随之销毁。
-
访问:必须通过self.前缀在类内部访问,通过instance.在外部访问。
2. 类变量
class Person:# 类变量 - 在类内直接定义,不在方法内species = "Homo sapiens" # 类变量population = 0 # 跟踪所有实例的计数器def __init__(self, name):self.name = name # 实例变量Person.population += 1 # 通过类名访问并修改类变量# 使用
print(Person.species) # "Homo sapiens" - 通过类名直接访问person1 = Person("Alice")
person2 = Person("Bob")print(person1.species) # "Homo sapiens" - 通过实例访问类变量
print(person2.species) # "Homo sapiens" - 所有实例共享相同的值print(Person.population) # 2 - 统计创建了多少个实例
3. 变量查找顺序(重点:理解类变量和实例变量)
现有一个疑问,因为Java中虽然也存在类似类变量的static静态变量,但一般都是使用final关键字修饰的,无法更改。因为Python中实例可以修改类变量,那岂不是说
只能在init方法中才能定义属性了?,那之前学的变量限制类型又有什么用呢?
💡 答案首先是,不用担心,正常使用即可,原因就是Python的变量查找顺序,一旦实例访问类变量,那Python是会将其赋值到实例的属性上的,不会直接修改类变量。
class Counter:count = 0 # 类变量,所有实例共享def __init__(self, name):self.name = name # 实例变量,每个实例私有# 创建两个实例
c1 = Counter("First")
c2 = Counter("Second")# 通过类访问类变量
Counter.count = 10
print(c1.count) # 10 - 所有实例都看到变化
print(c2.count) # 10# 通过实例访问类变量
c1.count = 20 # ⚠️ 关键点:这里发生了什么?
print(c1.count) # 20
print(c2.count) # 10 - c2仍然看到原来的类变量值
print(Counter.count) # 10 - 类变量本身没有被修改
Python查找属性的顺序是:
- 实例变量 (instance.dict)
- 类变量 (Class.dict)
- 父类变量 (按MRO顺序,Class.mro)
- 模块变量 (module globals)
- 内置变量 (builtins.dict)
Java对比
| 概念 | Python | Java |
|---|---|---|
| 实例级别 | 实例变量 | 实例字段(非static) |
| 类级别 | 类变量 | 静态字段(static) |
这里多说一下,如果你的第一语言是Java,那一定要对Python中的很多知识点吃透了,类似上面所说的类变量和实例变量,因为在Java中static静态变量我们用习惯了,毕竟Python是动态编程语言,有些地方很灵活,但也很容易犯错。
二、方法详解
Python类中有三种基本方法类型:实例方法、类方法和静态方法。它们通过不同的装饰器和第一个参数来区分,各自承担明确的职责。
2.1 实例方法
💡 定义在类内部,一般没有什么注解修饰,第一个参数是self。
1. self参数详解
- self的本质:self是对当前实例对象的引用,它本质上是一个指向实例的指针。
- 约定而非关键字:self只是命名约定,你可以用任何名字,但强烈不建议这样做。
- 自动传递:Python在调用实例方法时,会自动将实例作为第一个参数传入。
2. 方法的定义和调用
# 定义:在类内部定义,第一个参数为self
def method_name(self, param1, param2):# 方法体return result# 调用方式对比
person = Person("Alice")# 方式1:通过实例调用(推荐)
result1 = person.introduce("Hi") # Python自动传递person作为self# 方式2:通过类调用(不常见,但合法)
result2 = Person.introduce(person, "Hi") # 必须显式传递self参数print(result1) # "Hi, my name is Alice"
print(result2) # "Hi, my name is Alice"
2.2 类方法(@classmethod)
💡 和实例方法基本一致,最大的区别是用注解@classmethod修饰。
注意:类方法主要的作用是用来访问和修改类变量的,而且实例对象也可以直接访问类方法。
1. 类方法的定义和使用
定义
class Person:population = 0species = "Homo sapiens"def __init__(self, name):self.name = namePerson.population += 1# 类方法 - 操作类级别的数据@classmethoddef get_population(cls):return cls.population@classmethod def get_species(cls):return cls.species# 类方法可以修改类变量@classmethoddef set_species(cls, new_species):cls.species = new_speciesreturn f"Species updated to {new_species}"
使用
# 通过类调用
print(Person.get_population()) # 0
print(Person.get_species()) # "Homo sapiens"person1 = Person("Alice")
print(Person.get_population()) # 1# 通过实例调用
print(person1.get_population()) # 1 - 仍然访问类级别数据
print(person1.get_species()) # "Homo sapiens"# 修改类变量
Person.set_species("Human")
print(person1.get_species()) # "Human" - 所有实例都看到变化
- cls参数:接收类本身(如Person),而不是实例。
- 装饰器必需:必须使用@classmethod装饰器。
- 通过类和实例都可调用:两种调用方式都指向同一个类方法。
2. 类方法的用途
💡 替代构造函数
class Date:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = day# 替代构造函数1:从字符串解析@classmethoddef from_string(cls, date_string):year, month, day = map(int, date_string.split('-'))return cls(year, month, day) # 相当于调用Date(year, month, day)# 替代构造函数2:从时间戳创建@classmethoddef from_timestamp(cls, timestamp):import datetimedt = datetime.datetime.fromtimestamp(timestamp)return cls(dt.year, dt.month, dt.day)def __str__(self):return f"{self.year}-{self.month:02d}-{self.day:02d}"# 使用不同的构造方式
date1 = Date(2024, 1, 15) # 标准构造
date2 = Date.from_string("2024-01-20") # 类方法构造
date3 = Date.from_timestamp(1705708800) # 类方法构造print(date1) # 2024-01-15
print(date2) # 2024-01-20
print(date3) # 2024-01-20
3. 和Java的区别
| 特性 | Python类方法 | Java静态方法 |
|---|---|---|
| 第一个参数 | cls(类对象) | 无特殊参数 |
| 访问类变量 | 通过cls. | 通过类名或直接访问 |
| 多态支持 | 支持(cls是动态的) | 不支持(静态绑定) |
| 继承行为 | 子类调用时cls指向子类 | 总是绑定到定义它的类 |
2.3 静态方法(@staticmethod)
💡 静态方法需要注解@staticmethod修饰,不需要self和cls参数,就是一个普通的方法。
class Employee:company_name = "Tech Corp"def __init__(self, name, salary):self.name = nameself.salary = salary# 实例方法 - 操作实例数据def get_annual_salary(self):return self.salary * 12# 类方法 - 操作类数据@classmethoddef get_company_name(cls):return cls.company_name# 静态方法 - 工具函数,与实例/类状态无关@staticmethoddef calculate_tax(salary, tax_rate=0.2):return salary * tax_rate@staticmethoddef is_valid_salary(salary):return salary >= 0 and salary <= 1000000# 使用
emp = Employee("Alice", 5000)# 实例方法
print(emp.get_annual_salary()) # 60000# 类方法
print(Employee.get_company_name()) # "Tech Corp"# 静态方法 - 多种调用方式
print(Employee.calculate_tax(5000)) # 1000.0
print(emp.calculate_tax(5000)) # 1000.0 - 通过实例调用
print(Employee.is_valid_salary(50000)) # True
问题来了,既然和普通方法基本一致,为什么要定义在类的内部,这样不是更加的麻烦吗?而且为什么不直接使用普通的方法呢?
- 代码维护性更高,可读性更好,明确知道这个方法和某个类有关联。
- 从编译角度来说,少了self参数,稍微会减少字节码。
- 从类的继承角度来说比较友好,行为明确。
三、核心特殊方法(魔术方法)
3.1 对象生命周期相关
1. __new__(cls, ...):真正的构造函数
💡 作用:负责创建并返回类的新实例,相当于Java的构造函数创建对象。
class MyClass:def __new__(cls, *args, **kwargs):print("__new__被调用,创建新实例")instance = super().__new__(cls) # 必须调用父类的__new__return instance # 返回实例对象def __init__(self, value):print("__init__被调用,初始化实例")self.value = valueobj = MyClass(10)
# 输出:
# __new__被调用,创建新实例
# __init__被调用,初始化实例
2. __init__(self, ...):初始化器(非构造函数)
💡 作用:类初始化函数,也就是在类的实例实例化之后执行的初始化方法,这时候实例已经创建。
class Person:def __init__(self, name, age):self.name = nameself.age = age# 不需要返回值
-
__new__: 创建对象(分配内存) -
__init__: 初始化对象(设置属性)
3. __del__(self):析构器(了解即可,通常不推荐使用)
4. jquery和js中类似的生命周期函数(有点儿意思)
jquery的ready函数
$.ready() 的核心作用是:在 HTML 文档的结构(DOM树)完全加载和解析完毕后,立即执行代码,而无需等待样式表、图片等外部资源完全加载。
$(document).ready(function() {// 这里的代码会在 DOM 准备好后执行console.log("DOM is ready! (jQuery)");// 例如: $('#my-button').on('click', ...);
});// 或者简写形式
$(function() {console.log("DOM is ready! (jQuery shorthand)");
});
这对于老程序员就很熟悉了(用jsp的那一批),现在已经很少使用jQuery了,之所以之前经常使用这个ready函数是因为jsp的存在,在dom中存在了太多了变量赋值和一些其它操作,但实际上我们需要在js中获取到加载后的元素,这时候必须使用ready才行,不然你获取到的元素始终是null。
为什么说jsp的页面加载很慢呢,Java是一方面,另外一方面就是上面的加载机制了,必须要等到dom、css、js和图片等资源完全加载,而且不是并行的,你的dom加载肯定是最优先的,没有dom后面都不能加载。
js中的 DOMContentLoaded 方法
这是老祖宗,jQuery的ready就是基于它,等待页面的dom元素加载完成后会自动触发这个函数。
document.addEventListener('DOMContentLoaded', function() {// 这里的代码会在 DOM 准备好后执行console.log("DOM is ready! (Native JavaScript)");// 例如: document.getElementById('my-button').addEventListener('click', ...);
});
现代的 defer 属性
这是一个现在常用控制页面加载速度的一个方式,比起上述的两个方法都要通用和简洁,而且按照我们正常的操作习惯,script标签可以放在dom元素的开头。
<!DOCTYPE html>
<html>
<head><title>My Page</title><!-- 推荐将带 defer 的脚本放在 head 中 --><script src="app.js" defer></script>
</head>
<body><!-- 页面内容 -->
</body>
</html>
defer 属性告诉浏览器:
并行下载:立即开始下载 app.js 文件,但不要停止解析 HTML 文档(非阻塞)。延迟执行:等到整个 HTML 文档解析完毕后(即 DOMContentLoaded 事件触发前),再执行这个脚本。
使用 defer 的好处:
- 性能更好:脚本下载和页面渲染可以并行进行。
- 行为可预测:脚本的执行时机与 DOMContentLoaded 基本一致,可以安全地操作 DOM。
- 保持顺序:如果多个脚本都使用了 defer,浏览器会按照它们在 HTML 中出现的顺序来执行它们。
与 window.onload 的区别
onload是等待页面所有元素加载完成后的一个函数,一般使用较少,做一些图片的调整或者一些通知是很常见的。
window.addEventListener('load', function() {console.log("Page is fully loaded, including images! (Native JavaScript)");// 比如在这里获取一张图片的原始宽高是安全的
});
IIFE: 立即调用函数表达式(Immediately Invoked Function Expression)
这和上述的几种声明周期函数是不一样的,这本身是一种立即执行的写法,而不是生命周期,在ES6没有出现之前这种写法很受欢迎,通常和声明周期函数一起执行,该执行ajax请求的执行,然后用dom的函数放在生命周期函数中。
(function() {// 这里的代码会立刻执行var message = "Hello from inside the IIFE!";console.log(message);
})(); // <-- 最后这个括号是关键,它表示“立即调用”
最重要的是,ES5的变量只有var一种,它的作用域只分为函数作用域和全局作用域,经常会出现不同js文件变量污染的情况,所以js基本都使用IIFE包裹起来,这样可以达到局部作用域的效果,间接的实现了ES6的let和const。
3.2 对象表示相关
1. __str__(self):面向用户的字符串表示
class Product:def __init__(self, name, price):self.name = nameself.price = pricedef __str__(self):return f"产品: {self.name}, 价格: ¥{self.price}"product = Product("笔记本电脑", 5999)
print(str(product)) # 产品: 笔记本电脑, 价格: ¥5999
print(product) # 隐式调用__str__
2. __repr__(self):面向开发者的精确字符串表示
class Product:def __init__(self, name, price):self.name = nameself.price = pricedef __repr__(self):return f"Product('{self.name}', {self.price})"product = Product("笔记本电脑", 5999)
print(repr(product)) # Product('笔记本电脑', 5999)
3. 理解两者的区别
从上述可以发现,从语法角度来讲没有任何区别,都是返回一个字符串,实际上根本的区别在:软件设计意图上,可以理解为一个规范,str就是给用户看的,repr就是给程序员看的。
在生活中也很常见,同样是代表一个物品,它可以有多重表现方式,拿红票子来说,我们一看是红色的,100就知道这是一百块,那验钞机怎么识别呢,肯定通过我们不知道的方式,要不然仅凭100那肯定不行。
关于__repr__更多的是用于日志和调试,在控制台默认输出一个对象,调用的都是这个方法,如果不重写那输出的就是内存地址,很不直观。而且一个良好的repr输出应该是一个正确的Python表达式,它可以通过eval函数反过来实例化对象,例如
eval(repr(obj))
3.3 比较与排序相关
核心就是用运算符来对两个对象之间进行算术运算,如果不重写的话默认比较的是内容地址,在多数情况下是不适用的,所以就出现了以下的方法。
1. 比较:__eq__
2. 排序
__lt__ # less than 小于 <
__le__ # less or equal 小于等于 <=
__gt__ # greater than 大于 >
__ge__ # greater or equal 大于等于 >=
__eq__ # equal 等于 ==
__ne__ # not equal 不等于 !=
3. 完整示例
class Student:def __init__(self, name, score):self.name = nameself.score = scoredef __eq__(self, other):if not isinstance(other, Student):return NotImplementedreturn self.score == other.scoredef __lt__(self, other):if not isinstance(other, Student):return NotImplementedreturn self.score < other.scoredef __le__(self, other):return self < other or self == otherdef __hash__(self):return hash((self.name, self.score)) # 用于set和dict# 使用示例
alice = Student("Alice", 85)
bob = Student("Bob", 92)print(alice == bob) # False
print(alice < bob) # True
print(alice <= bob) # True# 自动获得其他比较操作
print(alice > bob) # False (基于__lt__自动生成)
3.4 属性访问与控制
1. 基本的属性访问
在Python中,所有的属性和方法
默认都是公开的,可以直接访问和修改。
class Person:def __init__(self, name, age):self.name = name # 公开属性self.age = age # 公开属性p = Person("Alice", 30)# 直接访问和修改
p.age = 31
print(p.age) # 输出: 31
但实际上这样有一个弊端,正常开发中我们不允许直接修改类中的变量,需要通过一定的手段(方法)进行,因为这样可控性更高,在Java中存在private修饰符,但是Python没有,为了倪补这个缺陷出现了两种约定。
1. 1 _single_underscore (单下划线前缀): “内部使用”的提示
- 这是一种约定,告诉其他开发者:“这是一个内部属性或方法,请不要在外部直接使用它,除非你非常清楚后果。它不属于公共API,
未来可能会改变。” - 它
不会在技术上阻止你访问。
class Person:def __init__(self, name, age):self.name = nameself._age = age # 约定为内部属性def display_info(self):print(f"Name: {self.name}, Age: {self._age}")p = Person("Bob", 25)
print(p._age) # 仍然可以访问,但不推荐
p._age = 26 # 仍然可以修改,但不推荐
1.2. __double_underscore (双下划线前缀): 名称改写 (Name Mangling)
- 这看起来像“私有”,但其主要目的是为了
避免在子类中意外覆盖基类的属性。 - Python 解释器会自动将 __age 这样的名字
重命名为 _ClassName__age。
class Person:def __init__(self, name, age):self.name = nameself.__age = age # 会被名称改写p = Person("Charlie", 40)
# print(p.__age) # AttributeError: 'Person' object has no attribute '__age'# 这才是它真正的名字
print(p._Person__age) # 输出: 40
p._Person__age = 41 # 仍然可以修改
2. “Getter/Setter” 的 Pythonic 方式 (@property装饰器)
在函数章节我们简单说过这个注解,是之后非常常用的一个,这会模拟出Java那种getter和setter方法的既视感。
class Person:def __init__(self, name, age):self.name = nameself._age = age # 1. 将实际存储值改为"内部"属性# 2. 创建 getter@propertydef age(self):"""age 的 getter"""print("Getting age...")return self._age# 3. 创建 setter@age.setterdef age(self, value):"""age 的 setter"""print(f"Setting age to {value}...")if not isinstance(value, int) or value < 0:raise ValueError("Age must be a non-negative integer.")self._age = value# 4. 创建 deleter (可选)@age.deleterdef age(self):"""age 的 deleter"""print("Deleting age...")del self._age# --- 使用方式 ---
p = Person("Dana", 50)# 访问时看起来是属性,实际调用了 getter 方法
current_age = p.age # 输出: Getting age...
print(current_age) # 输出: 50# 赋值时看起来是属性,实际调用了 setter 方法
p.age = 51 # 输出: Setting age to 51...
print(p.age) # 输出: Getting age... \n 51try:p.age = -5 # 输出: Setting age to -5...
except ValueError as e:print(e) # 输出: Age must be a non-negative integer.# 删除时看起来是属性,实际调用了 deleter 方法
del p.age # 输出: Deleting age...
上面的例子诠释了Python中getter和setter的实现,但实际上访问和修改的方式并没有变化。
3. 动态和高级属性控制 (Magic Methods)
这是 Python 动态语言特性发挥到极致的地方,Java 几乎没有直接的对应物。(引用,不是我说的)
3.1 __getattr__(self, name)
- 触发时机:仅当对一个
不存在的属性进行访问时,这个方法才会被调用 - 用途:实现
代理模式、懒加载、处理废弃的属性名等
class DynamicProxy:def __init__(self, target_obj):self._target = target_objdef __getattr__(self, name):print(f"__getattr__: '{name}' not found directly, delegating to target...")return getattr(self._target, name)class SecretService:def get_secret_code(self):return "007"proxy = DynamicProxy(SecretService())# proxy 本身没有 get_secret_code 方法
# 访问时触发 __getattr__
code = proxy.get_secret_code() # 输出: __getattr__: 'get_secret_code' not found directly, delegating to target...
print(code) # 输出: 007
这是一个经典的代理场景,代理对象如果不存在某个属性则使用被代理的对象,gatattr是一个内置函数,用来获取某个对象的属性和方法的,这里就获取了被代理对象的方法然后执行。
3.2 __getattribute__(self, name)
- 触发时机:
每一次对属性的访问都会无条件地拦截并调用此方法(即使属性存在)。 - 警告: 这个方法非常强大,但也极其危险。在内部不加特殊处理地访问自身属性(如 self.name)会导致无限递归!
必须通过 super().__getattribute__(name) 来安全地获取属性值。 - 用途: 实现需要监控所有属性访问的场景,如全面的访问日志、安全代理等。
class AccessLogger:def __init__(self):self.public_data = "some value"def __getattribute__(self, name):print(f"LOG: Accessing attribute '{name}'")# 必须用 super() 来避免无限递归return super().__getattribute__(name)logger = AccessLogger()
val = logger.public_data # 输出: LOG: Accessing attribute 'public_data'
# 即便访问不存在的属性,也是它先被调用
# try:
# logger.no_such_attr
# except AttributeError:
# pass # 输出: LOG: Accessing attribute 'no_such_attr'
3.3 __setattr__(self, name, value) ——属性赋值守卫(重点)
触发时机:每次属性变化的时候都会触发,排除通过self.__dict__进行赋值。
作用:
- 数据验证: 确保赋给属性的值符合特定规则。
- 只读属性: 禁止修改某些已经设置好的属性。
- 日志/审计: 记录对对象状态的每一次修改。
- 触发副作用: 当某个属性改变时,自动执行其他操作(例如更新缓存、通知观察者等)。
两种在setattr中赋值的操作
方法一:直接操作实例的 dict (最直接)
class MyClass:def __setattr__(self, name, value):print(f"Setting '{name}' to '{value}' via __dict__")self.__dict__[name] = value
方法二:使用 super() (更推荐,尤其在继承中)
class MyClass:def __setattr__(self, name, value):print(f"Setting '{name}' to '{value}' via super()")super().__setattr__(name, value)
实际例子
class TypedConfig:def __init__(self):# 预定义属性和它们的期望类型self._types = {'host': str,'port': int,'debug_mode': bool}# 使用 super() 来避免触发我们自定义的 __setattr__super().__setattr__('host', 'localhost')super().__setattr__('port', 8080)super().__setattr__('debug_mode', False)def __setattr__(self, name, value):print(f"Attempting to set '{name}' to '{value}'...")# 检查属性是否是预定义的if name not in self._types:raise AttributeError(f"'{name}' is not a valid config key.")# 检查类型是否匹配expected_type = self._types[name]if not isinstance(value, expected_type):raise TypeError(f"'{name}' must be of type {expected_type.__name__}, not {type(value).__name__}.")# 如果所有检查都通过,安全地设置属性super().__setattr__(name, value)print(f"Successfully set '{name}'.")# --- 使用 ---
config = TypedConfig()
print(f"Initial port: {config.port}") # -> 8080config.port = 9000 # 触发 __setattr__
# 输出:
# Attempting to set 'port' to '9000'...
# Successfully set 'port'.
print(f"New port: {config.port}") # -> 9000try:config.port = '8000' # 尝试设置错误的类型
except TypeError as e:print(f"Error: {e}") # -> Error: 'port' must be of type int, not str.try:config.user = 'admin' # 尝试设置一个不存在的属性
except AttributeError as e:print(f"Error: {e}") # -> Error: 'user' is not a valid config key.
3.4 __delattr__(self, name): 属性删除的守卫
- 触发时机:属性被删除的时候。
- 注意事项:和上面一样,需要使用字典和super在类中删除属性。
- 实际用例:
- 防止删除: 保护重要属性不被意外删除。
- 资源清理: 当一个属性被删除时,可能需要执行一些清理工作,比如关闭与该属性关联的文件句柄或网络连接。
- 日志/审计: 记录属性删除操作。
class TypedConfig:def __init__(self):# ... (和上面一样)self._types = {'host': str,'port': int,'debug_mode': bool}# 使用 super() 来避免触发我们自定义的 __setattr__super().__setattr__('host', 'localhost')super().__setattr__('port', 8080)super().__setattr__('debug_mode', False)# 新增一个不可删除的属性集合super().__setattr__('_undeletable', {'host', 'port'})def __setattr__(self, name, value):# ... (和上面一样)print(f"Attempting to set '{name}' to '{value}'...")if name not in self._types:raise AttributeError(f"'{name}' is not a valid config key.")expected_type = self._types[name]if not isinstance(value, expected_type):raise TypeError(f"'{name}' must be of type {expected_type.__name__}, not {type(value).__name__}.")super().__setattr__(name, value)print(f"Successfully set '{name}'.")# 新增的 __delattr__ 方法def __delattr__(self, name):print(f"Attempting to delete '{name}'...")if name in self._undeletable:raise AttributeError(f"Cannot delete protected attribute '{name}'.")# 如果允许删除,就安全地执行super().__delattr__(name)print(f"Successfully deleted '{name}'.")# --- 使用 ---
config = TypedConfig()
print(f"Debug mode is: {config.debug_mode}")# 尝试删除一个可删除的属性
del config.debug_mode
# 输出:
# Attempting to delete 'debug_mode'...
# Successfully deleted 'debug_mode'.
# print(config.debug_mode) # 这会引发 AttributeError# 尝试删除一个受保护的属性
try:del config.port
except AttributeError as e:print(f"Error: {e}") # -> Error: Cannot delete protected attribute 'port'.
四、继承与多态
4.1 基本继承语法
- 语法:
class ChildClass(ParentClass) - 核心概念: 子类 ChildClass 会自动“拥有”父类 ParentClass 中所有非私有的属性和方法
# 定义一个父类
class Animal:def __init__(self, name):self.name = nameprint(f"Animal '{self.name}' has been created.")def speak(self):# 这是一个通用的方法return f"{self.name} makes a sound."# 定义一个子类,继承自Animal
class Dog(Animal):# Dog类现在自动拥有了__init__和speak方法pass # pass表示我们暂时不添加任何新内容# --- 使用 ---
my_dog = Dog("Buddy") # 调用的是从Animal继承来的__init__方法
print(my_dog.speak()) # 调用的是从Animal继承来的speak方法
4.2 方法重写 与 super()函数
首先我们需要知道两个最基本的使用方式
super()的作用:动态调用父类方法,可以理解为super函数调用了父类的构造方法,创建了父类。- 在
__init__中的使用:为了保证父类被正确初始化,我们一般都需要手动调用父类的init方法初始化属性,这一步必不可少。
class Animal:def __init__(self, name):self.name = namedef speak(self):return f"{self.name} makes a sound."class Dog(Animal):def __init__(self, name, breed):# 1. 调用父类的__init__来初始化name属性super().__init__(name) # 2. 然后再初始化子类特有的breed属性self.breed = breedprint(f"Dog of breed '{self.breed}' has been created.")def speak(self): # 重写 speak 方法# 3. 调用父类的speak方法,并在其基础上扩展parent_speak = super().speak() return f"{parent_speak} Specifically, it barks!"# --- 使用 ---
my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.speak())
4.3 方法解析顺序(MRO——Method Resolution Order)
为什么要理解这个,因为在
Python中是多继承的,而Java是单继承,一旦父类中出现同名的方法,我们需要指定调用哪一个,而这个规则就是 MRO。
- 核心算法: Python使用
C3线性化算法来计算MRO。不需要手动实现这个算法,但需要理解它的目标:创建一个唯一的、从左到右、保留父类顺序的、单调的类列表。 - 查看MRO:
ClassName.__mro__或ClassName.mro()
class A:def ping(self):print("ping from A")class B(A):def ping(self):print("ping from B")class C(A):def ping(self):print("ping from C")class D(B, C): # D继承自B和Cpass# --- 查看 MRO ---
print(D.__mro__)
print(D.mro())d = D()
d.ping() # Python会根据MRO的顺序查找并调用第一个找到的ping方法

D.__mro__:输出一个继承元组。D.mro():输出一个继承列表。
4.4 __init_subclass__钩子(重点)
这是一个类上的魔法方法,对于理解Python的多继承和其作为动态语言的优势所在。
- 定义与时机: 它在父类中定义,
当任何子类被创建(定义)时,这个方法会被自动调用。注意,这不是在子类实例化时调用,而是在class SubClass(ParentClass):这行代码执行完毕时调用。 - 用途: 它提供了一个强大的机制来“钩入”子类的创建过程,可以用来自动
注册子类、验证子类定义是否合规、或动态修改子类等。
class PluginBase:PLUGINS = {} # 用于存储所有插件子类的字典def __init_subclass__(cls, **kwargs):# 当一个PluginBase的子类被定义时,这个方法会被调用super().__init_subclass__(**kwargs)# 'cls' 参数就是正在被定义的子类plugin_name = cls.__name__.lower()print(f"Registering plugin: {plugin_name}")cls.PLUGINS[plugin_name] = cls# --- 定义子类时,__init_subclass__ 会自动运行 ---
class JsonParser(PluginBase):def parse(self, data):return "Parsing JSON data..."class XmlParser(PluginBase):def parse(self, data):return "Parsing XML data..."# --- 检查注册结果 ---
print("\nAvailable plugins:", PluginBase.PLUGINS)# --- 使用注册的插件 ---
parser_name = "jsonparser"
parser_class = PluginBase.PLUGINS.get(parser_name)
if parser_class:parser_instance = parser_class()print(parser_instance.parse({}))
注意:
Python是动态语言,这是一个非常重要的例子来证明动态在哪里,Java是完全做不到的,一般在程序编译的时候就执行了,我们可以控制子类和校验子类,非常的有用。
五、组合 、Mixin模式、装饰器
5.1 组合
💡 这是一种设计模式,因为继承有一个非常大的弊端就是耦合性太高,只适合简单的场景,如果复杂的场景使用则会非常的麻烦,所以出现了组合,一般来说,组合优于继承,相当于将is-a转变为has-a。
- 继承的弊端(紧耦合): 当子类继承父类时,它们之间会形成一种非常紧密的、静态的关系。子类不仅继承了父类的公共接口,还可能继承了其内部实现细节。
如果父类的实现发生改变,所有子类都可能受到影响,这使得系统变得脆弱和僵化。这就是所谓的“紧耦合”。 - 组合的优势(松耦合): 组合通过对象间的引用来工作。Car类
只关心它所拥有的Engine对象提供了哪些公共方法(如start()),而完全不关心这个引擎是V8引擎还是电动机,也不关心其内部实现。这种关系更加灵活,耦合度更低。
部件类
class Engine:def __init__(self, horsepower):self.horsepower = horsepowerdef start(self):print(f"Engine with {self.horsepower}hp starting... Vroom!")class Wheels:def __init__(self, count=4):self.count = countdef report(self):return f"{self.count} wheels"
基本的组合实现
class Car:def __init__(self, model, horsepower):self.model = model# "has-a" 关系:Car实例包含一个Engine实例和一个Wheels实例self.engine = Engine(horsepower)self.wheels = Wheels()print(f"Car '{self.model}' has been assembled.")def drive(self):print(f"Driving the {self.model}.")self.engine.start() # 委托(Delegate)任务给engine对象def get_specs(self):print(f"Specs for {self.model}:")print(f"- Engine HP: {self.engine.horsepower}")print(f"- Wheels: {self.wheels.report()}")# --- 使用 ---
my_sedan = Car("Tesla Model S", 778)
my_sedan.drive()
print("-" * 20)
my_sedan.get_specs()
注意看上面的例子,我们在使用Car的时候还是需要传入参数778,这是Engine类的引擎大小,从某方面来说不算真正意义上的解耦合。
进阶组合
class ElectricMotor: # 一个不同的Engine类型def __init__(self, kilowatt):self.kilowatt = kilowattdef start(self):print(f"Electric motor with {self.kilowatt}kW starting... Whirrr!")class Car:# 注意 __init__ 的变化:它接收一个engine对象,而不是horsepowerdef __init__(self, model, engine_instance): self.model = modelself.engine = engine_instance # 注入依赖def drive(self):self.engine.start()# --- 使用 ---
# 1. 在外部创建部件
v8_engine = Engine(500)
electric_motor = ElectricMotor(350)# 2. 将部件注入到Car中
gas_car = Car("Ford Mustang", v8_engine)
electric_car = Car("Nissan Leaf", electric_motor)gas_car.drive() # 输出 V8 引擎的声音
electric_car.drive() # 输出电动机的声音
这个例子中我们直接将引擎类作为一个组件进行注入,这样我们不参与引擎的内部实现和参数调配,有点类似于Java中的AutoWried注解。
5.2 Mixin
💡 这是一个大多数语言都存在的特性,Mixin是一个功能,解决了在不同相关类中需要实现同样功能的问题,比如日志记录,主要实现的方式就是多继承。
一般我们实现的方式无非是:
- 继承某个类
- 重复方法
但是这两个都有弊端,一个是耦合性太高,一个是出现大量重复代码,所以Mixin就出现了。
- 创建小而专注的Mixin类
# Mixin 1: 提供日志功能
class LoggingMixin:def log(self, message, level="INFO"):# self 在这里会指向继承此Mixin的类的实例print(f"[{level}] - {self.__class__.__name__}: {message}")# Mixin 2: 提供字典转换功能
class DictSerializableMixin:def to_dict(self):# 一个简单的实现:将对象的__dict__属性转换为字典return self.__dict__
- 在你的主类中混入这些功能
# 一个完全独立的类
class DatabaseConnection:def __init__(self, connection_string):self.connection_string = connection_stringdef connect(self):print(f"Connecting to {self.connection_string}...")# 另一个完全独立的类,但我们希望它有日志和序列化功能
class User(LoggingMixin, DictSerializableMixin):def __init__(self, username, email):self.username = usernameself.email = emaildef login(self):# 使用从LoggingMixin继承来的方法self.log(f"User '{self.username}' logged in.")# --- 使用 ---
user = User("alice", "alice@example.com")
user.login()# 使用从DictSerializableMixin继承来的方法
user_data = user.to_dict()
print(f"User data as dict: {user_data}")
5.3 装饰器
虽然Mixin已经实现了功能,但实际上局限性还是比较多,一个是依旧使用了继承,二则是定制化不够高,如果要实现方法的时间统计和日志记录就显得有些麻烦,所以
装饰器则是为了更加详细的定制化服务。
def log_activity(func):def wrapper(*args, **kwargs):# args[0] is 'self' for an instance methodinstance_name = args[0].__class__.__name__print(f"[DECORATOR LOG] - {instance_name}: Calling method '{func.__name__}'")result = func(*args, **kwargs)return resultreturn wrapperclass FileHandler:@log_activitydef read_file(self, path):print(f"Reading from {path}")@log_activitydef write_file(self, path):print(f"Writing to {path}")# --- 使用 ---
handler = FileHandler()
handler.read_file("data.txt")
关于装饰器我们在前一章节函数中详细说明过,这和Java的切面很类似,只不过Java的切面定制化更高更详细。这里的原理就是Python将某个方法使用另外的函数进行装饰,然后自动调用,我们只需要返回原有函数的执行结果即可达到之前的目的。
六、鸭子类型与协议
6.1 鸭子类型哲学
💡 鸭子类型不是一个对象的类型,而是一种编程风格,核心就是“不关心这个对象是什么类型,只关心其中有什么方法”,简单点就是不看你是谁,就看你挣了多少钱。
# 传统的鸭子类型示例class Duck:def quack(self):print("嘎嘎!")class Person:def quack(self):print("我正在模仿鸭子叫!")def make_it_quack(duck_like_object):# 这个函数不关心传入的是什么类型的对象# 它只假设对象有一个 .quack() 方法并调用它duck_like_object.quack()# --- 测试 ---
d = Duck()
p = Person()make_it_quack(d) # 输出: 嘎嘎!
make_it_quack(p) # 输出: 我正在模仿鸭子叫!
在Js中也经常出现这种写法,不过很容易出现没有这个方法的情况,为了解决这种问题,Python引入了一个协议如下。
6.2 结构子类型
💡 这是一个协议,用来在编译时检查出问题,避免运行时错误。
typing.Protocol(Python 3.8+)- 静态类型检查中的鸭子类型支持
from typing import Protocol# 1. 定义一个协议,形式化“像鸭子”的行为
class Quackable(Protocol):def quack(self) -> None:... # 方法体不重要,只关心签名# 2. 我们的类保持不变,它们不需要继承 Quackable
class Duck:def quack(self) -> None:print("嘎嘎!")class Person:def quack(self) -> None:print("我正在模仿鸭子叫!")class Dog:def bark(self) -> None:print("汪汪!")# 3. 在函数签名中使用协议进行类型提示
def make_it_quack_safe(thing: Quackable) -> None:thing.quack()# --- 静态类型检查阶段 ---
d = Duck()
p = Person()
dog = Dog()make_it_quack_safe(d) # 类型检查通过
make_it_quack_safe(p) # 类型检查通过
make_it_quack_safe(dog) # 类型检查器会在这里报错!# 错误信息: Argument 1 to "make_it_quack_safe" has # incompatible type "Dog"; expected "Quackable"
这种实现的高明之处就是没有使用继承,避免了程序的耦合性过高,typing的实现很高级。
七、高级类特性
7.1 抽象基类(ABCs)
💡 直白来说就是Java的抽象类,强制实现某个方法,使用到了ABCs。
- ABC:一个必须要继承的类。
- abstractmethod:放在方法上即可实现强制实现。
from abc import ABC, abstractmethod# 1. 让基类继承自 ABC
class BasePlugin(ABC):# 2. 用 @abstractmethod 装饰器标记必须被子类实现的方法@abstractmethoddef run(self) -> None:"""运行插件的核心逻辑。"""pass # 抽象方法中可以没有实现代码def setup(self):# ABCs中也可以有普通方法,供子类继承或覆盖print("通用的设置逻辑...")# --- 尝试创建一个不完整的子类 ---
class IncompletePlugin(BasePlugin):# 这个类没有实现 run() 方法def some_other_method(self):pass# --- 尝试创建一个完整的子类 ---
class CompletePlugin(BasePlugin):def run(self) -> None:# 正确实现了抽象方法self.setup() # 还可以调用基类的普通方法print("插件正在运行!")# --- 验证效果 ---
# base = BasePlugin() # TypeError: Can't instantiate abstract class BasePlugin with abstract method run
# incomplete = IncompletePlugin() # TypeError: Can't instantiate abstract class IncompletePlugin with abstract method run
complete = CompletePlugin() # 成功!
complete.run()
7.2 数据类(@dataclass)(Python 3.7+)
💡 就是Java的@Data注解,自动生成某些方法,详细的我们在上一节说过,点击@dataclass装饰器即可查看。但只是从开发经验来说,一般不会使用这个注解,如果是在某些特殊的场景可能会实现,因为一般的项目都要求定制化和风格统一,这些代码量都忽略不计。
7.3 枚举类(Enum)
💡 枚举就不多说了,很常见的使用,只不过Python中的枚举更加偏向于一个集合或者元组,不像Java中依旧存在构造方法等等,直接看例子。
from enum import Enum, auto# 创建一个枚举类
class HttpMethod(Enum):GET = auto() # auto() 自动分配唯一的整数值POST = auto()PUT = auto()DELETE = auto()def handle_request_safe(method: HttpMethod):if method is HttpMethod.GET: # 使用 is 进行身份比较,更安全高效print(f"处理 {method.name} 请求,值为 {method.value}")elif method is HttpMethod.POST:print(f"处理 {method.name} 请求,值为 {method.value}")# ...# --- 使用 ---
handle_request_safe(HttpMethod.GET) # 清晰、安全
# handle_request_safe("GET") # 类型检查器会报错!传入了错误的类型# 枚举的特性
print(HttpMethod.GET) # 输出: HttpMethod.GET
print(HttpMethod.GET.name) # 输出: 'GET' (名称)
print(HttpMethod.GET.value) # 输出: 1 (值)
print(list(HttpMethod)) # 可以遍历所有成员
但是在Python中可以直接在方法中使用类型校验,某个参数必须是枚举中的类型,增加了函数的可读性,这一点还是比较方便的,比起Java需要再枚举类中增加一个校验方法方便得多。
7.4 __slots__属性
💡 这是类中的一个变量,通过将一个含有所有变量的字符串元组赋值,可以达到锁定类变量的作用,有两个好处。
- 防止添加额外的属性。
- 预留内存,减少内存的占用(因为默认的
__dict__生成会占用大部分内存)。
默认的dict
class NormalObject:def __init__(self, x, y):self.x = xself.y = yobj = NormalObject(1, 2)
print(obj.__dict__) # 输出: {'x': 1, 'y': 2}# __dict__ 带来了动态性,可以随时添加新属性
obj.z = 3
print(obj.__dict__) # 输出: {'x': 1, 'y': 2, 'z': 3}
使用__slots__进行优化
import sysclass SlottedObject:# 声明实例将只有 'x' 和 'y' 两个属性__slots__ = ('x', 'y')def __init__(self, x, y):self.x = xself.y = yslotted_obj = SlottedObject(1, 2)# 1. 内存优化
# print(slotted_obj.__dict__) # AttributeError: 'SlottedObject' object has no attribute '__dict__'
# 在需要创建成千上万个小对象时,内存节省非常可观
obj_size = sys.getsizeof(NormalObject(1,2).__dict__)
slotted_obj_size = sys.getsizeof(slotted_obj)
print(f"一个普通对象字典的内存大约: {obj_size} bytes")
print(f"一个slotted对象的总内存大约: {slotted_obj_size} bytes") # 会小得多# 2. 属性限制
slotted_obj.x = 10 # OK
# slotted_obj.z = 3 # AttributeError: 'SlottedObject' object has no attribute 'z'
八、元编程基础
8.1 元类(Metaclasses)(了解)
💡 这是一个非常强大也是不经常使用的知识点,因为这多半在写框架或者组件的时候才使用的,是一个很底层的知识,核心就一点:可以干涉类创建的过程,在类创建前后加入个性化操作。
1. type函数
💡 这是一个非常强大的函数,同时也是创建者的顶端,所有的对象创建的顶层过程都是type,包括type自己,如果执行type(type),那么输出也是type自己。
💡 同时我们要知道,所有类的类型都是type,这里要区分一个知识点,“万物皆对象”这句话在Python同样适用,只不过从继承角度来说,所有的对象都是继承自Object,但是从创建角度来说,所有的对象都是通过type函数创建的。
class MyClass:x = 10def greet(self):print("Hello")# --- 让我们来探究 MyClass 的本质 ---
print(MyClass) # 输出: <class '__main__.MyClass'>
print(type(MyClass)) # 输出: <class 'type'># MyClass 是一个对象,它的类型是 'type'
# 就像 my_instance = MyClass(),my_instance 的类型是 MyClass 一样
2. 使用type()动态创建类
💡 type()不仅可以返回一个对象的类型,它还有一个更强大的三参数形式,可以用来动态地创建类:
type(name, bases, attrs)
- name: 类的名字 (字符串)
- bases: 父类的元组 (可以为空)
- attrs: 包含属性和方法的字典
# 这段动态代码
def greet_func(self):print("Hello from dynamic class")DynamicClass = type('DynamicClass', # 类的名字(), # 父类元组{'x': 10, 'greet': greet_func} # 属性和方法字典
)# ...等价于这段静态代码
# class DynamicClass:
# x = 10
# def greet(self):
# print("Hello from dynamic class")# --- 验证 ---
instance = DynamicClass()
print(instance.x) # 输出: 10
instance.greet() # 输出: Hello from dynamic class
print(type(instance)) # 输出: <class '__main__.DynamicClass'>
print(type(DynamicClass)) # 输出: <class 'type'>
3. 自定义元类来控制类的创建
# 1. 定义元类
class AllCapsMeta(type):# __new__ 在类对象创建之前被调用# cls: 元类本身 (AllCapsMeta)# name: 类的名字 (e.g., 'MyModel')# bases: 父类元组# attrs: 属性字典def __new__(cls, name, bases, attrs):print(f"--- Using AllCapsMeta to create class '{name}' ---")uppercase_attrs = {}for key, value in attrs.items():if not key.startswith('__'): # 忽略特殊方法uppercase_attrs[key.upper()] = valueelse:uppercase_attrs[key] = value# 调用父元类(type)的__new__方法来真正创建类,但使用我们修改后的属性字典return super().__new__(cls, name, bases, uppercase_attrs)# 2. 使用元类
class MyModel(metaclass=AllCapsMeta):# 我们在这里定义小写属性名table = 'users'limit = 10# --- 验证 ---
# 在类定义被读取时,元类的 __new__ 就会执行
# 输出: --- Using AllCapsMeta to create class 'MyModel' ---print(hasattr(MyModel, 'table')) # 输出: False
print(hasattr(MyModel, 'TABLE')) # 输出: True
print(MyModel.TABLE) # 输出: users
print(MyModel.LIMIT) # 输出: 10
关键点:
- 元类在类定义阶段就起作用,而不是在实例化阶段。
- 它拦截了类的创建过程,允许我们检查或修改类的名称、基类或属性字典。
- 这是一种非常强大的代码注入和转换机制,常见于框架(如Django ORM, SQLAlchemy)和库的设计中。
8.2 类装饰器
💡 平常我们大多数使用的是类装饰器,因为元类还是有些复杂的,主要是大材小用了,一般通过类装饰器就可以实现我们的需求,主要是方便、可控,其本质就是一个函数,实现也很方便。
# 装饰器函数
def add_creation_timestamp(cls): # 接收一个类 (cls)# 1. 获取原始的 __init__ 方法original_init = cls.__init__# 2. 定义一个新的 __init__ 方法def new_init(self, *args, **kwargs):from datetime import datetime# 在调用原始 __init__ 之前添加新功能self.created_at = datetime.now()print(f"{cls.__name__} instance created at {self.created_at}")# 调用原始的 __init__ 方法,以完成对象的初始化original_init(self, *args, **kwargs)# 3. 用新方法替换掉类的原始 __init__ 方法cls.__init__ = new_init# 4. 返回修改后的类return cls# --- 使用装饰器 ---
@add_creation_timestamp
class User:def __init__(self, name: str):self.name = name@add_creation_timestamp
class Product:def __init__(self, name: str, price: float):self.name = nameself.price = price# --- 验证 ---
user = User("Alice")
# 输出: User instance created at 2025-11-11 ...
print(user.name, user.created_at)import time
time.sleep(1)product = Product("Laptop", 1200.0)
# 输出: Product instance created at 2025-11-11 ...
print(product.name, product.created_at)
常见的应用场景
- 单例模式 (Singleton): 确保一个类只有一个实例。
- 注册插件: 自动将类注册到一个中央注册表中。
- 添加通用功能: 如日志、调试、数据校验等。
- @dataclass就是一个著名的类装饰器,它接收一个普通的类,然后给它添加__init__, __repr__等方法。
