Python 实例属性和类属性
Python 的类和对象是面向对象编程的核心,而属性(attributes)是类和对象中存储数据的地方。Python 的属性分为实例属性和类属性,这俩虽然听起来差不多,但用法和行为有挺大的区别。
参考文章:Python 实例属性和类属性 | 简单一点学习 easyeasy.me
1. 什么是实例属性和类属性?
1.1 实例属性
实例属性是属于某个具体对象(实例)的属性。每个对象都有自己的实例属性,互不干扰。就像你和你的朋友每人有自己的手机,手机里的照片、设置啥的都是独一无二的。
- 特点:
- 通常在
__init__
方法中通过self
定义。 - 每个实例的属性值可以不同。
- 只能通过实例访问(
obj.attribute
)。
- 通常在
1.2 类属性
类属性是属于整个类的属性,所有实例共享同一份数据。就像一个班级的班规,所有学生都得遵守同一套规则。
- 特点:
- 定义在类内部,但不在任何方法内(通常在类开头定义)。
- 通过类名或实例都可以访问(
ClassName.attribute
或obj.attribute
)。 - 修改类属性会影响所有实例(除非实例覆盖了它)。
2. 怎么定义和使用这两种属性?
2.1 定义实例属性
实例属性一般在 __init__
方法里用 self
来定义,每次创建对象时都会初始化。
class Dog:def __init__(self, name, age):self.name = name # 实例属性self.age = age # 实例属性# 创建两个 Dog 实例
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)print(dog1.name) # 输出: 旺财
print(dog2.name) # 输出: 小黑
上面代码里,name
和 age
是实例属性,dog1
和 dog2
各有一份,互不影响。
2.2 定义类属性
类属性直接在类里定义,不需要 self
,通常放在 __init__
方法外面。
class Dog:species = "Canis familiaris" # 类属性def __init__(self, name, age):self.name = nameself.age = agedog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)print(dog1.species) # 输出: Canis familiaris
print(dog2.species) # 输出: Canis familiaris
print(Dog.species) # 输出: Canis familiaris
species
是类属性,dog1
、dog2
和 Dog
类都能访问它,因为它是所有实例共享的。
3. 访问和修改属性的区别
3.1 访问属性
- 实例属性:只能通过实例访问,比如
dog1.name
。 - 类属性:可以通过类名(
Dog.species
)或实例(dog1.species
)访问。
class Dog:species = "Canis familiaris"def __init__(self, name):self.name = namedog = Dog("旺财")
print(dog.name) # 实例属性,输出: 旺财
print(dog.species) # 类属性,输出: Canis familiaris
print(Dog.species) # 类属性,输出: Canis familiaris
3.2 修改属性
- 实例属性:修改只影响当前实例。
- 类属性:通过类名修改会影响所有实例;通过实例修改会“遮盖”类属性(实际上是为实例创建了一个同名的实例属性)。
class Dog:species = "Canis familiaris"def __init__(self, name):self.name = namedog1 = Dog("旺财")
dog2 = Dog("小黑")# 修改类属性
Dog.species = "Canis lupus"
print(dog1.species) # 输出: Canis lupus
print(dog2.species) # 输出: Canis lupus# 通过实例“修改”类属性(实际上创建了实例属性)
dog1.species = "Doggo"
print(dog1.species) # 输出: Doggo(实例属性)
print(dog2.species) # 输出: Canis lupus(类属性)
print(Dog.species) # 输出: Canis lupus(类属性)
注意:dog1.species = "Doggo"
并不是修改了类属性,而是给 dog1
创建了一个新的实例属性 species
,遮盖了类属性。
4. 实际场景中的用法
4.1 实例属性的典型场景
实例属性适合存储每个对象独有的数据,比如名字、年龄、ID 等等。就像下面这个例子:
class Student:def __init__(self, name, student_id):self.name = nameself.student_id = student_idself.grades = []def add_grade(self, grade):self.grades.append(grade)student1 = Student("小明", "001")
student2 = Student("小红", "002")student1.add_grade(90)
student2.add_grade(85)print(student1.grades) # 输出: [90]
print(student2.grades) # 输出: [85]
每个学生有自己的成绩单,互不干扰,这就是实例属性的典型用法。
4.2 类属性的典型场景
类属性适合存储所有实例共享的数据,比如计数器、默认配置等。比如下面这个例子,用类属性来统计创建了多少个对象:
class Dog:count = 0 # 类属性,用来计数def __init__(self, name):self.name = nameDog.count += 1 # 每次创建实例,计数加 1dog1 = Dog("旺财")
dog2 = Dog("小黑")
dog3 = Dog("大黄")print(Dog.count) # 输出: 3
count
是类属性,记录了总共创建了多少只狗,所有的狗共享这个计数器。
5. 注意事项和常见坑
5.1 实例属性和类属性的命名冲突
如果实例属性和类属性同名,实例属性会“遮盖”类属性。前面已经看到过例子了,这种情况要小心,别搞混了。
5.2 可变类属性的陷阱
如果类属性是可变对象(比如列表或字典),所有实例共享同一份数据,修改会影响所有实例。
class Dog:tricks = [] # 类属性,是一个列表def __init__(self, name):self.name = namedef add_trick(self, trick):self.tricks.append(trick)dog1 = Dog("旺财")
dog2 = Dog("小黑")dog1.add_trick("坐下")
dog2.add_trick("握手")print(dog1.tricks) # 输出: ['坐下', '握手']
print(dog2.tricks) # 输出: ['坐下', '握手']
这可能不是你想要的!因为 tricks
是类属性,所有狗共享同一个列表。如果你想要每只狗有自己的技能列表,应该用实例属性:
class Dog:def __init__(self, name):self.name = nameself.tricks = [] # 实例属性def add_trick(self, trick):self.tricks.append(trick)dog1 = Dog("旺财")
dog2 = Dog("小黑")dog1.add_trick("坐下")
dog2.add_trick("握手")print(dog1.tricks) # 输出: ['坐下']
print(dog2.tricks) # 输出: ['握手']
5.3 动态添加实例属性
Python 允许动态给实例添加属性,但要小心,这可能会让代码不好维护。
class Dog:def __init__(self, name):self.name = namedog = Dog("旺财")
dog.age = 3 # 动态添加实例属性
print(dog.age) # 输出: 3
虽然很灵活,但建议尽量在 __init__
里定义所有属性,保持代码清晰。
6. 总结
- 实例属性:每个对象独有,定义在
__init__
里,用self
访问,适合存储对象特有的数据。 - 类属性:所有实例共享,定义在类里,用类名或实例访问,适合存储共享数据。
- 注意事项:小心命名冲突和可变类属性的陷阱,尽量在
__init__
里定义实例属性。