☰
Python中的self用法详解
2026/10/9 20:47:50 网站建设 项目流程

前言


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.8+
class Point:
def __init__(this, x, y): # 形参名故意叫 this
this.x = x
this.y = y

def norm(self): # 这个方法里叫 self
return (self.x ** 2 + self.y ** 2) ** 0.5


p = Point(3, 4)
print(p.norm()) # 推演输出:5.0
print(Point.norm(p)) # 推演输出:5.0

两个方法一个用this、一个用self,程序照跑不误。因为this和self只是位置上的第一个形参,名字本身没有任何魔法。


真正有魔法的是「点号访问 + 函数描述符」这套机制:


# 适用于 Python 3.8+
p = Point(3, 4)
print(p.norm) # 推演输出:<bound method Point.norm of ...>
print(p.norm.__self__ is p) # 推演输出:True
print(p.norm.__func__ is Point.__dict__["norm"]) # 推演输出:True

p.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.8+
class Temperature:
unit = "C"

def __init__(self, value):
self.value = value

def show(self): # 实例方法:第一个参数是实例
return f"{self.value}{self.unit}"

@classmethod
def from_fahrenheit(cls, f): # 类方法:第一个参数是类
return cls((f - 32) * 5 / 9)

@classmethod
def set_unit(cls, u):
cls.unit = u

@staticmethod
def is_valid(v): # 静态方法:不接收任何隐式参数
return -273.15 <= v


t = Temperature.from_fahrenheit(212)
print(t.show()) # 推演输出:100.0C
print(Temperature.is_valid(-300)) # 推演输出:False
print(t.is_valid(20)) # 推演输出:True

三个要点:



  1. 实例方法用self,因为它要读 / 写实例自己的数据(这里是self.value)。

  2. 类方法用cls,因为它操作的是类一级的数据(这里改了cls.unit)或者要做「备用构造器」(from_fahrenheit返回一个新实例)。

  3. 静态方法其实就是「放在类命名空间里的普通函数」。它不接收self也不接收cls。官方建议是:当你有个函数在概念上属于这个类、但不需要访问实例或类的数据时,用@staticmethod。如果它连类的概念都不需要,那它更适合当一个模块级函数。


注意Temperature.unit = "C"是类变量,所以set_unit改它会让所有实例都跟着变;这也正是类方法存在的价值。


四、cls 与 self 的区别


一句话:self是「一个实例」,cls是「一个类」。它们的名字同样是约定,同样只是「绑定进来的第一个参数」——只不过@classmethod绑定进来的是类而不是实例。


关键差别在继承时的表现:


# 适用于 Python 3.8+
class Plugin:
@classmethod
def create(cls):
return cls() # cls 是「实际被调用的那个类」

@staticmethod
def describe():
return "Plugin 的说明"


class AudioPlugin(Plugin):
pass


print(type(AudioPlugin.create()).__name__) # 推演输出:AudioPlugin
print(type(Plugin.create()).__name__) # 推演输出:Plugin
print(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不是关键字,而是「绑定进来的第一个参数」的惯例名;记住函数描述符与绑定方法这条链路,实例方法、类方法、静态方法三种形式就都能一眼看穿。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询