类变量与实例变量
理解类变量(class variables)和实例变量(instance variables)的区别,是掌握 Python 面向对象编程的关键。它们存储在不同的命名空间中,生命周期和共享方式完全不同。
类变量
类变量定义在类体中,不在任何方法内。它被所有实例共享:
class Dog(object):
kind = "canine" # 类变量
def __init__(self, name):
self.name = name # 实例变量
d = Dog("Fido")
e = Dog("Buddy")
print d.kind # canine
print e.kind # canine
print Dog.kind # canine,通过类访问
类变量通过类名访问(Dog.kind)或通过实例访问(d.kind)。通过实例访问时,Python 先在实例的 __dict__ 中查找,找不到再到类的 __dict__ 中查找。
实例变量
实例变量在 __init__ 或其他方法中通过 self.xxx 赋值。每个实例有独立的副本:
print d.name # Fido
print e.name # Buddy
d.name = "Rex"
print d.name # Rex
print e.name # Buddy —— 不受影响
可变类变量的陷阱
当类变量是可变对象(如列表、字典)时,通过实例修改会影响所有实例:
class Dog(object):
tricks = [] # 类变量,列表是可变的!
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick) # 修改类变量!
d = Dog("Fido")
e = Dog("Buddy")
d.add_trick("roll over")
e.add_trick("play dead")
print d.tricks # ['roll over', 'play dead'] —— 两个狗共享同一个列表!
print e.tricks # ['roll over', 'play dead']
print Dog.tricks # ['roll over', 'play dead']
这是 Python 面向对象中最经典的陷阱。tricks 是类变量,所有实例共享同一个列表。self.tricks.append() 修改的是这个共享列表。
正确做法
可变数据应该作为实例变量:
class Dog(object):
kind = "canine" # 类变量,字符串不可变,安全
def __init__(self, name):
self.name = name
self.tricks = [] # 实例变量,每个狗有自己的列表
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog("Fido")
e = Dog("Buddy")
d.add_trick("roll over")
e.add_trick("play dead")
print d.tricks # ['roll over']
print e.tricks # ['play dead']
通过实例修改类变量
通过实例给类变量赋值,会创建同名的实例变量,遮蔽类变量:
class Counter(object):
count = 0 # 类变量
c1 = Counter()
c2 = Counter()
print c1.count, c2.count # 0 0
Counter.count = 10 # 修改类变量
print c1.count, c2.count # 10 10
c1.count = 5 # 创建实例变量 c1.count!
print c1.count # 5,实例变量
print c2.count # 10,类变量
print Counter.count # 10,类变量
c1.count = 5 不是修改 Counter.count,而是在 c1 的 __dict__ 中创建了 count 属性。此后 c1.count 访问的是实例变量,c2.count 仍然访问类变量。
使用场景
| 场景 | 推荐 |
|---|---|
| 所有实例共享的常量 | 类变量(不可变对象) |
| 每个实例独立的数据 | 实例变量 |
| 统计实例数量 | 类变量(注意线程安全) |
| 默认配置 | 类变量 |
class Employee(object):
company = "ACME" # 类变量:所有员工同公司
employee_count = 0 # 类变量:统计员工数
def __init__(self, name):
self.name = name # 实例变量
self.id = Employee.employee_count
Employee.employee_count += 1
alice = Employee("Alice")
bob = Employee("Bob")
print alice.id # 0
print bob.id # 1
print Employee.employee_count # 2