python变量类型、赋值以及拷贝的深入理解

2023-11-08

目录

一、变量赋值理解

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]]]

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python变量类型、赋值以及拷贝的深入理解 的相关文章

随机推荐