在Python面向对象编程中,类实例变量是绑定到具体实例对象的属性,每个实例的实例变量相互独立,其值的更新和获取需要遵循Python的属性访问规则,很多取值异常问题都源于对规则的误解。

实例变量的存储基础
Python中每个类实例都有一个__dict__属性,这是一个字典,专门用来存储该实例的所有实例变量。当我们给实例的某个属性赋值时,默认会把这个键值对存入实例的__dict__中,而类属性则存储在类对象的__dict__里。
属性访问的优先级顺序是:实例__dict__ > 类__dict__ > 父类__dict__,如果实例__dict__中已经有对应的属性,就不会再去类或父类中查找,这也是获取最新值的核心依据。
常见取值误区
混淆类属性和实例属性
如果先给类定义了同名属性,再给实例赋值同名属性,很容易出现取值不符合预期的情况,看下面的示例:
class Student:
# 类属性
score = 0
# 创建实例
stu = Student()
# 此时访问的是类属性
print(stu.score) # 输出 0
# 给实例赋值同名属性,此时会存入实例的__dict__
stu.score = 90
print(stu.score) # 输出 90,此时访问的是实例属性
# 查看实例和类的__dict__
print(stu.__dict__) # 输出 {'score': 90}
print(Student.__dict__) # 输出中包含 'score': 0,类属性未被修改
动态修改后未正确访问
如果直接修改实例__dict__中的值,或者通过其他方式动态更新实例变量,只要遵循访问优先级,就能获取到最新值,但如果误访问了类属性就会出错:
class User:
def __init__(self, name):
self.name = name
u = User("张三")
# 动态修改实例变量
u.name = "李四"
# 正确获取最新值
print(u.name) # 输出 李四
# 错误方式:如果类有同名属性,可能拿到旧值
User.name = "王五" # 给类添加同名属性
print(u.name) # 还是输出 李四,因为实例__dict__中有该属性,优先访问实例的
正确获取最新值的方法
直接访问实例属性
这是最常用也最推荐的方式,只要实例变量已经被赋值,直接通过实例.属性名的方式访问,就会按照优先级先查实例__dict__,拿到最新值:
class Car:
def __init__(self, speed):
self.speed = speed
def update_speed(self, new_speed):
# 更新实例变量
self.speed = new_speed
my_car = Car(60)
my_car.update_speed(80)
# 直接访问获取最新值
print(my_car.speed) # 输出 80
通过__dict__查询
如果需要确认实例变量是否存在,或者要获取所有实例变量,可以直接访问实例的__dict__,它里面存储的就是当前实例所有实例变量的最新键值对:
class Book:
def __init__(self, title, price):
self.title = title
self.price = price
book = Book("Python编程", 89)
# 更新price
book.price = 79
# 通过__dict__获取最新值
print(book.__dict__.get("price")) # 输出 79
# 查看所有实例变量
print(book.__dict__) # 输出 {'title': 'Python编程', 'price': 79}
处理属性描述符场景
如果实例的属性被描述符(比如@property装饰的属性)管理,那么获取值时会被描述符的逻辑拦截,此时要保证描述符的返回逻辑是最新的:
class Temperature:
def __init__(self, value):
self._value = value
@property
def value(self):
# 返回最新的_value值
return self._value
@value.setter
def value(self, new_val):
self._value = new_val
t = Temperature(25)
t.value = 30
# 通过property获取最新值
print(t.value) # 输出 30
注意事项
- 不要随意修改实例
__dict__以外的属性存储结构,避免破坏访问规则 - 如果类定义了
__slots__,实例不会有__dict__属性,实例变量会存储在__slots__指定的槽位中,此时直接访问实例属性依然可以拿到最新值 - 避免在获取实例变量时,误调用类方法或者访问类属性,导致拿到非实例的最新值
只要遵循Python的属性访问优先级规则,优先从实例自身的属性存储中取值,就能稳定获取到类实例变量的最新值,避开大部分取值异常问题。