百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

详解 Python 访问类变量与实例变量的核心操作步骤

zhezhongyun 2025-10-19 15:34 30 浏览

核心概念速览

在深入操作步骤之前,我们先明确两个核心定义:

  1. 类变量 (Class Variable)定义位置:在类的内部,但在任何方法(包括 __init__)的外部。归属:属于类本身,被所有该类的实例(对象)共享。作用:通常用于定义所有实例都通用的属性或常量。例如,一个 Car 类可以有一个类变量 wheels = 4,因为所有汽车都有 4 个轮子。
  2. 实例变量 (Instance Variable)定义位置:通常在类的 __init__ 方法中,通过 self.变量名 的形式定义。归属:属于特定的实例(对象)。每个实例都有自己独立的一份,互不影响。作用:用于存储每个实例独有的数据。例如,Car 类的实例变量可以是 color 或 brand,因为每辆车的颜色和品牌都可能不同。

核心操作步骤详解

我们将通过一个完整的示例来展示所有核心操作。

步骤 1: 定义类并声明变量

首先,我们创建一个 Dog 类,它包含一个类变量和两个实例变量。

python

运行

class Dog:
    # 1. 定义类变量 (在所有方法之外)
    # 所有狗都属于"犬科"
    species = "Canidae"

    def __init__(self, name, age):
        # 2. 定义实例变量 (在 __init__ 方法中,使用 self)
        # 每个狗都有自己的名字和年龄
        self.name = name
        self.age = age

步骤 2: 创建实例

接下来,我们创建 Dog 类的两个实例。

python

运行

dog1 = Dog("旺财", 3)
dog2 = Dog("小白", 5)

步骤 3: 访问变量

访问变量分为三种情况:通过实例访问、通过类访问。

情况一:访问实例变量

实例变量只能通过实例(对象)来访问。

python

运行

# 通过实例 dog1 访问它的实例变量
print(f"{dog1.name} 的年龄是 {dog1.age}")  # 输出: 旺财 的年龄是 3

# 通过实例 dog2 访问它的实例变量
print(f"{dog2.name} 的年龄是 {dog2.age}")  # 输出: 小白 的年龄是 5

注意:尝试通过类来访问实例变量会引发 AttributeError,因为实例变量不属于类。

python

运行

# 错误示例
try:
    print(Dog.name)
except AttributeError as e:
    print(f"错误: {e}")  # 输出: 错误: type object 'Dog' has no attribute 'name'

情况二:访问类变量

类变量的访问方式有两种,这也是最容易混淆的地方。

方式 A: 通过类名访问(推荐)

这是最清晰、最明确的方式,能直接表明你正在操作的是类变量。

python

运行

print(f"狗的物种是: {Dog.species}")  # 输出: 狗的物种是: Canidae

方式 B: 通过实例访问(有陷阱)

实例也可以访问类变量,因为实例可以 “看到” 它所属类的属性。

python

运行

print(f"{dog1.name} 的物种是: {dog1.species}")  # 输出: 旺财 的物种是: Canidae
print(f"{dog2.name} 的物种是: {dog2.species}")  # 输出: 小白 的物种是: Canidae

当你通过实例访问一个属性时,Python 的查找顺序(MRO - Method Resolution Order)是:

  1. 首先,在实例自己的命名空间中查找。
  2. 如果找不到,再到实例所属的类的命名空间中查找。

因为 dog1 和 dog2 实例自己没有 species 这个实例变量,所以 Python 就去 Dog 类中找到了 species 这个类变量。

步骤 4: 修改变量

修改变量是最能体现两者区别的操作。

情况一:修改实例变量

修改实例变量非常直接,只会影响到进行修改的那个实例。

python

运行

# 修改 dog1 的年龄
dog1.age = 4

print(f"修改后,{dog1.name} 的年龄是 {dog1.age}")  # 输出: 修改后,旺财 的年龄是 4
print(f"{dog2.name} 的年龄仍然是 {dog2.age}")      # 输出: 小白 的年龄仍然是 5 (不受影响)

情况二:修改类变量(核心陷阱)

修改类变量有两种方式,结果完全不同。

方式 A: 通过类名修改(正确方式)

这种方式会真正地修改类变量本身,因此会影响到所有的实例(包括未来创建的实例)。

python

运行

# 假设所有的狗都进化了,物种名改变了 (这是一个对所有狗都生效的修改)
Dog.species = "Mammalia"

print(f"类的物种现在是: {Dog.species}")         # 输出: 类的物种现在是: Mammalia
print(f"{dog1.name} 的物种现在是: {dog1.species}") # 输出: 旺财 的物种现在是: Mammalia
print(f"{dog2.name} 的物种现在是: {dog2.species}") # 输出: 小白 的物种现在是: Mammalia

方式 B: 通过实例修改(陷阱!)

这种方式不会修改类变量,而是会在该实例中创建一个同名的实例变量,从而 “遮蔽”(shadowing)掉类变量。

python

运行

