python引用

在 python 中赋值语句总是建立对象的引用值,而不是复制对象。因此,python 变量更像是指针,而不是数据存储区域

这点和大多数 OO 语言类似吧,比如 C++、java 等。

首先来看个问题

在Python中,令values=[0,1,2];values[1]=values,为何结果是[0,[...],2]?

 
  1. >>> values = [0,1,2]

  2. >>> values[1] = values

  3. >>> values

  4. [0, [...], 2]

我预想的是

[0,[0,1,2],2]

但结果却赋值了无限次

问题在于Python没有赋值,只有引用,上面这个操作相当于创建了一个引用自身的结构,所以导致了无限循环。

下面这个例子可以帮助理解:

首先执行

values = [0,1,2]

Python 做的事情是首先创建一个列表对象 [0, 1, 2],然后给它贴上名为 values 的标签。如果随后又执行

values = [3,4,5]

Python 做的事情是创建另一个列表对象 [3, 4, 5],然后把刚才那张名为 values 的标签从前面的 [0, 1, 2] 对象上撕下来,重新贴到 [3, 4, 5] 这个对象上。

至始至终,并没有一个叫做 values 的列表对象容器存在,Python 也没有把任何对象的值复制进 values 去。过程如图所示:

现在我们回到之前那个问题,当执行

values[1] = values

Python做的事情则是把 values 这个标签所引用的列表对象的第二个元素指向 values 所引用的列表对象本身。执行完毕后,values 标签还是指向原来那个对象,只不过那个对象的结构发生了变化,从之前的列表 [0, 1, 2] 变成了 [0, ?, 2],而这个 ? 则是指向那个对象本身的一个引用。如图所示:

要达到你所需要的效果,即得到 [0, [0, 1, 2], 2] 这个对象,你不能直接将 values[1] 指向 values 引用的对象本身,而是需要吧 [0, 1, 2] 这个对象「复制」一遍,得到一个新对象,再将 values[1] 指向这个复制后的对象。Python 里面复制对象的操作因对象类型而异,复制列表 values 的操作是

values[:] #生成对象的拷贝或者是复制序列,不再是引用和共享变量,但此法只能顶层复制

所以你需要执行

values[1] = values[:]

Python 做的事情是,先 dereference 得到 values 所指向的对象 [0, 1, 2],然后执行 [0, 1, 2][:] 复制操作得到一个新的对象,内容也是 [0, 1, 2],然后将 values 所指向的列表对象的第二个元素指向这个复制二来的列表对象,最终 values 指向的对象是 [0, [0, 1, 2], 2]。过程如图所示:

往更深处说,values[:] 复制操作是所谓的「浅复制」(shallow copy),当列表对象有嵌套的时候也会产生出乎意料的错误,比如

 
  1. a = [0, [1, 2], 3]

  2. b = a[:]

  3. a[0] = 8

  4. a[1][1] = 9

问:此时 a 和 b 分别是多少?

正确答案是 a 为 [8, [1, 9], 3],b 为 [0, [1, 9], 3]。需要注意的是:b 的第二个元素也被改变了。

正确的复制嵌套元素的方法是进行「深复制」(deep copy),方法是

 
  1. import copy

  2. a = [0, [1, 2], 3]

  3. b = copy.deepcopy(a)

  4. a[0] = 8

  5. a[1][1] = 9

Python变量作用域

可变对象 & 不可变对象

在Python中,对象分为两种:可变对象和不可变对象,不可变对象包括int,float,long,str,tuple等,可变对象包括list,set,dict等。需要注意的是:这里说的不可变指的是值的不可变。对于不可变类型的变量,如果要更改变量,则会创建一个新值,把变量绑定到新值上,而旧值如果没有被引用就等待垃圾回收。另外,不可变的类型可以计算hash值,作为字典的key。可变类型数据对对象操作的时候,不需要再在其他地方申请内存,只需要在此对象后面连续申请(+/-)即可,也就是它的内存地址会保持不变,但区域会变长或者变短。

 
  1. >>> a = 'a1'

  2. >>> id(a)

  3. 140364890935624

  4. >>> a = 'b1'

  5. >>> id(a)

  6. 140364890935680

可以看出重新赋值之后,变量a的内存地址已经变了。 'a1'是str类型,不可变,所以赋值操作知识重新创建了str类型的 'a2'对象,然后将变量a指向了它。

 
  1. >>> a_list = [1,2,3]

  2. >>> id(a_list)

  3. 140364890927496

  4. >>> a_list.append(4)

  5. >>> id(a_list)

  6. 140364890927496

list重新赋值之后,变量a_list的内存地址并未改变,由于[1, 2, 3]是可变的,append操作只是改变了其value,变量a_list指向没有变。

函数值传递

例子1:

 
  1. >>> def func_int(a):

  2. ... a += 4

  3. ...

  4. >>> t = 0

  5. >>> func_int(t)

  6. >>> print(t)

  7. 0

例子2:

 
  1. >>> def func_list(a_list):

  2. ... a_list[0] = 4

  3. ...

  4. >>> t_list = [1,2,3]

  5. >>> func_list(t_list)

  6. >>> print(t_list)

  7. [4, 2, 3]

第一个例子看起来像是传值,而第二个例子确实传引用。其实,解释这个问题也非常容易,主要是因为可变对象和不可变对象的原因:对于可变对象,对象的操作不会重建对象,而对于不可变对象,每一次操作就重建新的对象。

在函数参数传递的时候,Python其实就是把参数里传入的变量对应的对象的引用依次赋值给对应的函数内部变量。参照上面的例子来说明更容易理解,func_int中的局部变量"a"其实是全部变量"t"所指向对象的另一个引用,由于整数对象是不可变的,所以当func_int对变量"a"进行修改的时候,实际上是将局部变量"a"指向到了整数对象"1"。所以很明显,func_list修改的是一个可变的对象,局部变量"a"和全局变量"t_list"指向的还是同一个对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薄了代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值