news 2026/10/30 2:54:57

深入浅析Python的类

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入浅析Python的类

前言


很多初学者把 Python 的类(class)理解成"把函数和变量装进一个盒子",于是写出了能跑但语义混乱的代码:类属性当实例属性用、self忘了写、继承关系理不清。真正理解类,需要知道三件事——类本身也是一个对象、属性查找有一套明确的优先级、类体(class body)在定义时就被执行了一次。


另一个常见误解是"类只是组织代码的语法糖"。事实并非如此:class语句运行时会新建一个命名空间(namespace),执行类体里的语句,然后用type构造出一个类对象。换句话说,类定义是运行时行为,不是编译期标记。


本文从数据模型(data model)的角度拆解类的构造过程、命名空间、属性查找顺序与绑定方法(bound method),并给出可以直接复制运行的示例。


一、class 语句到底做了什么


class语句的执行过程大致是三步:



  1. 解析类体的字节码,准备一个新的命名空间(实际就是一个字典);

  2. 在这个命名空间里按顺序执行类体内的所有语句(赋值、函数定义、if、循环等都会真的执行);

  3. 调用元类(默认是type)来创建类对象,类名绑定到当前作用域。


因为第 2 步会真的执行,所以类体里的表达式会在导入或定义的那一刻求值一次,而不是每次实例化时求值:


# 适用于 Python 3.8+
import time


def make_stamp():
print("类体被执行了一次")
return time.time()


class Config:
created_at = make_stamp() # 这一行在定义类时就跑了

这里的make_stamp()只会被调用一次。类对象创建后,Config.created_at是一个固定值。如果误以为它每次实例化都刷新,就会写出过期的配置。


类创建好之后,类的命名空间可以从__dict__看到,它是一个映射代理(mappingproxy):


# 适用于 Python 3.8+
class Point:
"""一个二维点。"""
dim = 2

def __init__(self, x, y):
self.x = x
self.y = y


p = Point(1, 2)
print(Point.__dict__["dim"]) # 2
print("__dict__" in Point.__dict__) # False
print(vars(p)) # {'x': 1, 'y': 2}

注意Point.__dict__里没有__dict__这个键,但实例p有__dict__(除非用了__slots__)。vars(p)等价于p.__dict__。


二、属性查找的优先级


实例属性访问obj.name时,查找顺序是:



  1. 先查类型(type)的 MRO,找数据描述符(data descriptor,定义了__set__或__delete__)。找到就直接用;

  2. 再查实例的__dict__;

  3. 最后查类型 MRO,找非数据描述符或普通类属性。


这个顺序解释了两个经典现象:@property能挡住实例属性赋值(数据描述符优先),而实例属性可以覆盖普通类属性(实例字典优先于类的普通属性)。


# 适用于 Python 3.8+
class Base:
name = "base"


class Child(Base):
pass


c = Child()
print(c.name) # base(沿 MRO 找到 Base.name)
c.name = "child"
print(c.name) # child(进入实例字典)
print(Child.name) # base(类属性没变)
del c.name
print(c.name) # base

再看看方法绑定。从类上取方法得到的是函数,从实例上取方法得到的是绑定方法(bound method),它把self记住了:


# 适用于 Python 3.8+
class Greeter:
def hello(self):
return "hi"


g = Greeter()
print(Greeter.hello) # <function ...>
print(g.hello) # <bound method ...>
print(g.hello()) # hi
print(g.hello.__self__) # <__main__.Greeter object ...>



访问方式得到的东西是否需要手动传 self



Greeter.hello函数(function)需要,Greeter.hello(g)

g.hello绑定方法(bound method)不需要

Greeter.__dict__["hello"]函数需要



三、类属性、实例属性与可变对象


最容易踩的坑是可变的类属性被所有实例共享。因为类属性只存在一份,放在类上:


# 适用于 Python 3.8+
class Bad:
items = [] # 类属性,全类共享

def add(self, x):
self.items.append(x) # 原地修改的是类属性,不是新建实例属性


a = Bad()
b = Bad()
a.add(1)
print(b.items) # [1] —— b 也"被改了"
print(a.items is b.items) # True

self.items.append(...)只做了属性读取再原地修改,没有给self.items赋值,所以不会写入实例字典。正确做法是在__init__里给每个实例建自己的列表:


# 适用于 Python 3.8+
class Good:
def __init__(self):
self.items = [] # 每个实例一份

def add(self, x):
self.items.append(x)


a = Good()
b = Good()
a.add(1)
print(b.items) # []
print(a.items) # [1]

四、类的相等性与可哈希性


默认情况下,用户定义的类用身份(identity)比较:两个不同实例永远不相等。如果想按内容比较,就定义__eq__。但要小心一个联动规则:定义__eq__而没有定义__hash__,__hash__会被自动设为None,实例变成不可哈希(unhashable)。


# 适用于 Python 3.8+
class WithEq:
def __init__(self, v):
self.v = v

def __eq__(self, other):
return isinstance(other, WithEq) and self.v == other.v


w = WithEq(1)
print(w == WithEq(1)) # True
try:
hash(w)
except TypeError as e:
print("TypeError:", e) # unhashable type: 'WithEq'

如果这个类的实例需要放进set或当字典键,就必须同时显式定义__hash__,并且保证相等的对象哈希值相同:


# 适用于 Python 3.8+
class Key:
def __init__(self, v):
self.v = v

def __eq__(self, other):
return isinstance(other, Key) and self.v == other.v

def __hash__(self):
return hash(self.v)


