Skip to content

Python 实例属性和类属性:个人储物柜与班级公告栏

引言:两种储物方式

想象你走进一家公司:

  • 个人储物柜:每个人一个柜子,里面放自己的私人物品——你的水杯、我的充电器、他的外套。柜子里的东西互不干扰。
  • 公司公告栏:贴在走廊墙上,所有人看到的是同一张公告。行政改一次,全公司都看到新内容。

Python 的类也有这两种"储物方式":

  • 实例属性:属于每个具体的实例(对象),互不干扰;
  • 类属性:属于类本身,所有实例共享。

搞混这两种属性,是初学者最容易踩的坑之一。


一、实例属性:每个实例的私人储物柜

1.1 通过 self 绑定

python
class Student(object):
    def __init__(self, name):
        self.name = name    # 实例属性,存在每个实例自己身上

s = Student('Bob')
print(s.name)   # Bob

self.name = name 把数据绑定到当前这个实例上。每个实例独立:

python
s1 = Student('Alice')
s2 = Student('Bob')

print(s1.name)   # Alice
print(s2.name)   # Bob

改一个不影响另一个:

python
s1.name = 'Charlie'
print(s1.name)   # Charlie
print(s2.name)   # Bob(没变)

1.2 通过实例变量动态绑定

Python 是动态语言,创建后还可以随时"贴标签":

python
s = Student('Bob')
s.score = 90      # 动态绑定新属性
print(s.score)    # 90

这个 score 只存在于 s 这个实例上,其他实例没有:

python
s2 = Student('Alice')
print(s2.score)   # AttributeError: 'Student' object has no attribute 'score'

生活化理解self.name 是出厂时就装好的储物柜;s.score 是你后来自己挂的小袋子——只有你这间柜子有。


二、类属性:全班的公共公告栏

2.1 在类中直接定义

python
class Student(object):
    name = 'Student'    # 类属性,属于类本身

注意:没有 self.,直接写在类体里。

2.2 实例和类都能访问

python
>>> s = Student()
>>> print(s.name)        # Student(实例没找到,去类里找)
>>> print(Student.name)  # Student(直接访问类属性)

实例访问 s.name 时,Python 的查找顺序:

  1. 先看实例自己有没有 name
  2. 没有?去类里找;
  3. 类里也没有?报错 AttributeError

2.3 类属性的典型用途:统计实例数量

python
class Student(object):
    count = 0    # 类属性:统计创建了多少个学生

    def __init__(self, name):
        self.name = name
        Student.count += 1   # 每创建一个实例,计数+1

测试:

python
>>> print(Student.count)   # 0

>>> bart = Student('Bart')
>>> print(Student.count)   # 1

>>> lisa = Student('Lisa')
>>> print(Student.count)   # 2

所有实例共享同一个 count——bart 和 lisa 操作的是同一个数字。

生活化理解:公告栏上写着"本班已报到 2 人",每个学生来报到,行政就在公告栏上改数字。所有人看到的是同一张公告。


三、关键陷阱:同名属性会"屏蔽"

3.1 实例属性屏蔽类属性

python
class Student(object):
    name = 'Student'    # 类属性

s = Student()

>>> print(s.name)        # Student(访问类属性)
>>> s.name = 'Michael'   # 给实例绑定同名属性
>>> print(s.name)        # Michael(实例属性屏蔽了类属性!)
>>> print(Student.name)  # Student(类属性还在,只是被挡住了)

3.2 删除实例属性后,类属性"重现"

python
>>> del s.name           # 删除实例的 name
>>> print(s.name)        # Student(类属性又出现了)

3.3 为什么这是"陷阱"?

python
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        # ❌ 错误:self.count += 1 创建的是实例属性!
        # 它屏蔽了类属性 count,类里的 count 永远是 0

# 正确写法:
# Student.count += 1

如果你不小心写成 self.count += 1,每个实例都会创建一个自己的 count(值是 1),而类的 count 始终是 0——统计完全失效。

生活化理解:公告栏上写着"已报到 0 人",你自己柜子里贴了一张"已报到 1 人"。别人看公告栏还是 0,你看自己柜子以为报到了——信息不对称,全乱套。


四、实例属性 vs 类属性:一张表看懂

对比项实例属性类属性
定义位置__init__self.xxx类体中直接写 xxx = ...
归属每个实例独立拥有类本身,所有实例共享
访问方式实例.xxx类名.xxx实例.xxx
修改影响只影响当前实例影响所有实例
内存每个实例一份只有一份
典型用途实例的个性化数据(姓名、成绩)共享配置、计数器、常量

