Python技术迷

什么是 Python 类中的 self?

TypeError: UserCache.refresh() missing 1 required positional argument: 'self'

这个报错刚出来的时候,我第一眼不会去看业务逻辑。 Python 里出现这个,八成是类方法调用姿势不对,或者把实例方法当普通函数用了。

很多人学 Python 类的时候,最别扭的就是这个 self。

看着像关键字,其实不是。你把它改成 dongge,代码也能跑。

classUserCounter:
defadd(dongge, user_id):
        dongge.total += 1
        dongge.last_user = user_id

counter = UserCounter()
counter.total = 0
counter.add("u_10086")

print(counter.total)
print(counter.last_user)

能输出:

1
u_10086

所以 self 不是 Python 语法强制的名字,只是大家约定俗成这么写。 但我不建议乱改。线上代码里看到 dongge.xxx、that.xxx 这种东西,我一般会多看两眼,心里会有点不踏实。

问题来了,self 到底是谁?

看这段代码:

classOrderChecker:
defmark_checked(self, order_id):
        self.last_checked_order = order_id
        print("checked:", order_id)

checker = OrderChecker()
checker.mark_checked("O20260101001")

真正执行的时候,Python 并不是只把 "O20260101001" 传进去。

它会偷偷把 checker 这个对象也传进去,大概等价于:

OrderChecker.mark_checked(checker, "O20260101001")

所以方法里第一个参数 self,接到的就是当前这个对象。

这也是为什么类里面访问成员变量,要写:

self.last_checked_order = order_id

而不是:

last_checked_order = order_id

这俩不是一回事。

前者是给对象挂了一个属性。后者只是方法里的局部变量,方法跑完基本就没了。

我写个更像业务里的例子:

classImportJob:
def__init__(self, job_id):
        self.job_id = job_id
        self.success_count = 0
        self.fail_rows = []

defhandle_row(self, row):
ifnot row.get("phone"):
            self.fail_rows.append({
"row_no": row.get("row_no"),
"reason": "phone_empty"
            })
return

        self.success_count += 1

defreport(self):
return {
"job_id": self.job_id,
"success": self.success_count,
"failed": len(self.fail_rows)
        }


job = ImportJob("import_20260604")
job.handle_row({"row_no": 1, "phone": "13800000000"})
job.handle_row({"row_no": 2, "phone": ""})

print(job.report())

这里的 self.success_count、self.fail_rows 都是这个导入任务自己的状态。

再开一个任务,它们互不影响:

job_a = ImportJob("job_a")
job_b = ImportJob("job_b")

job_a.handle_row({"row_no": 1, "phone": ""})
job_b.handle_row({"row_no": 1, "phone": "13900000000"})

print(job_a.report())
print(job_b.report())

这地方如果你不用 self,两个任务的状态就没地方放。 你写成全局变量,那就是迟早埋雷。导入任务一并发,数据串了,排查起来很难看。

self 最容易踩的坑,是忘记写。

classTokenStore:
defsave(token):
        token.value = "abc"

这段代码看着像能用,其实调用时很容易炸:

store = TokenStore()
store.save("abc")

Python 会把 store 自动传给第一个参数 token,然后你又传了一个 "abc",参数数量就乱了。

正确写法应该是:

classTokenStore:
defsave(self, token):
        self.value = token

还有一种更隐蔽的坑,是把实例属性和局部变量混着写。

classPriceParser:
defparse(self, text):
        price = int(text.replace("元", ""))
        self.price = price

这里 price 是临时变量,self.price 才是对象属性。

如果后面别的方法要用价格,就得从 self.price 拿:

classPriceParser:
defparse(self, text):
        price = int(text.replace("元", ""))
        self.price = price

defis_expensive(self):
return self.price > 1000

但这段我其实不太喜欢。

因为 is_expensive() 依赖 parse() 先执行。调用顺序一乱,就会报:

AttributeError: 'PriceParser' object has no attribute 'price'

更稳一点,我会在初始化里把字段先摆出来:

classPriceParser:
def__init__(self):
        self.price = None

defparse(self, text):
        self.price = int(text.replace("元", ""))

defis_expensive(self):
if self.price isNone:
returnFalse
return self.price > 1000

这不是洁癖,是排障经验。 对象上到底有哪些字段,最好一眼能看到。别等跑到某个分支才动态冒出来一个属性。

再说一个常见误会:类里面的方法,不一定都要 self。

如果这个方法不需要访问对象状态,就可以写成静态方法:

classPhoneCleaner:
    @staticmethod
defclean(phone):
return phone.replace(" ", "").replace("-", "")

调用:

phone = PhoneCleaner.clean("138-0000-0000")
print(phone)

这种方法不关心某个具体对象,只是做一个清洗动作。 硬塞 self 进去也能写,但没必要。

如果方法要访问类本身,而不是某个对象,用 cls:

classRetryPolicy:
    default_limit = 3

    @classmethod
deffrom_config(cls, config):
        limit = config.get("retry_limit", cls.default_limit)
        policy = cls()
        policy.limit = limit
return policy

self 是当前对象。cls 是当前类。 静态方法啥也不自动收。

这三个别混。

我平时判断一个方法该怎么写,就看它用不用对象状态。

要读写 self.xxx,那就是实例方法:

defadd_error(self, row_no, reason):
    self.errors.append((row_no, reason))

只处理传入参数,不碰对象状态,那大概率是静态方法:

@staticmethod
defnormalize_name(name):
return name.strip().lower()

要根据类创建对象,或者读类变量,那才考虑类方法:

@classmethod
defempty(cls):
return cls()

self 没有玄学,它就是 Python 帮你把“当前这个对象”传进来了。

真正该注意的不是它叫什么,而是你有没有把状态放对地方。

该属于对象的,放 self.xxx。 只在当前函数用一下的,放局部变量。 跟对象没关系的,别假装有关系。

这几个分清楚,Python 类的代码就不会写得一团黏糊。