print(hash(Key(1)) == hash(Key(1))) # True
print(len({Key(1), Key(1)})) # 1

常见坑点



  1. 可变类属性当实例属性用


❌


``python # 适用于 Python 3.8+ class Box: data = {}``


✅


``python # 适用于 Python 3.8+ class Box: def __init__(self): self.data = {}``



  1. 忘了写self形参


❌


``python # 适用于 Python 3.8+ class A: def f(): # 调用时参数数量不匹配 return 1``


✅


``python # 适用于 Python 3.8+ class A: def f(self): return 1``



  1. __init__写了return却返回非None


❌


``python # 适用于 Python 3.8+ class A: def __init__(self): return 5 # TypeError: __init__() should return None``


✅


``python # 适用于 Python 3.8+ class A: def __init__(self): self.value = 5``



  1. 用is比较值相等的对象


❌


``python # 适用于 Python 3.8+ a = WithEq(1) b = WithEq(1) print(a is b) # False,身份不同``


✅


``python # 适用于 Python 3.8+ print(a == b) # True,走 __eq__``



  1. 定义了__eq__还指望能放进set


❌


``python # 适用于 Python 3.8+ class C: def __eq__(self, other): return True # hash(C()) 会 TypeError``


✅


```python # 适用于 Python 3.8+ class C: def __eq__(self, other): return isinstance(other, C)


def __hash__(self): return 0 ```



  1. 在类体里引用同类的前一个类属性


❌


``python # 适用于 Python 3.8+ class C: base = 10 derived = base * 2 # NameError:类体不是闭包作用域``


✅


```python # 适用于 Python 3.8+ class C: base = 10


C.derived = C.base * 2 ```



  1. 用type(x) == T判类型,漏掉子类


❌


``python # 适用于 Python 3.8+ print(type(True) == int) # False,bool 是 int 的子类``


✅


``python # 适用于 Python 3.8+ print(isinstance(True, int)) # True``


总结




概念关键结论



class 语句运行时执行类体,用元类构造类对象

属性查找数据描述符 → 实例字典 → 类 MRO

类属性 vs 实例属性类上只有一份;可变类属性会被所有实例共享

方法绑定函数 vs 绑定方法,绑定方法自带self

__eq__与__hash__定义__eq__会把__hash__置为None

实例命名空间vars(obj)即obj.__dict__



类不是语法糖,而是一套可推理的运行时机制:类体只执行一次,属性查找有固定优先级,相等性与哈希性互相牵制。把这三条记牢,绝大多数"奇怪的行为"都能自己解释清楚。写继承和比较逻辑时,优先问一句"这个属性到底存在类上还是实例上"。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 2:54:55

SDH传输网入门:STM-1帧结构、3-7-3复用与易错点解析

简介&#xff1a;这份华为HCIA-Transmission V2.0培训课程PDF面向准备华为传输认证、希望系统梳理传送网知识体系的工程师与学员&#xff0c;围绕华为传输产品与网络演进展开&#xff0c;串联PDH的局限、SDH/WDM/OTN/PTN/RTN等主流体制&#xff0c;并落到STM-1 155.520Mb/s速率…

作者头像 李华
网站建设 2026/10/30 2:54:27

Linux进程通信与同步实验:从fork到信号量的六个实战场景

简介&#xff1a;这份资源是西安电子科技大学操作系统课程的实验报告文档&#xff0c;面向正在学习操作系统原理、需要完成Linux环境下进程与线程编程实验的高校学生。内容围绕进程管理、线程同步与进程间通信展开&#xff0c;涵盖进程的建立、线程共享进程数据、信号通信、匿名…

作者头像 李华
网站建设 2026/10/30 2:53:40

MoE+MLA大模型落地:推理显存、长文本与纯RL训练

简介&#xff1a;一份围绕DeepSeek大模型技术突破的PDF专题资料&#xff0c;面向AI研究者、开发者、投资人及对大模型原理感兴趣的学习者&#xff0c;聚焦MoE架构、MLA机制、强化学习训练路线与R1/V3等标志性成果的解析。内容从诞生背景与幻方量化孵化背景切入&#xff0c;系统…

作者头像 李华
网站建设 2026/10/30 2:53:36

PyTorch张量本质与训练循环底层机制详解

简介&#xff1a;《Deep Learning with PyTorch》是 Manning 出版社推出的经典英文原版教材&#xff0c;面向具备一定 Python 基础、希望系统掌握 PyTorch 与深度学习实践的开发者与学习者&#xff0c;帮助读者从框架基础一路进阶到模型训练、分布式与部署等工程环节。全书围绕…

作者头像 李华
网站建设 2026/10/30 2:51:39

DeepSeek-R1本地部署实战:Ollama一键推理落地指南

简介&#xff1a;本资源是一份面向AI开发者与研究人员的DeepSeek大语言模型本地部署实战指南&#xff0c;聚焦Ollama平台安装、DeepSeek-R1多版本模型下载与运行、Docker容器化管理及Open WebUI图形界面搭建全流程&#xff0c;解决个人设备上低成本、低门槛运行主流开源LLM的核…

作者头像 李华
网站建设 2026/10/30 2:51:08

多帧超分辨率重构实战:亚像素配准、融合与迭代反投影

简介&#xff1a;这份代码与数据包聚焦多帧图像超分辨率重构&#xff0c;面向具备图像处理基础、希望从理论走向MATLAB实现的学习者和开发者&#xff0c;着重解决低分辨率多帧图像融合、恢复与质量评估的落地问题。压缩包共399个文件、约15.38MB&#xff0c;以大量低分辨率与参…

作者头像 李华