首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Python中的self用法详解
📅 2026/10/9 20:48:09
✍️ 爱科研究院
👁 阅读 3,247
前言self大概是 Python 初学者最先记住、又最先误解的东西。它的误解通常集中在三点以为self是关键字以为它必须叫self以为「方法里的self会自动变成一个实例」。真相要从描述符协议说起函数本身就是一种非数据描述符non-data descriptor它被定义在类里之后通过实例做点号访问时会自动返回一个「绑定方法bound method」把实例塞到第一个参数的位置上。你写下的self本质上只是那个被塞进来的第一个形参的名字。官方文档写得很直白按惯例实例参数叫self但它叫this或任何别的名字都可以。所以这是一条「约定」不是语法。本文专讲self它到底是什么、为什么必须是第一个参数、staticmethod/classmethod与实例方法三者的差别、以及cls与self的区别。代码按 Python 3.8 给出。一、self 只是一个形参名先看一段故意「不守规矩」的代码证明self不是关键字# 适用于 Python 3.8class Point:def __init__(this, x, y): # 形参名故意叫 thisthis.x xthis.y ydef norm(self): # 这个方法里叫 selfreturn (self.x ** 2 self.y ** 2) ** 0.5p Point(3, 4)print(p.norm()) # 推演输出5.0print(Point.norm(p)) # 推演输出5.0两个方法一个用this、一个用self程序照跑不误。因为this和self只是位置上的第一个形参名字本身没有任何魔法。真正有魔法的是「点号访问 函数描述符」这套机制# 适用于 Python 3.8p Point(3, 4)print(p.norm) # 推演输出bound method Point.norm of ...print(p.norm.__self__ is p) # 推演输出Trueprint(p.norm.__func__ is Point.__dict__[norm]) # 推演输出Truep.norm拿到的不是原函数而是一个绑定方法对象。它内部记着两样东西原始函数__func__以及被绑定的实例__self__。调用p.norm()时CPython 实际执行的是Point.norm(p)——实例被自动塞到第一个参数的位置这就是self的全部来源。二、为什么必须是第一个参数因为绑定机制永远是「把实例放到参数列表的最前面」没有例外。你写def norm(self)接收的就是这个被塞进来的实例你写成def norm(a, b)那么a拿到实例、b只能等调用者再传一个参数。于是下面三条规则是强行成立的写法实际调用形式结果p.method()type(p).method(p)正常实例占据第一个参数Point.method(p)同上实例是显式传的正常两种写法完全等价Point.method()少了实例这个参数TypeError参数个数不足再对照官方的绑定规则表三种方法在「从实例调用」和「从类调用」时的变换一目了然方法种类从实例obj.f(*args)调用从类Cls.f(*args)调用普通函数实例方法f(obj, *args)f(*args)staticmethodf(*args)f(*args)classmethodf(type(obj), *args)f(cls, *args)注意第二、三列普通函数从类调用时不再自动补参数。这就是「Point.method()会报参数不足」的原因——走类调用时没人给你补实例。三、实例方法、类方法、静态方法三兄弟对比# 适用于 Python 3.8class Temperature:unit Cdef __init__(self, value):self.value valuedef show(self): # 实例方法第一个参数是实例return f{self.value}{self.unit}classmethoddef from_fahrenheit(cls, f): # 类方法第一个参数是类return cls((f - 32) * 5 / 9)classmethoddef set_unit(cls, u):cls.unit ustaticmethoddef is_valid(v): # 静态方法不接收任何隐式参数return -273.15 vt Temperature.from_fahrenheit(212)print(t.show()) # 推演输出100.0Cprint(Temperature.is_valid(-300)) # 推演输出Falseprint(t.is_valid(20)) # 推演输出True三个要点实例方法用self因为它要读 / 写实例自己的数据这里是self.value。类方法用cls因为它操作的是类一级的数据这里改了cls.unit或者要做「备用构造器」from_fahrenheit返回一个新实例。静态方法其实就是「放在类命名空间里的普通函数」。它不接收self也不接收cls。官方建议是当你有个函数在概念上属于这个类、但不需要访问实例或类的数据时用staticmethod。如果它连类的概念都不需要那它更适合当一个模块级函数。注意Temperature.unit C是类变量所以set_unit改它会让所有实例都跟着变这也正是类方法存在的价值。四、cls 与 self 的区别一句话self是「一个实例」cls是「一个类」。它们的名字同样是约定同样只是「绑定进来的第一个参数」——只不过classmethod绑定进来的是类而不是实例。关键差别在继承时的表现# 适用于 Python 3.8class Plugin:classmethoddef create(cls):return cls() # cls 是「实际被调用的那个类」staticmethoddef describe():return Plugin 的说明class AudioPlugin(Plugin):passprint(type(AudioPlugin.create()).__name__) # 推演输出AudioPluginprint(type(Plugin.create()).__name__) # 推演输出Pluginprint(AudioPlugin.describe()) # 推演输出Plugin 的说明AudioPlugin.create()里cls拿到的是AudioPlugin而不是Plugin所以造出来的是AudioPlugin实例。这就是「备用构造器」为什么一定要用cls而不是写死类名——写死类名的话子类调用时会造出父类的实例。而describe是静态方法它不知道调用者是谁AudioPlugin.describe()与Plugin.describe()返回同一个字符串。静态方法里没有cls也没有self这一点要和类方法严格区分开。常见坑点1. 定义实例方法时忘写self。❌def show():然后里面用self.value调用时报NameError或参数不匹配。 ✅def show(self):第一个参数永远留给实例。2. 把self当成关键字不敢改名。❌ 以为写def __init__(this, x)会报语法错误。 ✅self只是惯例名换成任何合法标识符都能跑但为了可读性团队里应统一用self。3. 在静态方法里使用self。❌staticmethod修饰的方法里写self.x报NameError。 ✅ 静态方法不接收隐式参数需要数据就通过参数显式传入或改用实例方法。4. 通过类名调用实例方法却忘了传实例。❌Point.norm()报TypeError: norm() missing 1 required positional argument。 ✅ 要么p.norm()要么Point.norm(p)把实例显式补上。5. 在类方法里写死类名而不是用cls。❌classmethod里写return Base()子类调用时造出的是父类实例。 ✅ 写return cls()让它跟随实际调用的类。6. 在__init__里把属性名拼错之后才报错。❌self.vlaue value然后在show()里访问self.value构造时不报错、用的时候才AttributeError。 ✅ 赋值和读取用同一个拼写__slots__能帮你在赋值阶段就把不存在的属性名挡下来。7. 以为self会自动创建实例属性。❌ 方法里只写了self.total 1没赋值期待属性凭空出现。 ✅ 实例属性是靠self.名字 值这个赋值动作创建的只读不写不会创建任何东西。总结概念是什么由谁绑定典型用途self第一个形参按约定叫self实例方法自动把实例传进来读写实例自己的数据cls第一个形参按约定叫cls类方法自动把类传进来备用构造器、改类级数据staticmethod不接收隐式参数的普通函数什么都不绑定与类概念相关但不依赖实例 / 类数据绑定方法对象函数 实例的组合函数描述符在点号访问时生成让p.m()等价于Cls.m(p)一句话收尾self不是关键字而是「绑定进来的第一个参数」的惯例名记住函数描述符与绑定方法这条链路实例方法、类方法、静态方法三种形式就都能一眼看穿。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/9 20:48:09
Python中的super().__init__()用法详解
2026/10/9 20:43:07
技术周榜深度拆解:开发工具、AI与前端项目的选型逻辑与避坑指南
2026/10/9 20:43:07
Codex 插件实战:12 个提升 AI 编程效率的必备工具
2026/10/9 22:33:27
Laravel Eloquent 底层机制全攻略:从模型思维到性能优化实践
2026/10/9 22:33:27
Rust递归数据结构内存布局:Box堆分配与栈上大数组的取舍
2026/10/9 22:33:27
Matlab魔术公式轮胎模型实现与参数辨识指南
2026/10/9 22:33:27
Vibe Coding 必须知道的 7 个工具平台:从 GitHub 到 TaoToken 的完整链路
2026/10/9 22:33:27
时间不是物理量,而是必须显式约定的系统契约
2026/10/9 22:28:26
Python协同过滤推荐系统实战:从算法到Vue前端
2026/10/9 0:01:35
RISC-V裸机启动全流程:从复位向量到main函数的七步实现
2026/10/9 0:01:35
Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南
2026/10/9 0:01:35
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错
2026/10/8 5:02:14
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/9 1:10:43
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/9 3:31:49
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/8 4:30:43
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/9 3:32:01
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/9 11:36:17
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)