python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python访问类变量与实例变量

Python中访问类变量与实例变量的完整步骤

作者:2401_89134191

在Python中,类变量和实例变量的访问方式有所不同,理解它们的区别和正确访问方法很重要,所以本文给大家详细介绍了Python访问类变量与实例变量的完整步骤,需要的朋友可以参考下

好的,我们来系统地拆解一下 Python 中访问类变量和实例变量的完整步骤。这是一个非常核心且容易混淆的概念。

我会遵循以下结构,由浅入深,确保你彻底理解:

  1. 核心定义:明确什么是类变量和实例变量。
  2. 查找规则:详细解释 Python 在访问属性时遵循的 “查找链”(Lookup Chain)。
  3. 代码演练:通过实例代码,一步步追踪查找过程。
  4. 修改与遮蔽:解释如何修改变量,以及 “遮蔽”(Shadowing)现象。
  5. 可变对象陷阱:探讨当类变量是可变对象(如列表、字典)时的特殊情况。
  6. 总结与速查表:提供一个清晰的总结,帮助你快速决策。

1. 核心定义

类变量 (Class Variable)

实例变量 (Instance Variable)

代码示例:

class Dog:
    # 类变量:所有狗都共享这个属性
    species = "Canis lupus familiaris"
    count = 0 # 用于计数狗的数量
 
    def __init__(self, name, age):
        # 实例变量:每个狗对象都有自己的 name 和 age
        self.name = name
        self.age = age
        Dog.count += 1 # 每次创建实例,类变量 count 加 1

2. 查找规则:属性查找链 (Attribute Lookup Chain)

当你使用 instance.attribute 的形式访问一个属性时,Python 会严格按照以下顺序进行查找,直到找到为止:

查找实例自身的命名空间

查找类的命名空间

查找父类的命名空间(继承链)

抛出异常

一句话总结: 先找实例,再找类,最后找父类。 (instance -> Class -> Parent Class)

3. 代码演练:追踪查找过程

让我们创建一个实例,并追踪访问不同变量时的路径。

# 沿用上面的 Dog 类
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

场景 1:访问实例变量 dog1.name

  1. 查找 dog1 的 __dict__{'name': 'Buddy', 'age': 3}
  2. 找到了 name,值为 'Buddy'
  3. 查找停止,返回 'Buddy'

场景 2:访问类变量 dog1.species

  1. 查找 dog1 的 __dict__{'name': 'Buddy', 'age': 3}
  2. 没有找到 species
  3. 继续查找 dog1 所属的类 Dog 的 __dict__
  4. 在 Dog.__dict__ 中找到了 species,值为 "Canis lupus familiaris"
  5. 查找停止,返回 "Canis lupus familiaris"

场景 3:访问类变量 Dog.count

  1. 这里直接从类 Dog 开始查找。
  2. 在 Dog.__dict__ 中找到了 count,值为 2
  3. 查找停止,返回 2

场景 4:访问一个不存在的变量 dog1.weight

  1. 查找 dog1 的 __dict__{'name': 'Buddy', 'age': 3}
  2. 没有找到 weight
  3. 继续查找 Dog 的 __dict__
  4. 也没有找到 weight
  5. Dog 没有父类(除了 object),在 object 中也找不到。
  6. 查找失败,抛出 AttributeError: 'Dog' object has no attribute 'weight'

4. 修改与遮蔽 (Shadowing)

修改变量的行为取决于你通过什么来修改它。

4.1 修改实例变量

这是最常见的操作,直接在实例的 __dict__ 中创建或更新属性。

dog1.age = 4 # 修改 dog1 自己的 age
print(dog1.age)  # 输出: 4
print(dog2.age)  # 输出: 5 (不受影响)

4.2 修改类变量

这里有两种方式,效果完全不同:

方式一:通过类名修改(推荐)

这会真正地修改类的 __dict__ 中的值,所有实例都会受到影响。

Dog.species = "Canis familiaris" # 通过类名修改
 
print(Dog.species)  # 输出: Canis familiaris
print(dog1.species) # 输出: Canis familiaris (dog1 会查找到更新后的类变量)
print(dog2.species) # 输出: Canis familiaris (dog2 也会查找到更新后的类变量)

方式二:通过实例修改(导致遮蔽)

不会修改类变量,而是会在该实例自己的 __dict__ 中创建一个同名的实例变量

