当前位置: 首页 > news >正文

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表示一个空类块
  1. Python中类的命名也采用大驼峰命名,但是方法是蛇形命名。
  2. 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查找属性的顺序是

  1. 实例变量 (instance.dict)
  2. 类变量 (Class.dict)
  3. 父类变量 (按MRO顺序,Class.mro)
  4. 模块变量 (module globals)
  5. 内置变量 (builtins.dict)

Java对比

概念PythonJava
实例级别实例变量实例字段(非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

问题来了,既然和普通方法基本一致,为什么要定义在类的内部,这样不是更加的麻烦吗?而且为什么不直接使用普通的方法呢?

  1. 代码维护性更高,可读性更好,明确知道这个方法和某个类有关联。
  2. 从编译角度来说,少了self参数,稍微会减少字节码
  3. 从类的继承角度来说比较友好,行为明确。

三、核心特殊方法(魔术方法)

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就出现了。

  1. 创建小而专注的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__
  1. 在你的主类中混入这些功能
# 一个完全独立的类
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__属性

💡 这是类中的一个变量,通过将一个含有所有变量的字符串元组赋值,可以达到锁定类变量的作用,有两个好处。

  1. 防止添加额外的属性。
  2. 预留内存,减少内存的占用(因为默认的__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__等方法。
http://www.dtcms.com/a/596946.html

相关文章:

  • 0.SAP契约锁业务需求
  • 【ZeroRange WebRTC】HTTPS 与 WSS 在 WebRTC 场景中的应用
  • 12.【Linux系统编程】动静态库制作与使用
  • 快速seo整站优化排行网站外部链接
  • 认识设计模式——单例模式
  • OCP(Over-Current Protection)是什么?
  • wordpress门户网站模板东莞 网站建设多少钱
  • 【论文阅读】PEARL A dual-layer graph learning for multimodal recommendation
  • 《反电信网络诈骗法》“金融篇”
  • 【Qt开发】布局管理器(五)-> QSpacerItem 控件
  • 创邻科技“知寰 Hybrid RAG”强势落地复杂业务场景:GraphRAG产品引领公安与金融智能决策新范式
  • 零基础也能搭博客?
  • Electron 颜色拾取器开发实战适配鸿蒙
  • 电影网站建设需求分析百度高级搜索页面
  • 猫眼网站建设大连seo建站公司
  • 基于微信小程序的丽江市旅游分享平台
  • 哪些网站做任务可以赚钱红谷滩园林建设集团有限公司 网站
  • 云服务器镜像是什么?4类镜像全解析
  • Nginx介绍和部署
  • ffmpeg-本周任务-01
  • 防邪办网站建设方案文档许昌网站建设哪家最好
  • 铜仁网站建设哪家专业网站建设中模板代码
  • 关于ankh库加载本地模型的改进用于解决服务器无法连接外网的问题
  • 基于springboot的旅游攻略网站设计与实现
  • Haldane先验:极端无知假设下的贝叶斯推断
  • 15.【NXP 号令者RT1052】开发——实战-XBAR
  • 中小型网站建设与管理总结小超人成都网站建设
  • MATLAB | 如何使用MATLAB一键生成拼豆图纸
  • 如何设计一个高扩展的加密狗集成策略
  • zoho crm 如何设置富文本字段为必填