Python 类属性与实例属性自增差在哪?一个计数例子讲透区别

编程狮(w3cschool.cn) 2026-10-19 12:32:40 浏览数 (74)
反馈

Python 类属性与实例属性在自增时最大的差别,是看清楚你自增的到底是谁:类属性属于类、被所有实例共享,属于对象的属性各自独立。最容易被坑的一点是,在实例上写 self.count += 1 并不会真正改动共享的类属性,而是悄悄给这个实例新建了一个同名属性,把类属性遮蔽掉。你如果正被计数对不上困扰,多半就是踩了这个遮蔽陷阱。下面用一个计数器的例子,把直觉、原理、自增差异和排查方法一次讲透。

Python 类属性与实例属性差在哪

先看结论

类属性与实例属性的自增差异,本质是对「共享」和「独立」的理解,搞混就会得到意外的计数。

  • 类属性定义在类体里,归所有实例共享;实例属性通常在 __init__ 里用 self.x 定义,每个对象一份;
  • 在实例上写 self.count += 1,Python 会先读类属性再写回,结果是给该实例新建实例属性,类属性原值不变;
  • 要真正让所有实例共享自增,必须用 type(self).count += 1 或 类名.count += 1;
  • 排查时看 实例.__dict__,能立刻判断到底改的是哪一个。

想先把 Python 的类与对象基础补顺,可以先过 Python3 教程 的面向对象章节。

一、先建立直觉:两类属性分别住在哪

理解自增差异之前,先想清楚属性存在哪里。这块直觉对了,后面所有现象都能解释通。

1.1 类属性属于类,实例属性属于对象

类属性写在 class 缩进块里、不在任何方法内部,它属于这个类本身,所有实例都能看到同一份。实例属性用 self.x = ... 赋值产生,只属于「这一个」对象。打个比方:类属性像公司公告栏,谁看都是同一张;属于实例的那份属性像员工自己的笔记本,各写各的。

1.2 从命名空间看差异

下面这段做的是创建一个类属性与一个挂在实例上的属性,再分别打印类和实例的命名空间,直观看到它们住在不同的地方。

class Demo:
    class_attr = "我是类属性"

d = Demo()
d.instance_attr = "我是实例属性"
print("类命名空间:", Demo.__dict__.get("class_attr"))
print("实例命名空间:", d.__dict__)

上面这段的预期正常输出是:第一行打印 类命名空间: 我是类属性,第二行打印 实例命名空间: {'instance_attr': '我是实例属性'}。可以看到,类属性在 Demo.__dict__,实例那份属性只在 d.__dict__。关于字典与对象存储的细节,可以对照 Python 速查手册 的命名空间部分。

二、自增时差在哪:关键看你自增的是谁

这是全文的核心。同样是「加一」,写在类上还是实例上,结果天差地别。

2.1 自增类属性看起来像共享,其实常被遮蔽

很多初学者写出 self.count += 1,以为所有实例会一起涨。但 Python 的执行顺序是:先读取(实例没有就向上找类属性),再做 +=,最后把结果写回 self.count。写回这一步发生在实例上,于是凭空多出一个实例属性,把类属性盖住了。

2.2 用计数器看清三种自增

下面这段做的是定义一个带类属性 count 的计数器,在实例上自增一次,分别打印实例、另一个实例和类本身的值。

class Counter:
    count = 0  # 类属性,所有实例共享

    def tick(self):
        self.count += 1   # 注意:这会创建实例属性并遮蔽类属性

a = Counter()
b = Counter()
a.tick()
print("a.count =", a.count)                # 1
print("b.count =", b.count)                # 0
print("Counter.count =", Counter.count)    # 0  <- 类属性没被改

上面这段的预期正常输出是三行:a.count = 1、b.count = 0、Counter.count = 0。a 拿到了 1,是因为 a 自己多了个实例属性;b 和类本身仍然是 0,证明共享的类属性根本没动。想亲手改数验证,可以到 Python 在线运行 里跑一遍。

2.3 真正共享的自增该怎么写

如果目标是所有实例一起涨,应该显式自增类属性,而不是依赖 self。下面这段把 tick 改成自增类属性,两个实例各调一次后,类属性会变成 2。

class Counter:
    count = 0

    def tick_shared(self):
        type(self).count += 1   # 真正自增类属性(等价于 Counter.count += 1)

a = Counter()
b = Counter()
a.tick_shared()
b.tick_shared()
print("Counter.count =", Counter.count)    # 2

