实例级别变量和类级别变量区别

本文讨论了Python中类级别变量与实例级别变量的区别,包括它们的作用域、生命周期、访问方式和默认值。通过代码示例展示了类级别变量如何在所有实例间共享,以及如何通过实例级别变量实现每个对象独立的存储。最后,解释了为何在某些操作下,对类级别变量的修改会影响到所有实例,而在其他情况下则不会。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录遇到的问题

综述

实例级别变量和类级别变量是面向对象编程中两个不同的概念,它们的作用域和生命周期不同,具体区别如下:

  1. 作用域

类级别变量是在整个类中可见的,也就是说,所有该类的实例共享同一个类级别变量。

而实例级别变量是在实例化对象时创建的,每个对象都有自己独立的实例级别变量。

  1. 生命周期

类级别变量的生命周期与类相同,即在程序执行期间,只要类存在,类级别变量也存在。

而实例级别变量的生命周期与实例对象相同,即在实例对象被销毁时,实例级别变量也被销毁。

  1. 访问方式

类级别变量可以通过类名或实例对象访问,因为所有该类的实例共享同一个类级别变量。

而实例级别变量只能通过实例对象访问,因为每个对象都有自己独立的实例级别变量。

  1. 默认值

类级别变量的默认值在类定义时就确定了,即使没有实例化任何对象,该变量也存在。

而实例级别变量在实例化对象时才被创建,并且默认值是None

总的来说,实例级别变量和类级别变量在作用域、生命周期、访问方式和默认值等方面都存在明显的不同,因此在编写面向对象程序时需要根据具体需求选择适合的变量类型

举例如下

1、list是类级别变量

class pclass:
    list = []


L = [pclass() for p in range(3)]

L[1].list.append([1, 2])
print(L[0].list)
print(L[1].list)
print(L[2].list)

L[1].list.remove([1, 2])
print(L[0].list)
print(L[1].list)
print(L[2].list)

这段代码的结果是

[[1, 2]]
[[1, 2]]
[[1, 2]]
[]
[]
[]

三者中的L[ ].list共享

在这个代码中,list是一个类级别变量,这意味着该变量是类的所有实例共享的。

在这个大前提下

1、修改list的情况

pclass类的定义中,使用list = [] 定义了一个类级别变量 list。这意味着所有 pclass 类的实例都共享同一个 list 变量。当你创建 L 列表中的每个实例时,它们都是 pclass 类的实例,因此它们都共享同一个 list 变量。

当我们在L[1].list上调用append方法添加元素时,它会将元素添加到list列表中,这会影响所有其他实例的 list 变量,因为它们都指向同一个对象。

然后,我们打印L[2].list的值,L[2]L[1]都是pclass类的实例,它们都共享同一个类级别的变量list。因此,当我们打印L[2].list时,它实际上是打印L[1].list

2、不修改list的情况

当我们在L[1].list上使用赋值时,它会将list指向赋值元素,但并不改变。

我们打印L[2].list的值,虽然L[2]L[1]都是pclass类的实例,它们都共享同一个类级别的变量list。但是赋值操作并没有改变L[2].list,只是将它指向一个实例对象。因此,当我们打印L[2].list时,它不再等于L[1].list

比如将

L[1].list.append([1, 2])

改为

L[1].list = [1, 2]

此时的结果

[]
[1, 2]
[]

在这个例子中,list 是实例级别变量。

pclass 类的 __init__ 方法中,使用 self.list 定义了一个实例级别变量 list,它会被分配给 pclass 的每个实例。当你创建一个实例时,会自动创建一个 list 变量,它会与这个实例相关联。

因此,当你创建 L 列表中的每个实例时,都会创建一个新的 list 变量,它们是彼此独立的。在 L[1].list.append(2) 中,你只是向 L[1] 对象的 list 变量添加了一个值,并不会影响其他对象的 list 变量。所以,打印 L[2].list 时会输出空列表 []

总结

pclass.list.append(1)L[1].list = [1, 2] 的区别在于:

前者是直接对类级别变量 list 进行操作,而后者是对 L[1] 对象的实例级别变量 list 进行赋值。

L[0].listL[1].listL[2].list 都指向同一个类级别变量 list,它们在 pclass 类中定义,并且由于是类级别变量,因此在所有 pclass 对象中共享。因此,当我们修改其中一个对象的 list 变量时,所有对象的 list 变量的值都会发生相应的变化。

然而,当我们执行 L[1].list = [1, 2] 时,我们实际上是给 L[1] 对象的 list 实例级别变量赋值,而不是修改类级别变量的值。这会使 L[1].list 指向一个新的列表对象,这个实例级别变量只对 L[1] 对象可见,不会影响其他对象。而 L[0].listL[2].list 仍然指向共享的类级别变量 list,即空列表 []

扩展:

直接赋值操作不会改变 pclass.list,因为它是一个类级别变量,而赋值操作会创建一个新的实例级别变量。

对于类级别变量 pclass.list,只有在调用类级别变量所属的类的名称来更改它,或者使用以下语法之一更改类级别变量的值:

  1. pclass.list.append(item)

  1. pclass.list.extend(sequence)

  1. pclass.list.insert(index, item)

  1. pclass.list.remove(item)

  1. pclass.list.pop([index])

  1. pclass.list.sort([key, reverse])

  1. pclass.list.reverse()

2、list是实例级别变量

