关于python可变对象,不可变对象,赋值,引用,浅拷贝,深拷贝的思考

获取对象的地址

  • id() 函数用于获取对象的内存地址。
>>>a = 'runoob'
>>> id(a)
4531887632
>>> b = 1
>>> id(b)
140588731085608
  • 标识、相等性和别名
>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832}
# lewis是charles的别名
>>> lewis = charles 
>>> lewis is charles
True
>>> id(charles), id(lewis) 
(4300473992, 4300473992)
# 向lewis中添加一个元素相等于向charles中添加一个元素
>>> lewis['balance'] = 950 
>>> charles
{'name': 'Charles L. Dodgson', 'balance': 950, 'born': 1832}
>>> alex =  {'name': 'Charles L. Dodgson', 'born': 1832}
# 比较两个对象,结果相等,这是因为dict 类的__eq__ 方法就是这样实现的
>>> alex == charles
True
# 但它们是不同的对象。这是Python 说明标识不同的方式:a is not b
>>> alex is not charles
True

在上述代码中,lewis 和charles 是别名,即两个变量绑定同一个对象。而alex 不是charles 的别名,因为二者绑定的是不同的对象。alex 和charles 绑定的对象具有相同的值(== 比较的就是值),但是它们的标识不同。
对象ID 的真正意义在不同的实现中有所不同。在CPython 中,id() 返回对象的内存地址,
但是在其他Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在
对象的生命周期中绝不会变

  • 在==和is之间选择

== 运算符比较两个对象的值(对象中保存的数据),而is 比较对象的标识。通常,我们关注的是值,而不是标识,因此Python 代码中== 出现的频率比is 高。

然而,在变量和单例值之间比较时,应该使用is。目前,最常使用is 检查变量绑定的值是不是None。下面是推荐的写法:

x is None

否定的正确写法是:

x is not None

is 运算符比== 速度快,因为它不能重载,所以Python 不用寻找并调用特殊方法,而是直接比较两个整数ID。而a == b 是语法糖,等同于a.__eq__(b)

可变对象&不可变对象

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

不可变对象示例

>>> a = 1
>>> id(a)
7068776
>>> b = a
>>> id(b)
7068776
>>> a += 1
# 重新赋值之后,变量a的内存地址已经变了
>>> id(a)
7068752
>>> a
2
>>> b
1
>>> id(b)
7068776
# 执行a=1后开辟了一块内存存放1,然后b也指向1,执行a+=1后,因为整型不可变,所以又开辟了一块内存存放2,现在a指向2,b还指向1。

可变对象示例

>>> a = [1,1]
>>> id(a)
140687424863480
>>> b = a
>>> id(b)
140687424863480
>>> a.append(3)
>>> b
[1, 1, 3]
>>> a
[1, 1, 3]
# 执行a = [1, 1]后,a指向这个列表,执行b = a后,b也指向这个列表,两个变量指向同一块内存。执行a.append(3)之后,因为列表是可变对象,append操作知识改变了其值,内存地址并没有变.

python中一切都是对象,参数传递都是对象的引用,具体的,如果函数收到的是可变对象(如列表字典),则可以修改对象的原始值;如果是不可变对象(整型浮点型字符元组),就不能修改原始对象

函数传递参数

python的参数传递分为两种情况:

  • 对于不可变对象作为函数参数,相当于C系语言的值传递
  • 对于可变对象作为函数参数,相当于C系语言的引用传递

来看几个例子:

参数为不可变对象:

def add(num):
    num = num + 10
d = 2
add(d)
print(d)
# 输出结果为2, 因为整数是不可变对象,执行num=num+10后,会产生新的对象,重新开辟一块内存存放12,num指向12, 而d没有变化

参数为可变对象

def add(nums):
    nums.append(3)
    
d = [1,2]
add(d)
print(d)
# 输出结果为[1,2,3]。执行add方法时,nums指向[1,2]。因为列表是可变对象,直接在nums进行操作不会产生新的对象,所以返回[1,2,3]

赋值,浅拷贝,深拷贝的区别

  • 直接赋值:其实就是对象的引用(别名)
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

b=a, 赋值引用,a和b都指向同一个对象

这里写图片描述

b = a.copy(): 浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)

这里写图片描述

b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

这里写图片描述
实例分析

#!/usr/bin/python

import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
 
b = a                       #赋值,传对象的引用
c = copy.copy(a)            #对象拷贝,浅拷贝
d = copy.deepcopy(a)        #对象拷贝,深拷贝
 
a.append(5)                 #修改对象a
a[4].append('c')            #修改对象a中的['a', 'b']数组对象
 
print( 'a = ', a )
print( 'b = ', b )
print( 'c = ', c )
print( 'd = ', d )

'''输出结果
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
d =  [1, 2, 3, 4, ['a', 'b']]
'''

总结如下:

  1. 普通的赋值得到的其实仅仅是共享引用
  2. 浅拷贝(字典的copy方法,list分片)可以进行顶层对象拷贝
  3. 深拷贝(copy.deepcopy)可以彻底实现自顶向下的完全拷贝
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值