记录遇到的问题
综述
实例级别变量和类级别变量是面向对象编程中两个不同的概念,它们的作用域和生命周期不同,具体区别如下:
作用域
类级别变量是在整个类中可见的,也就是说,所有该类的实例共享同一个类级别变量。
而实例级别变量是在实例化对象时创建的,每个对象都有自己独立的实例级别变量。
生命周期
类级别变量的生命周期与类相同,即在程序执行期间,只要类存在,类级别变量也存在。
而实例级别变量的生命周期与实例对象相同,即在实例对象被销毁时,实例级别变量也被销毁。
访问方式
类级别变量可以通过类名或实例对象访问,因为所有该类的实例共享同一个类级别变量。
而实例级别变量只能通过实例对象访问,因为每个对象都有自己独立的实例级别变量。
默认值
类级别变量的默认值在类定义时就确定了,即使没有实例化任何对象,该变量也存在。
而实例级别变量在实例化对象时才被创建,并且默认值是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].list、L[1].list 和 L[2].list 都指向同一个类级别变量 list,它们在 pclass 类中定义,并且由于是类级别变量,因此在所有 pclass 对象中共享。因此,当我们修改其中一个对象的 list 变量时,所有对象的 list 变量的值都会发生相应的变化。
然而,当我们执行 L[1].list = [1, 2] 时,我们实际上是给 L[1] 对象的 list 实例级别变量赋值,而不是修改类级别变量的值。这会使 L[1].list 指向一个新的列表对象,这个实例级别变量只对 L[1] 对象可见,不会影响其他对象。而 L[0].list 和 L[2].list 仍然指向共享的类级别变量 list,即空列表 []。
扩展:
直接赋值操作不会改变 pclass.list,因为它是一个类级别变量,而赋值操作会创建一个新的实例级别变量。
对于类级别变量 pclass.list,只有在调用类级别变量所属的类的名称来更改它,或者使用以下语法之一更改类级别变量的值:
pclass.list.append(item)
pclass.list.extend(sequence)
pclass.list.insert(index, item)
pclass.list.remove(item)
pclass.list.pop([index])
pclass.list.sort([key, reverse])
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 + 1 将 i 的值修改为 1,然后打印输出 1。在第二次迭代中,i 的初始值为 1,循环体中的 i = i + 1 将 i 的值修改为 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;改变
这两个例子中的变量作用域不同。
在第一个例子中,i 是 for 循环的控制变量,其作用域只在循环内部。每次循环,都会为 i 分配一个新的值,而不会使用上一次循环中的值。在 for 循环中,使用 range(5) 会生成一个序列 [0, 1, 2, 3, 4],每次迭代循环,将序列中的一个值赋值给变量 i。i 只是一个循环计数器,并不影响循环本身,所以在循环内部对 i 进行赋值操作并不会影响循环。
而在第二个例子中,a 的作用域是整个 main 函数,所以在 for 循环内部对 a 进行修改后,下一次循环时 a 的值已经发生了变化,因此循环次数也会受到影响。