目录
一、变量赋值理解
1、变量分类及理解
(1)变量类型
(2)变量创建的理解
(3)可变变量与不可变变量的区分:
(4)不可变变量的特点:
(5)可变变量的特点:
2、不可变变量赋值
(1)不可变变量赋值
(2)不可变变量间赋值
3、可变变量赋值
二、函数的参数传递
1、不可变变量传参
2、可变变量传参
3、拷贝的区别
一、变量赋值理解
1、变量分类及理解
Python是“万物皆对象”的理念,所以没有指针和引用的概念,导致很多时候参数的传递和调用的时候会产生疑问:我到底是复制了一份新的做操作还是在它指向的内存操作?这个问题根本上和可变、不可变变量有关。
(1)变量类型
所有的变量都可以按可变、不可变变量来分类,那么我将常用的一些变量类型进行分类。
整型、浮点型、布尔型、复数、字符串、元组
列表、集合、字典、其他迭代器
(2)变量创建的理解
首先我们需要明白变量是如何被创建的,这对理解变量赋值及可变不可变的区别有很大帮助
对于不可变变量来说,python解释器会判断该值在内存中是否存在,若不存在,python会分配内存,在内存中创建该值,然后看变量是否存在,若不存在就创建变量,最后把该值赋值给变量。
x = 1
y = 1
如上情况,此时x与y的关系是什么呢?
执行x=1时,python会分配内存,在内存中创建数字1,然后创建x,最后把1赋值给x;当执行y=1时,1已经在内存中被创建了,就不会再创建了,会直接将刚才就创建好的1赋值给y,我们可以通过id或者is来判断x与y是否在内存中是同一个地址。
print(id(x) == id(y))
# or
print(x is y)
# 结果
True
True
可变变量的创建与不可变变量是完全相反的,每次创建时python都会分配新的内存空间来存储列表,所以内存空间内会存在相同的值。
a = [1,2,3]
b = [1,2,3]
print(id(a) == id(b))
# 结果
False
(3)可变变量与不可变变量的区分:
按照我的个人理解,可变变量(mutable variable)就是内存中内容可以被修改的变量,而不可变变量(immutable variable)就是内存中内容不可以被修改的变量。
听起来比较抽象。我们可以把变量名理解为一个指针或者引用,他们都指向了内存中的一块空间。比如上节中x和y同时指向了1的那块空间,a指向了[1,2,3]的那部分。那么如何理解内存中的内容可以被修改呢?对于一个int型的变量,我创建了1,那么这之后*任何在这个作用域中所有被赋为1 的变量都会指向它,而外部没有方法来把这个内存中的内容改成2或者其他值*。而对于列表来说,那块[1,2,3]我随时可以把他改为[1,2,3,4],这就是内存中内容可以被修改。
(4)不可变变量的特点:
- 优点:这样可以减少重复的值对内存空间的占用。
- 缺点:我要修改这个变量绑定的值,如果内存中没用存在该值的内存块,那么必须重新开辟一块内存,把新地址与变量名绑定。而不是修改变量原来指向的内存块的值,这回给执行效率带来一定的降低。
(5)可变变量的特点:
- 缺点:创建一次就开辟一次地址,不会引用同一块内存,所以会比不可变变量占用内存空间。
2、不可变变量赋值
(1)不可变变量赋值
python是一门很特殊的语言,变量在声明的时候并不定义它的类型,而是通过赋值的类型体现出来,所以一般python变量在声明时需要赋初值,。
# 变量赋值
a = 1
int b = 2
在C语言中, 给变量赋值时,需要先指定数据类型,同时会开辟一块内存区域,用于存储值;而在Python中,“变量”的严格叫法是“名字(name)”,也可以理解为标签,就像每个人的姓名一样,只是标记我们的一个标签。这种方式是和python“万物皆对象”的思想相契合的。
从上面可以看到,在python中,当执行: a = 1
python首先会在分配一段内存空间用来创建int型对象“1”,然后用a指向它;
(2)不可变变量间赋值
a = 1
b = a
a = 2
# 此时,a=?b=?
变量a被赋值,意味着a指向了内存中的int类对象1的,变量b赋值变量a,并不代表b指向了变量a,而是变量b同时也指向了int类对象1,此时,对象1可通过a或b引用,此时a和b的地址和值都是一样的,当变量a修改赋值时,只是代表变量a与其对象的指向关系发生了变化,变量a指向新的对象2,并不会影响b的指向关系,所以最终的输出结果为:a = 2,b = 1。
3、可变变量赋值
以列表为例:可变变量间赋值的话,a和c的地址id相同,变量a的内容修改后,c的内容相应变化。
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print("id(a) == id(c) :%s", id(a) == id(c))
print("id(a) == id(b) :%s", id(a) == id(b))
a[0] = 10
print("id(a) == id(c) :%s", id(a) == id(c))
print(a)
print(c)
二、函数的参数传递
1、不可变变量传参
不可变变量作为函数参数传参时,是不会改变外部变量的值,这里可以理解为,不可变变量作为参数传参时仅传递值。
def test(a):
a = a+1
print(a)
b = 1
test(b)
print(b)
# 结果
2
1
这里把形参g传递到函数中,相当于执行 a = g ,这时a和g指向同一个对象,在函数内部给a重新赋值,其实是让a重新指向一个新的对象,并不会对g的值造成影响。
2、可变变量传参
def test(a):
a[0] = 10
print(a)
b = [1, 2, 3]
test(b)
print(b)
# 结果
[10, 2, 3]
[10, 2, 3]
为什么这次就可以用函数改变外部变量呢?对可变变量,本身内容就可以被修改,那么在函数内部也允许修改本身,因此传递进函数的是该内存空间的引用,对该参数操作,这时候就能修改外部变量了。
所以,在python中,函数能不能修改外部变量,主要看这个变量的类型是可修改的还是不可修改的。
3、拷贝的区别
在函数传参时,我们时而需要修改外部的变量,时而不需要修改外部的变量,所以这个时候我们就需要用到拷贝了。拷贝的方式有两种,copy和deepcopy,那么二者的区别就在于可变、不可变对象。
copy和deepcopy对于不可变对象来说是没有区别的,都是把指向内存已有的空间的引用进行拷贝。
# copy拷贝
def test(a, id_b):
import copy
a = copy.copy(a)
print("id(a) == id(b) 是:%s", id(a) == id_b)
a = a + 10
print(a)
b = 0
test(b, id(b))
print(b)
# 结果
id(a) == id(b) 是:%s True
10
0
# deepcopy拷贝
def test1(a, id_b):
import copy
a = copy.deepcopy(a)
print("id(a) == id(b) 是:%s", id(a) == id_b)
a = a + 10
print(a)
b = 0
test1(b, id(b))
print(b)
# 结果
id(a) == id(b) 是:%s True
10
0
但对于可变对象,copy是拷贝指向内存已有空间的引用,而deepcopy才是新开辟一块空间,将原来的内容完全拷贝,然后返回新的空间的引用。
copy(浅拷贝)& deepcopy(深拷贝)【结果完全一致】
当拷贝的可变变量的子变量是不可变变量时,对自变量进行修改,如下原始变量b和拷贝后的变量a,分别指向子变量,当进行修改a[0]时,可以看做a[0]和b[0]分别为指向1的变量,此时,修改a[0]为10,会给10重新分配存储空间,也就是a[0]指向10的存储空间,b[0]的指向关系不会有变化。此处可参考(2)不可变变量间赋值
# 说明:拷贝后,此时a开辟了新的空间(并不是指向了b的引用),
# 但是 a [0] 、a [1]、a [2]只是分别指向了变量1、2、3的引用
def test(a, id_b):
print("拷贝前id(a)是:%s", id(a))
import copy
a = copy.copy(a)
# a = copy.deepcopy(a)
print("拷贝后id(a)是:%s", id(a))
print("拷贝后id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id_b)
a[0] = 10
return a
b = [1, 2, 3]
print("拷贝前id(b)是:%s", id(b))
a = test(b, id(b[0]))
print("修改后 id(a)是:%s", id(a))
print("修改后 id(b)是:%s", id(b))
print("修改后 id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id(b[0]))
print("修改后 id(a[1]) == id(b[1]) 是:%s", id(a[1]) == id(b[1]))
print(b)
print(a)
# 结果
拷贝前id(b)是:%s 3089483811072
拷贝前id(a)是:%s 3089483811072
拷贝后id(a)是:%s 3089483811328
拷贝后id(a[0]) == id(b[0]) 是:%s True
修改后 id(a)是:%s 3089483811328
修改后 id(b)是:%s 3089483811072
修改后 id(a[0]) == id(b[0]) 是:%s False
修改后 id(a[1]) == id(b[1]) 是:%s True
[1, 2, 3]
[10, 2, 3]
copy(浅拷贝)
当对可变变量进行浅拷贝,其子对象是可变变量时,拷贝的则是可变子对象的引用,可以理解为仅拷贝了空间引用关系,当不可变子对象进行修改时,会重新开辟空间存储,并建立新的引用关系;当可变子对象进行修改时,会直接修改,但是引用关系没有变化。
def test(a):
c = a
print("拷贝前id(a)是:%s", id(a))
import copy
a = copy.copy(a)
print("拷贝后id(b)是:%s", id(c))
print("拷贝后id(a)是:%s", id(a))
print("拷贝后不可变变量 id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id(c[0]))
print("拷贝后可变变量 id(a[2]) == id(b[2]) 是:%s", id(a[2]) == id(c[2]))
print("拷贝后可变变量 id(a[2][0]) == id(b[2][0]) 是:%s", id(a[2][0]) == id(c[2][0]))
print("拷贝后可变变量 id(a[2][2]) == id(b[2][2]) 是:%s", id(a[2][2]) == id(c[2][2]))
print("拷贝后可变变量 id(a[2][2][0]) == id(b[2][2][0]) 是:%s", id(a[2][2][0]) == id(c[2][2][0]))
a[0] = 10
a[2][0] = "c"
return a
b = [1, 2, ["a", "b", [1, 2]]]
print("拷贝前id(b)是:%s", id(b))
a = test(b)
print("修改后 id(a)是:%s", id(a))
print("修改后 id(b)是:%s", id(b))
print("修改后 不可变变量 id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id(b[0]))
print("修改后 可变变量 id(a[2]) == id(b[2]) 是:%s", id(a[2]) == id(b[2]))
print("修改后 可变变量 id(a[2][0]) == id(b[2][0]) 是:%s", id(a[2][0]) == id(b[2][0]))
print("未修改 变量 id(a[1]) == id(b[1]) 是:%s", id(a[1]) == id(b[1]))
print(b)
print(a)
# 结果
拷贝前id(b)是:%s 2480675678720
拷贝前id(a)是:%s 2480675678720
拷贝后id(b)是:%s 2480675678720
拷贝后id(a)是:%s 2480675678272
拷贝后不可变变量 id(a[0]) == id(b[0]) 是:%s True
拷贝后可变变量 id(a[2]) == id(b[2]) 是:%s True
拷贝后可变变量 id(a[2][0]) == id(b[2][0]) 是:%s True
拷贝后可变变量 id(a[2][2]) == id(b[2][2]) 是:%s True
拷贝后可变变量 id(a[2][2][0]) == id(b[2][2][0]) 是:%s True
修改后 id(a)是:%s 2480675678272
修改后 id(b)是:%s 2480675678720
修改后 不可变变量 id(a[0]) == id(b[0]) 是:%s False
修改后 可变变量 id(a[2]) == id(b[2]) 是:%s True
修改后 可变变量 id(a[2][0]) == id(b[2][0]) 是:%s True
未修改 变量 id(a[1]) == id(b[1]) 是:%s True
[1, 2, ['c', 'b', [1, 2]]]
[10, 2, ['c', 'b', [1, 2]]]
deepcopy(深拷贝)
当对可变变量进行深拷贝,其子对象是可变变量时,会给拷贝的可变子对象分配新的空间,并返回新的引用,修改拷贝后的变量,不会影响拷贝前的变量。
def test(a):
c = a
print("拷贝前id(a)是:%s", id(a))
import copy
a = copy.deepcopy(a)
print("拷贝后id(b)是:%s", id(c))
print("拷贝后id(a)是:%s", id(a))
print("拷贝后不可变变量 id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id(c[0]))
print("拷贝后可变变量 id(a[2]) == id(b[2]) 是:%s", id(a[2]) == id(c[2]))
print("拷贝后可变变量 id(a[2][0]) == id(b[2][0]) 是:%s", id(a[2][0]) == id(c[2][0]))
print("拷贝后可变变量 id(a[2][2]) == id(b[2][2]) 是:%s", id(a[2][2]) == id(c[2][2]))
print("拷贝后可变变量 id(a[2][2][0]) == id(b[2][2][0]) 是:%s", id(a[2][2][0]) == id(c[2][2][0]))
a[0] = 10
a[2][0] = "c"
return a
b = [1, 2, ["a", "b", [1, 2]]]
print("拷贝前id(b)是:%s", id(b))
a = test(b)
print("修改后 id(a)是:%s", id(a))
print("修改后 id(b)是:%s", id(b))
print("修改后 不可变变量 id(a[0]) == id(b[0]) 是:%s", id(a[0]) == id(b[0]))
print("修改后 可变变量 id(a[2]) == id(b[2]) 是:%s", id(a[2]) == id(b[2]))
print("修改后 可变变量 id(a[2][0]) == id(b[2][0]) 是:%s", id(a[2][0]) == id(b[2][0]))
print("未修改 变量 id(a[1]) == id(b[1]) 是:%s", id(a[1]) == id(b[1]))
print(b)
print(a)
# 结果
拷贝前id(b)是:%s 1733009275136
拷贝前id(a)是:%s 1733009275136
拷贝后id(b)是:%s 1733009275136
拷贝后id(a)是:%s 1733009274688
拷贝后不可变变量 id(a[0]) == id(b[0]) 是:%s True
拷贝后可变变量 id(a[2]) == id(b[2]) 是:%s False
拷贝后可变变量 id(a[2][0]) == id(b[2][0]) 是:%s True
拷贝后可变变量 id(a[2][2]) == id(b[2][2]) 是:%s False
拷贝后可变变量 id(a[2][2][0]) == id(b[2][2][0]) 是:%s True
修改后 id(a)是:%s 1733009274688
修改后 id(b)是:%s 1733009275136
修改后 不可变变量 id(a[0]) == id(b[0]) 是:%s False
修改后 可变变量 id(a[2]) == id(b[2]) 是:%s False
修改后 可变变量 id(a[2][0]) == id(b[2][0]) 是:%s False
未修改 变量 id(a[1]) == id(b[1]) 是:%s True
[1, 2, ['a', 'b', [1, 2]]]
[10, 2, ['c', 'b', [1, 2]]]