类属性
所有对象共享的属性,在类内部直接定义:
class Dog:
species = "Canis familiaris" # 类属性
def __init__(self, name):
self.name = name
dog1 = Dog("Buddy")
dog2 = Dog("Max")
print(dog1.species) # Canis familiaris
print(dog2.species) # Canis familiaris
# 修改类属性(通过类名)
Dog.species = "Canis lupus"
print(dog1.species) # Canis lupus
访问规则: 实例属性通过 self 访问,类属性通过 类名 或 self 访问。
class Person:
country = "中国" # 类属性(存在类空间 Person.__dict__)
def __init__(self, name):
self.name = name # 实例属性(存在实例空间 p.__dict__)
p = Person("张三")
- 类属性:属于类本身,所有实例共享一份数据
- 实例属性:每个对象自己独有
Python 规则:对象 . 访问属性时,查找顺序
对象.属性名 查找流程(就近原则):
- 先去实例自己的字典
obj.__dict__找; - 找不到 → 去所属类
cls.__dict__找类属性; - 还找不到 → 顺着继承 MRO 往上父类依次查找。
所以 p.country:实例没有 country,自动去 Person 里取类属性,语法合法。
对比 Java/C++ 为什么不能用对象访问静态变量(类属性)
Java
class Person {
static String country = "中国"; // 静态变量=Python类属性
}
public class Test {
public static void main(String[] args) {
Person p = new Person();
// 语法允许编译通过,但编码规范强制禁止,IDE会警告
System.out.println(p.country);
// 标准写法:Person.country
}
}
- 语法层面:Java 其实能对象。静态变量,只是规范不推荐;
- 设计思想:静态属于类,实例只是附属载体,为了代码可读性强制要求类名调用,避免混淆实例 / 静态;
- 底层:编译期
p.country会被编译器优化成Person.country,对象引用完全没用。
C++
同理,对象可以访问静态成员,但业界统一只用 类名::静态变量,目的区分层级。
Python 独特设计:动态命名空间分离,读写行为不一样
1. 读取:对象。类属性 可读(共享)
print(p.country) # 读:实例没有,去类拿
print(Person.country)
2. 赋值:对象。类属性 会新增实例属性,不会修改类属性(大坑)
p.country = "美国"
# 不会改 Person.country!而是给实例p新增实例属性country
print(p.country) # 美国(实例自己的)
print(Person.country) # 中国(类属性没变)
p2 = Person("李四")
print(p2.country) # 中国,不受p影响
真正修改类属性必须用类名:
Person.country = "华夏"
Python 允许对象访问类属性的底层 & 设计原因
- 统一
.访问语法,简化 API Python 追求语法统一,不管实例属性 / 类属性,全部用.访问,不用区分两种调用语法,降低学习成本。 - 属性查找是动态链式查询(MRO 统一机制) Python 没有给 “类属性” 单独一套访问语法,统一走一套属性检索链路:实例→类→父类,天然支持对象读取类属性。
- 动态语言无编译期校验,不强制语法隔离 Java/C++ 是静态编译语言,编译时就能区分静态 / 实例,强制规范; Python 运行时才查找属性,没有编译阶段做语法拦截,自然放开读取权限。
- 实用场景:实例直接读取共享配置 类属性常用来存全局配置、常量、计数器,实例不需要写
类名.xxx,直接self.xxx就能读取共享数据,代码更简洁:
class Request:
timeout = 30 # 全局超时配置
def send(self):
print(f"请求超时 {self.timeout}") # 直接self读类属性