# dog1 发生了变异,它觉得自己是一个 "Robot"
# 注意:这里的操作是给 dog1 实例增加了一个新的实例变量 species
dog1.species = "Robot"

# 查看 dog1 的属性,它现在使用的是自己的实例变量
print(f"变异后,{dog1.name} 的物种是: {dog1.species}") # 输出: 变异后,旺财 的物种是: Robot

# 查看 dog2 的属性,它仍然使用类变量
print(f"{dog2.name} 的物种仍然是: {dog2.species}")    # 输出: 小白 的物种仍然是: Mammalia

# 查看类的属性,类变量本身没有被改变!
print(f"类的物种仍然是: {Dog.species}")              # 输出: 类的物种仍然是: Mammalia

总结与最佳实践

操作

通过实例 (e.g., dog1)

通过类 (e.g., Dog)

访问变量

1. 先找实例变量

2. 找不到再找类变量

直接访问类变量

修改变量

创建或修改实例变量,不会影响类变量或其他实例

修改类变量,影响所有实例(除非实例已被遮蔽)

最佳实践:

  1. 明确意图:当你想操作所有实例共享的数据时,使用 ClassName.variable。当你想操作某个特定实例独有的数据时,使用 instance.variable。
  2. 避免歧义:为了代码的可读性和减少错误,强烈建议通过 ClassName.variable 的方式来访问和修改类变量。仅在你明确希望为某个实例创建一个与类变量同名的、独立的属性时,才使用 instance.variable 来 “遮蔽” 类变量。这种情况在实际开发中相对少见。
  3. 可变对象的陷阱:如果类变量是一个可变对象(如列表 list、字典 dict),通过实例修改它的内容(而不是重新赋值)会影响所有实例。因为你修改的是对象本身,而不是变量的引用。
  4. python
  5. 运行
  6. class Cat: toys = [] # 可变的类变量 cat_a = Cat() cat_b = Cat() cat_a.toys.append("毛线球") # 通过实例修改了类变量指向的列表对象 print(cat_a.toys) # 输出: ['毛线球'] print(cat_b.toys) # 输出: ['毛线球'] (cat_b 也受到了影响!) print(Cat.toys) # 输出: ['毛线球'] (类变量本身也变了)
  7. 这是因为 cat_a.toys 找到了类变量 toys,然后调用了它的 append 方法。要避免这种情况,应在 __init__ 中为每个实例创建独立的可变对象。
  8. python
  9. 运行
  10. class Cat: def __init__(self): self.toys = [] # 每个实例都有自己的 toys 列表 cat_a = Cat() cat_b = Cat() cat_a.toys.append("毛线球") print(cat_a.toys) # 输出: ['毛线球'] print(cat_b.toys) # 输出: [] (cat_b 不受影响)

希望这份详细的解释能帮助你彻底理解 Python 中类变量和实例变量的操作!

编辑分享

如何在Python中修改类变量的值?

好的,在 Python 中修改类变量的值是一个非常常见但又容易出错的操作。关键在于修改的方式,不同的方式会导致截然不同的结果。

下面我将为你详细解析两种核心的修改方式、一个常见的陷阱以及最佳实践。

核心方法与陷阱

我们先定义一个简单的类作为例子:

python

运行

class Dog:
    # 这是一个类变量,被所有 Dog 实例共享
    species = "Canidae"

    def __init__(self, name):
        self.name = name

# 创建两个实例
dog1 = Dog("旺财")
dog2 = Dog("小白")

print(f"初始时,类变量 Dog.species 的值是: '{Dog.species}'")
print(f"dog1 看到的 species 是: '{dog1.species}'")
print(f"dog2 看到的 species 是: '{dog2.species}'")
# 输出:
# 初始时,类变量 Dog.species 的值是: 'Canidae'
# dog1 看到的 species 是: 'Canidae'
# dog2 看到的 species 是: 'Canidae'

方法一:通过类名修改(正确的方式)

这是修改类变量最标准、最推荐的方式。当你通过类名来修改时,你是在真正地改变类的属性,这个改变会立即反映到所有的实例上。

操作步骤:

  1. 使用 ClassName.variable_name = new_value 的语法。

示例代码:

python

运行

# 假设所有的狗都进化了,物种名称需要更新
Dog.species = "Mammalia"

print("\n--- 通过类名修改后 ---")
print(f"类变量 Dog.species 的新值是: '{Dog.species}'")
print(f"dog1 看到的 species 现在是: '{dog1.species}'")
print(f"dog2 看到的 species 现在是: '{dog2.species}'")

# 输出:
# --- 通过类名修改后 ---
# 类变量 Dog.species 的新值是: 'Mammalia'
# dog1 看到的 species 现在是: 'Mammalia'
# dog2 看到的 species 现在是: 'Mammalia'

结论:通过 Dog.species = "Mammalia",我们成功地修改了 Dog 类的 species 属性,并且 dog1 和 dog2 这两个已存在的实例访问到的 species 值也随之改变。