五、常见误区与避坑指南

5.1 误区一:用 self.xxx 修改类属性

python
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        self.count += 1   # ❌ 创建实例属性,屏蔽类属性

s1 = Student('A')
s2 = Student('B')
print(Student.count)   # 0(没变化!)
print(s1.count)        # 1(实例自己的)
print(s2.count)        # 1(实例自己的)

修正:修改类属性必须用 类名.xxx

python
def __init__(self, name):
    self.name = name
    Student.count += 1   # ✅ 操作类属性

5.2 误区二:在类属性中放可变对象

python
class Student(object):
    scores = []    # ❌ 危险!所有实例共享同一个列表

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

s1 = Student('A')
s2 = Student('B')

s1.scores.append(90)
print(s2.scores)   # [90] —— s2 也"看到"了!

所有实例共享同一个列表,改一个全变。

修正:可变对象放 __init__ 里作为实例属性:

python
def __init__(self, name):
    self.name = name
    self.scores = []    # ✅ 每个实例独立

5.3 误区三:以为 del 实例属性 会删除类属性

python
class Student(object):
    name = 'Student'

s = Student()
s.name = 'Michael'

del s.name
print(s.name)          # Student(类属性还在!)
print(Student.name)    # Student

del s.name 只删实例属性,类属性纹丝不动。想删类属性用 del Student.name

5.4 误区四:把类属性当"全局变量"滥用

python
class Config(object):
    debug = True
    timeout = 30

# 到处改
Config.debug = False

类属性确实可以当全局配置用,但缺乏封装、容易被意外修改。建议:

  • @property 提供只读访问;
  • 或改用模块级常量(config.py 里写 DEBUG = True)。

六、实战练习

练习 1:实例计数器

完成下面的 Student 类,每创建一个实例,count 自动加 1:

python
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        # 你的代码

# 测试
if Student.count != 0:
    print('测试失败!')
else:
    bart = Student('Bart')
    if Student.count != 1:
        print('测试失败!')
    else:
        lisa = Student('Lisa')
        if Student.count != 2:
            print('测试失败!')
        else:
            print('Students:', Student.count)
            print('测试通过!')
参考答案
python
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        Student.count += 1   # 关键:用类名操作,不是 self

练习 2:共享配置与独立数据

设计一个 Car 类:

  • 类属性 wheels = 4(所有汽车默认 4 轮)
  • 实例属性 brandspeed
  • 方法 show_info() 打印品牌和车轮数
python
c1 = Car('BMW')
c2 = Car('Audi')
c1.show_info()   # BMW: 4 wheels
c2.show_info()   # Audi: 4 wheels

Car.wheels = 6   # 改成 6 轮(概念车)
c1.show_info()   # BMW: 6 wheels
c2.show_info()   # Audi: 6 wheels
参考答案
python
class Car(object):
    wheels = 4    # 类属性:所有车共享

    def __init__(self, brand):
        self.brand = brand   # 实例属性:各自独立
        self.speed = 0

    def show_info(self):
        print(f'{self.brand}: {Car.wheels} wheels')

注意 Car.wheels = 6 修改后,所有实例都看到新值——这就是类属性的共享特性。

练习 3:找出 bug

下面代码想统计创建了多少个实例,但结果始终是 0,找出原因:

python
class Student(object):
    count = 0

    def __init__(self, name):
        self.name = name
        self.count += 1

s1 = Student('A')
s2 = Student('B')
print(Student.count)   # 期望 2,实际 0
参考答案

self.count += 1 创建的是实例属性 count,它屏蔽了类属性。

执行过程:

  1. self.count 先读取——实例没有,找到类属性 count = 0
  2. self.count = 0 + 1 = 1——给实例创建了一个 count = 1
  3. 类属性 Student.count 始终是 0。

修复

python
def __init__(self, name):
    self.name = name
    Student.count += 1   # 明确操作类属性

七、小结

  1. 实例属性通过 self.xxx 绑定,属于各个实例,互不干扰——像个人储物柜;
  2. 类属性在类体中直接定义,属于类本身,所有实例共享——像班级公告栏;
  3. 访问顺序:实例属性优先,找不到才找类属性——同名会屏蔽;
  4. 修改类属性必须用 类名.xxxself.xxx 会创建实例属性导致屏蔽;
  5. 类属性别放可变对象(如列表),否则所有实例共享同一个,改一个全变;
  6. 典型用途:实例属性存个性化数据,类属性存共享配置和计数器。

分清"个人储物柜"和"班级公告栏",你的类设计就会清晰、安全、不出怪事。