这个新创建的实例变量会 “遮蔽”(shadow)掉类变量,也就是说,之后再通过这个实例访问该变量时,会直接返回实例自己的那个,而不再去查找类。

# 此时,Dog.species 是 "Canis familiaris"
print(f"Before shadowing, dog1.__dict__: {dog1.__dict__}") # {'name': 'Buddy', 'age': 4}
 
dog1.species = "Wolf" # 通过实例修改,触发遮蔽
 
print(f"After shadowing, dog1.__dict__: {dog1.__dict__}") # {'name': 'Buddy', 'age': 4, 'species': 'Wolf'}
print(f"Dog.__dict__['species']: {Dog.__dict__['species']}") # 'Canis familiaris' (类变量没变!)
 
print(dog1.species) # 输出: Wolf (访问的是实例自己的 species)
print(dog2.species) # 输出: Canis familiaris (dog2 没有被遮蔽,访问的仍是类变量)
print(Dog.species)  # 输出: Canis familiaris (类变量本身没变)

5. 可变对象陷阱 (Mutable Object Pitfall)

这是一个非常经典的面试题和错误来源。当类变量是可变对象(如列表 list、字典 dict、集合 set)时,通过实例对其进行原地修改(in-place modification),会影响所有实例。

原因:因为实例和类共享同一个可变对象的引用。

代码示例:

class Cat:
    # 类变量,一个空列表
    tricks = []
 
    def __init__(self, name):
        self.name = name
 
cat1 = Cat("Kitty")
cat2 = Cat("Lucy")
 
# 通过实例 cat1 向 tricks 列表添加元素
cat1.tricks.append("play dead")
 
# 查看结果
print(cat1.tricks) # 输出: ['play dead']
print(cat2.tricks) # 输出: ['play dead'] (Oh no! cat2 的 tricks 也变了)
print(Cat.tricks)  # 输出: ['play dead'] (实际上是类变量被修改了)
 
# 检查它们是否指向同一个对象
print(id(cat1.tricks)) # e.g., 140183245326144
print(id(cat2.tricks)) # e.g., 140183245326144 (和上面的 id 相同)
print(id(Cat.tricks))  # e.g., 140183245326144 (和上面的 id 相同)

陷阱分析:cat1.tricks.append(...) 这个操作,Python 首先在 cat1.__dict__ 中找 tricks,没找到,然后去 Cat.__dict__ 中找到了 tricks 列表。接着,它对这个找到的列表对象本身执行了 append 操作。因为所有实例和类都指向这同一个列表对象,所以大家都看到了变化。

如何避免?如果你想让每个实例都有自己独立的可变对象(比如一个空列表),你应该在 __init__ 方法中初始化它。

class Cat:
    def __init__(self, name):
        self.name = name
        # 在实例化时,为每个实例创建一个独立的列表
        self.tricks = []
 
cat1 = Cat("Kitty")
cat2 = Cat("Lucy")
 
cat1.tricks.append("play dead")
 
print(cat1.tricks) # 输出: ['play dead']
print(cat2.tricks) # 输出: [] (cat2 的列表不受影响)

6. 总结与速查表

特性类变量 (Class Variable)实例变量 (Instance Variable)
定义位置类内部,方法外部通常在 __init__ 方法中,以 self. 开头
所属对象实例
共享性被所有实例共享每个实例独有
访问方式Class.var 或 instance.varinstance.var
查找顺序实例查找失败后,再查找类优先查找实例
修改方式Class.var = new_value (影响所有实例)instance.var = new_value (只影响当前实例)
通过实例修改instance.var = new_value 会创建一个同名的实例变量,遮蔽类变量直接修改实例自己的变量
可变对象风险如果是可变对象(如列表),通过实例进行原地修改(append等)会影响所有实例每个实例的可变对象都是独立的,无此风险

实践建议:

  1. 明确意图:如果一个属性对所有实例都通用,用类变量。如果每个实例都需要自己独立的一份,用实例变量。
  2. 修改类变量用类名:为了代码清晰,避免歧义,修改类变量时始终使用 Class.variable 的形式。
  3. 警惕可变类变量:除非你明确希望所有实例共享一个可变对象(例如,一个全局计数器),否则不要将可变对象用作类变量来存储实例相关的状态。

希望这个全面的拆解能帮助你彻底掌握类变量和实例变量的访问机制!

以上就是Python中访问类变量与实例变量的完整步骤的详细内容,更多关于Python访问类变量与实例变量的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文