目录
前言
python中Numbers(数字),String(字符串),List(列表),Tuple(元组),Dictionary(字典),Set(集合)这几种数据类型,在不同的数据类型下,变量赋值操作会有不同的表现结果,其中Numbers,String,Tupes属于不可变类型,List,Dictonary和Set属于可变类型,对于可变类型和不可变类型的区别可以参考python可变类型与不可变类型 - anne199534 - 博客园,大概就是同一地址指向的内存空间的值是否可变,同一地址指向的内存值可变即为可变类型,同一地址指向的内存值不可变即为不可变类型。下面以不可以变类型和可变类型分析python赋值操作
一、不可变类型的赋值
变量是指向内存空间地址的别名,或者说是变量名绑定了(引用)内存某一内存的地址。不可变类型的变量在首次赋值操作时,解释器会在先在内存中分配空间,然后把变量名绑定到空间的地址上。如果变量名和变量值已经被使用过,则解释器不会分配新的内存空间,仍然保持原来的内存空间和内存值,只是把等号左边的变量名绑定到那片已经分配的内存地址上
1、Numbers的赋值
代码如下(示例):
a=34;##首次使用,分配内存,a为地址,34是内存值
print(id(a))
print(id(34))
print(f'a初始化值={a}')
a=66;##a解绑34对应的地址,重新绑定66对应的地址
print(id(a))
print(f'a重新赋值={a}')
b=89;
print(f'b初始化地址{id(b)}')
print(f'b初始化值{b}')
a=b ###a被绑定到b的内存地址上
print(id(a))
print(id(b))
执行结果:
1323983439120
1323983439120
a初始化值=34
1323983440144
a重新赋值=66
b初始化地址1323983440880
b初始化值89
1323983440880
1323983440880
2.String类型的赋值
代码如下(示例):
a='henhao'
print(f'a首次绑定地址为{id(a)},值为{a}')
a='henhao'##'henhao'地址已经被分配,仍然不变
print(f'a再次绑定地址为{id(a)},值为{a}')
a='nihao'##绑定新值'nihao'的内存空间
print(f'a再次绑定地址为{id(a)},值为{a}')
a首次绑定地址为2160371553968,值为henhao a再次绑定地址为2160371553968,值为henhao a再次绑定地址为2160371554096,值为nihao
3.Tupes类型赋值
a=('hello','world','nice','love')
print(f'a首次绑定地址为{id(a)},值为{a}')
a=(1,2)
print(f'a再次绑定地址为{id(a)},值为{a}')
结果如下:
a首次绑定地址为1756499367696,值为('hello', 'world', 'nice', 'love')
a再次绑定地址为1756499507776,值为(1, 2)
4.函数传参赋值
def swap(a,b):
a,b=b,a
print(f'函数作用域下a的地址{id(a)},a的值为{a}');
print(f'函数作用域下b的地址{id(b)},b的值为{b}');
if __name__=='__main__':
a=23;
b=15;
print(f'函数调用前a的地址{id(a)},值为{a},b的地址为{id(b)},值为{b}')
swap(a,b)
print(f'函数调用后a的地址{id(a)},值为{a},b的地址为{id(b)},值为{b}')
结果:
函数调用前a的地址1800340374448,值为23,b的地址为1800340374192,值为15
函数作用域下a的地址1800340374192,a的值为15
函数作用域下b的地址1800340374448,b的值为23
函数调用后a的地址1800340374448,值为23,b的地址为1800340374192,值为15
分析:
swap函数没有实现交换两值,在swap函数中a=b,a解绑原来的内存地址,绑定b的值15,a在函数域中变化,因为不同作用域下的变量名互不影响,所以在main函数作用域中,a变量名绑定仍然是23的内存地址。同时也印证了数字类型是不可变类型。里面可以从还有python的引用计数和垃圾回收的角度分析
二、可变类型的赋值
可变类型的赋值可以通过调用该类型的方法来改变当前变量的内存值或者改变当前变量的内存地址,达到改变当前变量的效果。这里以List详细分析
1.List赋值
list1=[23,'asd',89]
print(f'list1的地址{id(list1)},值为{list1}')
list1.append(45)##变量绑定的地址不变,改变地址绑定的内存值
print(f'list1 apend的地址{id(list1)},值为{list1}');
print(f'list1[0]的地址{id(list1[0])},值为{list1[0]}');
list1[0]='hello'##list1[0]变量绑定地址变化,导致值变化,但是列表list1的地址不变
print(f'赋值后list1[0]的地址{id(list1[0])},值为{list1[0]}');
print(f'list1的地址{id(list1)},值为{list1}');
list1=['henhao',90]##变量与原地址解绑,改变绑定的地址
print(f'list1 重新赋值后的地址{id(list1)},值为{list1}')
结果
list1的地址1724504826240,值为[23, 'asd', 89]
list1 apend的地址1724504826240,值为[23, 'asd', 89, 45]
list1[0]的地址1724480553904,值为23
赋值后list1[0]的地址1724500281968,值为hello
list1的地址1724504826240,值为['hello', 'asd', 89, 45]
list1 重新赋值后的地址1724504618368,值为['henhao', 90]
分析:可见在同一作用域下,list的地址不变,改变地址的值,list的内部元素如果是不可变类型是地址发生改变,导致内存值变化 。如果是新的list,赋值给变量名,那么变量名绑定新的地址。(原地址的引用计数为0,内存被回收?新地址的引用计数为1,因此print输出是新内存值?)
内存释放,垃圾回收参见:Python内存管理及释放_jiangjiane的博客-CSDN博客_python 释放内存
2、函数传参
def swap1(mlist):
print(f'swap1函数作用域赋值前的地址{hex(id(mlist[2]))},值为{mlist[2]},b的地址为{hex(id(mlist[3]))},值为{mlist[3]}')
print(f'swap1函数作用域赋值前list的地址{hex(id(mlist))}')
mlist[2],mlist[3]=mlist[3],mlist[2]
print(f'swap1函数作用域赋值后的地址{hex(id(mlist[2]))},值为{mlist[2]},b的地址为{hex(id(mlist[3]))},值为{mlist[3]}')
print(f'swap1函数作用域赋值后list的地址{hex(id(mlist))}')
mlist.append(34)
print(f'函数作用域append后list的地址{hex(id(mlist))},值为{mlist}')
mlist=[345]
print(f'函数作用域append后list的地址{hex(id(mlist))},值为{mlist}')
if __name__=='__main__':
mlist=[34,'nihao',89,'jiu']
print(f'main作用域赋值前list的地址{hex(id(mlist))},内存值为{mlist}')
swap1(mlist)
print(f'main作用域赋值后list的地址{hex(id(mlist))},内存值为{mlist}')
结果:
main作用域赋值前list的地址0x241c1ca3a00,内存值为[34, 'nihao', 89, 'jiu']
swap1函数作用域赋值前的地址0x241c05e0bf0,值为89,b的地址为0x241c1cd63f0,值为jiu
swap1函数作用域赋值前list的地址0x241c1ca3a00
swap1函数作用域赋值后的地址0x241c1cd63f0,值为jiu,b的地址为0x241c05e0bf0,值为89
swap1函数作用域赋值后list的地址0x241c1ca3a00
函数作用域append后list的地址0x241c1ca3a00,值为[34, 'nihao', 'jiu', 89, 34]
函数作用域append后list的地址0x241c1c71600,值为[345]
main作用域赋值后list的地址0x241c1ca3a00,内存值为[34, 'nihao', 'jiu', 89, 34]
分析:
对于list,传参后如果采用函数方法后,list的地址不变,list元素的地址变化mlist[2],mlist[3]=mlist[3],mlist[2]或者list的地址的内存发生变化append,都会导致原来list的值发生变化,在swap函数作用下对变量别名解绑后,赋新值后的作用域只在swap有效,在main函数中的仍然是list的原地址值。
总结
无论是可变类型和不可变类型,都取决于当前作用域的动作,在当前作用域变量名解绑后,当前作用域的值都为变量名绑定的新值。如果在不同作用域下,对于可变类型,若想改变主函数作用域的值可以在子函数(其他作用域下)调用方法来修改,但解绑操作只能修改当前子函数作用域下的变量值,不能改变主函数域下的值