如果您想让每个L对象都拥有自己独立的list列表,而不是共享一个类级别变量,可以将list变量定义为实例级别变量。

这可以通过在类定义中使用__init__方法来实现,在创建每个实例时初始化它们的list变量。

以下是示例代码:

class pclass:
    def __init__(self):
        self.list = []


L = [pclass() for p in range(3)]

L[1].list.append(2)
print(L[0].list)
print(L[1].list)
print(L[2].list)

此时的结果为

[]
[2]
[]

在这个新代码中,我们将list变量移动到__init__方法中,以便每个实例都可以拥有自己的list列表。

这样,当我们在L[1].list上调用append方法时,它只会将元素添加到L[1]对象自己的list列表中,并不会影响到其他对象的list列表。

注意,我们在创建实例时使用了一个循环,这将创建一个包含3个pclass对象的列表。

3、应用

class pclass:
    list = []


L = [pclass() for p in range(3)]

L[1].list.append(2)
print(L[0].list)
print(L[1].list)
print(L[2].list)

L[1].list = [2]
print(L[0].list)
print(L[1].list)
print(L[2].list)

L[1].list.append(2)
print(L[0].list)
print(L[1].list)
print(L[2].list)

结果

[2]
[2]
[2]
[2]
[2]
[2]
[2]
[2, 2]
[2]

此时的问题是

为什么第一次L[1].list.append(2)改变了print(L[0].list)和print(L[2].list)

而第二次L[1].list.append(2)就无法改变print(L[0].list)和print(L[2].list)?

在第一次操作 L[1].list.append(2) 时,会在 pclass 类的类级别变量 list 中创建一个列表对象,然后将 L[1].list 指向该列表对象。由于列表对象是可变的,所以在之后向 L[1].list 所指向的列表对象中添加元素时,也就相当于在 pclass 类的类级别变量 list 中的列表对象中添加了元素,因此第一次操作可以改变所有 L 中的 list。

而在第二次操作 L[1].list = [2] 时,会将 L[1].list 指向一个新的列表对象 [2],这个列表对象与 pclass 类的类级别变量 list 中的列表对象不同。因此,之后对 L[1].list 中的列表对象进行操作时,不会对其他 L 中的 list 产生影响,也就是第二次操作无法改变其他 L 中的 list。

class pclass:
    def __init__(self):
        self.list = []

实质上也是在类创立之初就给每个L分配了实例对象

这么看来,对第一种情况,如果改为

class pclass:
    list = []


L = [pclass() for p in range(3)]

L[1].list = []
L[1].list.append([1, 2])
print(L[0].list)
print(L[1].list)
print(L[2].list)

只多加了一行

L[1].list = []

那么结果是

关于循环的一个小问题:

a = 5
for i in range(a):
    a=a-1
    print(i)


为什么for i in range(a)里面的a并没有被a=a-1影响

for 循环中,range() 函数只在循环开始之前计算一次,因此 range(a) 中的 a 变量只在循环开始之前计算一次。当循环开始时,range() 函数已经生成了一个可迭代的序列,并且这个序列是固定的,不会受到 a 变量的变化的影响。

在循环体内部修改 a 变量的值,不会影响 range(a) 的结果。因此,无论循环体内部如何修改 a 的值,循环都会按照最初生成的 range(a) 序列进行迭代,这就是为什么在循环体内部修改 a 的值并不会影响循环的迭代行为的原因。

for i in range(5):
    i = i + 1
    print(i)    

为什么for i in range(5)里面的i并没有被i = i + 1影响

for i in range(5) 循环中,i 是一个循环变量,其值由 range(5) 逐次提供,只在循环开始之前计算一次。

在每一次循环迭代中,i 的值被赋值为当前迭代的下一个值。因此,在循环体中 i = i + 1 修改的是当前迭代中的 i,而不是整个循环中的 i

实质是一个赋值操作

具体来说,在第一次迭代中,i 的初始值为 0,循环体中的 i = i + 1i 的值修改为 1,然后打印输出 1。在第二次迭代中,i 的初始值为 1,循环体中的 i = i + 1i 的值修改为 2,然后打印输出 2。以此类推,直到循环结束。

因此,在循环结束后,i 的值仍然是最后一次迭代中的值,也就是 4。因为循环中的 i = i + 1 操作只是修改了循环体中的 i,而不会对整个循环中的 i 产生影响。

对比C++

#include <iostream>
using namespace std;
 
int main ()
{
   // for 循环执行
   for( int a = 10; a < 20; a = a + 1 )
   {
       a = a + 1;
       cout << "a 的值:" << a << endl;
   }
 
   return 0;
}

为什么for( int a = 10; a < 20; a = a + 1 )里面的a会被a = a + 1;改变

这两个例子中的变量作用域不同。

在第一个例子中,ifor 循环的控制变量,其作用域只在循环内部。每次循环,都会为 i 分配一个新的值,而不会使用上一次循环中的值。在 for 循环中,使用 range(5) 会生成一个序列 [0, 1, 2, 3, 4],每次迭代循环,将序列中的一个值赋值给变量 i。i 只是一个循环计数器,并不影响循环本身,所以在循环内部对 i 进行赋值操作并不会影响循环。

而在第二个例子中,a 的作用域是整个 main 函数,所以在 for 循环内部对 a 进行修改后,下一次循环时 a 的值已经发生了变化,因此循环次数也会受到影响。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值