Python 类变量、实例变量与 ClassVar
Python 类变量、实例变量与 ClassVar
1. 核心区别
实例变量
属于某一个具体对象,每个实例各自拥有一份。
通常写在 __init__ 中:
1 | class Student: |
1 | s1 = Student("楠瓜") |
判断重点不是它是否写在 __init__ 里,而是:
1 | self.xxx = value |
只要属性赋值给 self,就是实例变量。它也可以在其他实例方法中创建:
1 | class Student: |
不过为了结构清晰,通常会在 __init__ 中初始化实例变量。
类变量
属于类本身,由这个类的所有实例共享。
直接在类体中赋值:
1 | class Student: |
推荐通过类访问和修改:
1 | print(Student.school) |
实例也能读取类变量:
1 | s1 = Student() |
2. 属性查找规则
访问:
1 | s1.school |
Python 会大致按照以下顺序查找:
1 | 先找 s1 实例自身 |
所以:
1 | class Student: |
这里不是修改类变量,而是在 s1 上创建了一个同名实例变量。
1 | print(s1.school) # 楠瓜大学 |
这种现象叫做实例属性遮蔽类属性。
真正修改类变量应该写:
1 | Student.school = "楠瓜大学" |
3. 类体中的类型标注
下面这段代码:
1 | class Foo: |
只是类型声明,表示:
Foo的实例预计会有一个y属性,并且类型是str。
它没有真正赋值,也没有创建可访问的类属性。
1 | class Foo: |
此时真正创建实例变量的是:
1 | self.y = y |
因此:
1 | foo = Foo("hello") |
4. ClassVar 是什么
导入方式:
1 | from typing import ClassVar |
使用方式:
1 | class Foo: |
ClassVar[str] 表示:
x是类变量x的类型是str- 类型检查器不应该把它当作实例字段
它主要用于帮助:
- Pylance
- Pyright
- mypy
- dataclass
- 其他依赖类型标注的框架
它不会在运行时制造一种特殊变量。
5. 只有标注,没有赋值
1 | class Foo: |
这只是声明:
x应当是一个字符串类型的类变量。
但现在并没有真正给它值:
1 | print(Foo.x) # AttributeError |
通常由当前类或子类赋值:
1 | class Foo: |
或者:
1 | class BaseTool: |
6. 普通类型标注和 ClassVar 对比
1 | from typing import ClassVar |
可以理解成:
1 | x:属于 Foo 类 |
访问方式:
1 | foo1 = Foo("对象一") |
7. 为什么抽象类常使用 ClassVar
抽象类经常要求不同子类提供固定的类级别信息:
1 | from abc import ABC, abstractmethod |
具体工具子类:
1 | class ReadFileTool(Tool): |
这里的 name 描述的是:
这一类工具叫什么。
而不是:
某一个工具对象叫什么。
所以适合使用类变量。
8. ClassVar 不等于抽象约束
1 | class Tool: |
这不会像 @abstractmethod 一样,在运行时强迫子类定义 name。
子类即使不写:
1 | class BadTool(Tool): |
可能仍然能够实例化,只是在访问时出错:
1 | BadTool.name # AttributeError |
因此:
1 | name: ClassVar[str] |
主要表达类型和设计意图。
1 |
|
才是运行时真正强制子类实现的抽象要求。
9. 可变类变量陷阱
下面的列表会被所有实例共享:
1 | class Student: |
1 | s1 = Student() |
如果每个对象应该有独立列表,应写成实例变量:
1 | class Student: |
10. 最终判断口诀
1 | self.xxx = value |
是实例变量。
1 | class Foo: |
是类变量。
1 | class Foo: |
只有类型声明,没有赋值;通常表示预期的实例属性。
1 | class Foo: |
明确声明为字符串类型的类变量。
最后记一句:
描述“这一个对象的数据”,用实例变量。
描述“这一类对象共同的属性”,用类变量。ClassVar是给类型检查器看的类变量标记。




