python中赋值引用、浅拷贝、深拷贝

本文深入探讨Python中变量引用的概念,包括浅拷贝与深拷贝的区别,并通过实例解析了函数参数传递机制及常见陷阱。

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

python中一切赋值都是引用

比如说在list中:

t=[1,2,2]
def main(n):
    n[0]=3
    return n
main(t)
print(t)
'输出是t[3,2,2]'

同样,使用return的返回值也是一样的结果,因为无论怎么说,在python中的赋值都是引用,变量本身就是一个标签,只是我们可以通过变量取访问内存中的数据而已。
在实际的使用过程中如果需要复制一个变量,使用copy模块,这就涉及到copy模块的深拷贝和浅拷贝问题:
对于list,可以使用

b=[1,2,2]
a=b[:]

需要注意的一点
对于可变对象和不可变对象,都是浅拷贝,但是对于不可变对象(字符串和tupple),如果对象发生了修改,则会开辟新的存储空间来保存这个变量,如果一个对象中既有可变对象,又有不可变对象,则是分别用他们自己的准则来决定是不是要开辟空间。

浅拷贝

浅拷贝就是在使用copy.copy来开辟内存空间的时候出现的
比如如果是直接赋值

a=['asd',[1,2,3]]
b=a
b[0]='asds'
b[1].append(4)

输出a和b的结果是:

['asds', [1, 2, 3, 4]]
['asds', [1, 2, 3, 4]]

被拷贝对象和拷贝对象的值都发生了更改,如果我们使用copy.copy()来进行浅拷贝,比如:

a=['asd',[1,2,3]]
b=copy.copy(a)
b[0]='asds'
b[1].append(4)

输出a和b的结果是:

['asd', [1, 2, 3, 4]]
['asds', [1, 2, 3, 4]]

所以说浅拷贝的本质也只是在其他内存上开了个小空间来存储对原来对象的引用,如果新拷贝的对象的值发生了更改,那个会判断这个是不是可变对象,如果是可变对象,那么被拷贝对象和拷贝对象的值都发生改变,如果是不可变对象,则开辟新的空间来存储。

深拷贝

深拷贝当然就不用说了,就是开内存存储被拷贝对象的所有信息!

a=['asd',[1,2,3]]
b=copy.copy(a)
b[0]='asds'
b[1].append(4)

输出的a和b的结果是:

['asd', [1, 2, 3]]
['asds', [1, 2, 3, 4]]

关于python值传递、作用域等问题的解释

def func_int(a):
    a += 4
def func_list(a_list):
    a_list[0] = 4
t = 0
func_int(t)
print t
# output: 0

t_list = [1, 2, 3]
func_list(t_list)
print t_list
# output: [4, 2, 3]

对于上面的输出,不少Python初学者都比较疑惑:第一个例子看起来像是传值,而第二个例子确实传引用。其实,解释这个问题也非常容易,主要是因为可变对象和不可变对象的原因:对于可变对象,对象的操作不会重建对象,而对于不可变对象,每一次操作就重建新的对象。
在函数参数传递的时候,Python其实就是把参数里传入的变量对应的对象的引用依次赋值给对应的函数内部变量。参照上面的例子来说明更容易理解,func_int中的局部变量”a”其实是全部变量”t”所指向对象的另一个引用,由于整数对象是不可变的,所以当func_int对变量”a”进行修改的时候,实际上是将局部变量”a”指向到了整数对象”1”。所以很明显,func_list修改的是一个可变的对象,局部变量”a”和全局变量”t_list”指向的还是同一个对象。

为什么修改字典d的值不用global关键字先声明呢?

s = 'foo'
d = {'a':1}
def f():
    s = 'bar'
    d['b'] = 2
f()
print s  # foo
print d  # {'a': 1, 'b': 2}

这是因为,在s = ‘bar’这句中,它是“有歧义的“,因为它既可以是表示引用全局变量s,也可以是创建一个新的局部变量,所以在python中,默认它的行为是创建局部变量,除非显式声明global,global定义的本地变量会变成其对应全局变量的一个别名,即是同一个变量。
在d[‘b’]=2这句中,它是“明确的”,因为如果把d当作是局部变量的话,它会报KeyError,所以它只能是引用全局的d,故不需要多此一举显式声明global。
上面这两句赋值语句其实是不同的行为,一个是rebinding(不可变对象), 一个是mutation(可变对象).

但是如果是这样
d = {'a':1}
def f():
    d = {}
    d['b'] = 2
f()
print d  # {'a': 1}

在d = {}这句,它是”有歧义的“了,所以它是创建了局部变量d,而不是引用全局变量d,所以d[‘b’]=2也是操作的局部变量。
推而远之,这一切现象的本质就是”它是否是明确的“。
仔细想想,就会发现不止dict不需要global,所有”明确的“东西都不需要global。因为int类型str类型之类的不可变对象,每一次操作就重建新的对象,他们只有一种修改方法,即x = y, 恰好这种修改方法同时也是创建变量的方法,所以产生了歧义,不知道是要修改还是创建。而dict/list/对象等可变对象,操作不会重建对象,可以通过dict[‘x’]=y或list.append()之类的来修改,跟创建变量不冲突,不产生歧义,所以都不用显式global。

陷阱1

=和append不一样

list_a = []
def a():
    list_a = [1]      ## 语句1
a()
print list_a    # []
list_b = []
def b():
    list_b.append(1)    ## 语句2
b()
print list_b    # [1]

因为 = 创建了局部变量,而 .append() 或者 .extend() 重用了全局变量。

陷阱2

不要再参数中指明是list

In[2]: def foo(a, b, c=[]):
...        c.append(a)
...        c.append(b)
...        print(c)
...
In[3]: foo(1, 1)
[1, 1]
In[4]: foo(1, 1)
[1, 1, 1, 1]
In[5]: foo(1, 1)
[1, 1, 1, 1, 1, 1]

相关链接
引用和赋值
图解深拷贝和浅拷贝
关于引用和赋值以及拷贝作用域等等,很详细
深拷贝和浅拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值