← 返回题目列表

Python 类属性和实例属性有什么区别?

高频 简单 第 1 / 21 题 更新于 2026/07/27
类属性实例属性对象模型属性查找

简化版

类属性属于类对象,所有实例默认共享;实例属性属于某个实例,只影响当前对象。访问属性时,Python 通常先找实例自己的属性,再找类属性,所以实例属性可以遮蔽同名类属性。

详细版

示例:

class User:
    role = "guest"   # 类属性

    def __init__(self, name):
        self.name = name  # 实例属性

u1 = User("Tom")
u2 = User("Jerry")

role 定义在类上,u1.roleu2.role 默认都能访问到它;name 定义在实例上,每个对象各有一份。

print(u1.role)  # guest
print(u2.role)  # guest
print(u1.name)  # Tom
print(u2.name)  # Jerry

如果给实例赋同名属性:

u1.role = "admin"
print(u1.role)     # admin
print(u2.role)     # guest
print(User.role)   # guest

这不是修改类属性,而是在 u1 上新增了实例属性 role,遮蔽了类属性。

高频坑是把可变对象放到类属性里:

class Team:
    members = []

t1 = Team()
t2 = Team()
t1.members.append("Tom")
print(t2.members)  # ['Tom']

因为 members 是类属性,两个实例共享同一个列表。每个实例独立的数据应该放到 __init__ 里。

完整版教学

一、Python 里的类本身也是对象

在 Python 中,类不是只存在于语法层面的模板,类本身也是对象。

class User:
    role = "guest"

执行这段代码时,Python 会创建一个类对象 User,并把 role 放到类对象的命名空间里。

实例化:

u = User()

会创建实例对象。实例对象通常有自己的属性字典,用来保存实例属性。

可以观察:

print(User.__dict__["role"])  # guest
print(u.__dict__)             # {}

如果执行:

u.name = "Tom"

实例字典会变成:

{'name': 'Tom'}

这就是类属性和实例属性的存放位置差异。

二、属性查找顺序

当你访问:

u.role

Python 会大致按下面思路查找:

  1. 先看实例对象上有没有这个属性;
  2. 如果实例上没有,再去类对象上找;
  3. 如果类上没有,再沿着继承链找父类;
  4. 还找不到就抛出 AttributeError

所以:

u.role = "admin"

会在实例上创建一个新属性。之后再访问 u.role,先命中实例属性,就不会继续读取类属性。

这叫属性遮蔽,不是覆盖类属性。

三、修改类属性和修改实例属性

修改类属性:

User.role = "member"

没有遮蔽同名实例属性的对象,会读到新的类属性:

u1 = User()
u2 = User()
u1.role = "admin"

User.role = "member"

print(u1.role)  # admin
print(u2.role)  # member

u1 有自己的 role,所以不受类属性变化影响;u2 没有自己的 role,所以继续从类上读取。

四、可变类属性为什么危险

类属性适合放共享常量:

class HttpStatus:
    OK = 200
    NOT_FOUND = 404

但如果类属性是列表、字典、集合这类可变对象,就很容易变成共享状态。

错误示例:

class Cart:
    items = []

    def add(self, item):
        self.items.append(item)

多个购物车实例会共享一个 items 列表,这通常不是业务期望。

正确写法:

class Cart:
    def __init__(self):
        self.items = []

    def add(self, item):
        self.items.append(item)

每次创建实例时都会创建新的列表。

五、类属性适合哪些场景

类属性适合表达“属于整个类”的信息:

  • 常量;
  • 默认配置;
  • 所有实例共享的只读信息;
  • 类级计数器或注册表,但要明确管理副作用。

例如:

class User:
    count = 0

    def __init__(self):
        User.count += 1

这里 count 表示创建过多少个用户实例,是类级状态,放在类属性上是合理的。

六、常见误区与追问

记忆钩子:读属性时“先实例、再类、再父类”;写属性时要看写在谁身上。obj.x = ... 通常是在实例上新建或覆盖绑定,Class.x = ... 才是在改类属性。

  • 误区:实例访问到类属性后,给实例赋值就是修改类属性。 obj.count = 1 会在实例 __dict__ 里放一个同名属性,遮蔽类属性;只有 Class.count = 1 或通过类对象修改才会改变类属性绑定。
  • 误区:类属性适合放所有默认值。 不会被原地修改的常量可以放类属性,但列表、字典这类可变对象如果作为共享默认值,很容易让多个实例互相污染。
  • 追问:为什么 self.items.append(x) 可能影响所有实例? 如果 items 没有在实例上初始化,而是从类属性里查到同一个列表,那么 append 改的是共享列表对象,不是给当前实例创建新列表。
  • 追问:如何判断属性来自实例还是类? 可以看 obj.__dict__Class.__dict__;实例字典里有这个名字就是实例属性,否则可能来自类或父类。
  • 误区:类属性和静态变量完全等价。 Python 类属性是类对象上的普通属性,也会参与继承和属性查找;子类赋同名属性会遮蔽父类属性,不一定修改父类。
  • 追问:类属性适合什么面试场景举例? 适合放常量、所有实例共享的配置、计数器或注册表;但共享可变状态要明确管理,必要时用实例属性或工厂函数创建独立对象。

七、加强记忆

类属性在类上,实例属性在对象上。读属性时先找实例,再找类;写 obj.x = ... 通常是在实例上创建或修改属性,不是在改类属性。可变对象如果要每个实例独立,就放进 __init__;只有真正共享的状态才放类属性。