方法二:通过实例修改(常见的陷阱)

这种方式不会修改类变量,而是会在该实例内部创建一个同名的实例变量。这个新创建的实例变量会 “遮蔽”(shadowing)掉类变量,使得该实例在后续访问这个变量时,优先使用自己的实例变量。

操作步骤:

  1. 使用 instance_name.variable_name = new_value 的语法。

示例代码:

python

运行

# dog1 发生了变异,它认为自己是一个机器人
# 注意:这一步操作的影响
dog1.species = "Robot"

print("\n--- 通过实例 dog1 修改后 ---")
print(f"dog1 看到的 species 现在是: '{dog1.species}'") # dog1 使用了自己的实例变量
print(f"dog2 看到的 species 仍然是: '{dog2.species}'") # dog2 仍然使用类变量
print(f"类变量 Dog.species 的值是: '{Dog.species}'")    # 类变量本身没有被改变!

# 输出:
# --- 通过实例 dog1 修改后 ---
# dog1 看到的 species 现在是: 'Robot'
# dog2 看到的 species 仍然是: 'Mammalia'
# 类变量 Dog.species 的值是: 'Mammalia'

结论:dog1.species = "Robot" 这行代码并没有修改 Dog 类的 species 属性。它只是给 dog1 这个具体的实例增加了一个名为 species 的实例变量。从这一刻起,当 dog1 访问 species 时,它会直接返回自己的 instance variable,而 dog2 和 Dog 类本身则不受影响。


核心原理:Python 的属性查找机制

理解这一切的关键在于 Python 的属性查找顺序(当你执行 instance.attribute 时):

  1. 先找实例自身:Python 首先会在该实例的内部字典(__dict__)中查找属性。在 dog1.species = "Robot" 之后,dog1.__dict__ 中就有了 {'name': '旺财', 'species': 'Robot'}。而 dog2.__dict__ 中仍然只有 {'name': '小白'}。
  2. 再找类:如果在实例自身找不到,Python 就会去该实例所属的类的字典(__dict__)中查找。在 dog1.species = "Robot" 之前,dog1.__dict__ 中没有 species,所以 Python 去 Dog.__dict__ 中找到了 'species': 'Canidae'。

特殊情况:修改可变类型的类变量

当类变量是一个可变对象(如列表 list、字典 dict、集合 set)时,情况会更加微妙。

如果你通过实例修改该对象的内容(而不是重新赋值),那么这个修改会影响到所有实例,因为你修改的是所有实例共享的那个对象本身。

示例代码:

python

运行

class Dog:
    tricks = []  # 可变的类变量

dog_a = Dog()
dog_b = Dog()

# 通过实例 dog_a 向共享的 tricks 列表中添加元素
dog_a.tricks.append("握手")

print(f"dog_a 的技巧: {dog_a.tricks}") # 输出: dog_a 的技巧: ['握手']
print(f"dog_b 的技巧: {dog_b.tricks}") # 输出: dog_b 的技巧: ['握手'] (dog_b 也看到了!)
print(f"类的技巧: {Dog.tricks}")       # 输出: 类的技巧: ['握手'] (类变量本身也变了)

为什么会这样?

因为 dog_a.tricks 查找时找到了类变量 tricks(一个列表对象),然后调用了它的 append 方法。这个方法直接修改了列表对象的内容,而没有创建新的变量。

如何避免?

如果你不希望可变对象被所有实例共享,应该在 __init__ 方法中为每个实例创建一个独立的副本。

python

运行

class Dog:
    def __init__(self, name):
        self.name = name
        self.tricks = [] # 每个实例都有自己的 tricks 列表

dog_a = Dog("旺财")
dog_b = Dog("小白")

dog_a.tricks.append("握手")

print(f"dog_a 的技巧: {dog_a.tricks}") # 输出: dog_a 的技巧: ['握手']
print(f"dog_b 的技巧: {dog_b.tricks}") # 输出: dog_b 的技巧: [] (dog_b 不受影响)

总结与最佳实践

  1. 明确你的意图:如果你想修改所有实例共享的那个值,请使用 ClassName.variable = new_value。如果你只想为某个特定实例设置一个独立的值,请使用 instance.variable = new_value。这实际上是在创建 / 修改一个实例变量。
  2. 推荐的做法始终通过类名来修改类变量。这能让你的代码意图清晰,避免意外的错误和混淆。仅在你明确需要 “遮蔽” 类变量时,才通过实例进行赋值操作。
  3. 警惕可变对象:当类变量是 list, dict 等可变类型时,要清楚地知道通过实例修改其内容会影响全局。如果这不是你想要的行为,请在 __init__ 中初始化它们。

weibo.com/ttarticle/p/ShOw?id=2309405222534647251102

weibo.com/ttarticle/p/ShOw?id=2309405222521347113124

weibo.com/ttarticle/p/ShOw?id=2309405222530029060449

weibo.com/ttarticle/p/ShOw?id=2309405222531061121283

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...