上面这段的预期输出是 Counter.count = 2,两个实例的操作累加到同一个类属性上。这里 type(self).count 明确指向类,避开了实例侧属性的遮蔽。关于更复杂的对象模型,可以读 Python 学习 的进阶部分。

类属性与实例属性在自增时的命名空间变化示意

三、边界与验证:怎么确认你改的是哪一个

代码能跑不代表你改对了「那一个」属性。尤其在多人协作或重构时,必须能快速验证。

3.1 用 __dict__ 验证改动落点

判断自增到底落在哪,最直接的就是看实例自己的命名空间有没有多出属性。下面这段在 a.tick() 之后打印 a.__dict__ 和身份比较。

# 排查:自增到底改了谁
print("a 自己的属性:", a.__dict__)            # {'count': 1},实例被悄悄加了属性
print("是否与类共享:", a.count is Counter.count)  # False

上面这段的预期输出是:a 自己的属性: {'count': 1} 以及 是否与类共享: False。a.__dict__ 里出现了 count,说明自增落在了实例上;is 比较为 False 进一步证明实例值和类属性不是同一个对象。这就是「遮蔽」的实锤。

3.2 失败分支:误以为共享导致计数错乱

最常见的失败场景是:你以为 self.count += 1 在共享计数,于是用一堆实例去累加,期待类属性是总数,结果类属性始终是 0,而每个实例各拿到一个从 1 开始的数。处理方式是先想清语义——要全局计数就用 type(self).count += 1;要每实例独立计数,就老老实实写 self.count = 0 在 __init__ 里初始化实例上的属性,别借用类属性当初值。

四、什么时候用类属性、什么时候用实例属性

一句话原则:所有对象共用、且不会改变的放进类属性,比如配置项、默认阈值;每个对象各自不同、或会独立变化的放进实例那边的属性,比如用户昵称、本次会话状态。自增计数要不要共享,决定了你写在 类名. 还是 self. 上。拿不准时,用第三节的 __dict__ 验证法跑一次,比反复猜更高效。

Python 类属性自增示例

总结

Python 类属性与实例属性的自增差异,根源在于「类属性共享、实例那边独立」,以及 Python 在 self.x += 1 时会先读后写在实例上落地。踩坑几乎都出在「以为实例自增会改共享值」这一错觉上。

要点带走:

  • 类属性写在类体里、被所有实例共享,实例上的属性用 self.x 定义、各一份;
  • self.count += 1 会创建实例上的属性并遮蔽类属性,类属性原值不变;
  • 真正共享自增要写 type(self).count += 1 或 类名.count += 1;
  • 用 实例.__dict__ 和 is 比较,能立刻确认自增落在哪。

下一步建议把第二节的两个计数器类敲进 Python 在线运行环境里对照跑一遍,把输出和本文结论对上号。

常见问题

Q:为什么我在实例上改了值,另一个实例没跟着变?

A:因为你触发了实例侧属性遮蔽。在实例上赋值或 += 会新建一个属于该实例的属性,覆盖掉类属性的读取,类属性本身没动,所以别的实例看到的还是类上的原值。

Q:self.count += 1 和 Counter.count += 1 到底差什么?

A:前者落在实例上,后者落在类上。前者每个对象各加各的,后者所有实例共享同一个累加结果。语义完全不同,用错就会得到错的计数。

Q:类变量和类属性是一回事吗?

A:在 Python 语境里常说类变量,也就是这里说的类属性,都是定义在类体、归类所有、被实例共享的那一份。文章里两个词指同一事物。

Q:实例变量和实例属性是一回事吗?

A:是的。实例变量就是挂在实例上的属性,指通过 self.x = ... 绑定到单个对象上的那一份数据,每个实例互相独立。

Q:怎么避免不小心遮蔽类属性?

A:自增共享计数时显式写 type(self).count += 1;而每个实例独立的状态,在 __init__ 里用 self.x = 初值 明确初始化,不要借类属性当默认值。

延伸学习

想沿着这条路径继续补,可以按这个顺序来:

  1. 系统补一遍 Python 与面向对象基础,Python 零基础到高薪就业 覆盖了从语法到项目的完整链路;
  2. 想看类与对象更系统的讲解,读一篇相关笔记 Python 类与对象;
  3. 概念想再展开,可以看 Python 教程 的面向对象章节。

0 人点赞