python学习笔记(一)——可变对象与不可变对象

笔者在初学python时,对可变对象与不可变对象这块仅仅只是走马观花地扫了一眼,并未深究,直到最近给我上了印象深刻的一课,笔者才去查阅资料和亲自试验,大概把这块弄明白了。

问题的发现:

笔者最近在一次比赛中帮队友处理json文件数据时,对字典这一可变对象的错误使用,导致结果出现了大问题,所幸队友发现了数据存在问题,及时止损,下面先来看看这块错误代码:

annotations1 = []
image = {}
f = open('D:\\test.json',encoding='UTF-8')
content1 = json.load(f)
for i in content1:
    image['name'] = 'stage1/train/' + i['name']
    image['id'] = i['id']
    image['num'] = i['num']
    image['ignore_region'] = i['ignore_region']
    image['type'] = i['type']
    image['annotation'] = []
    for point in i['annotation']:
        position = {}
        position['x'] = point[0]
        position['y'] = point[1]
        image['annotation'].append(position)
    annotations1.append(image)

这段代码执行完毕后,annotations1中的内容是3000多个一模一样的字典,且全部为原json文件的最后一个内容(即最后一次for循环的内容),这让笔者很是疑惑:
1.为什么3000多个内容是一摸一样的?
2.为什么全是最后一次for循环的内容?

寻求答案:

上述两个疑惑让笔者立马联想到了可变对象与不可变对象,于是查阅了资料和亲自试验了一下:

  • 可变对象:可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,没有开辟新的出地址,通俗点说就是原地改变
    先来看一段代码:
#a被修改,但是a的地址没有发生变化
a = {'x':1,'y':2}
print('id of a is:',id(a))
a['x'] = 7
print('id of a is:',id(a))

结果如下:
在这里插入图片描述
由于a是一个字典,字典属于可变对象,当a的值被修改,实际上是直接在a所指向的内存上直接在修改数据,并不是开辟新的内存,故修改前后,a的地址并未发生变化,始终是同一块内存。

再看:

a = {'x':1,'y':2}
print('id of a is:',id(a))
b = a
print('id of b is:',id(b))

在这里插入图片描述
这里b = a其实是令b指向a指向的那块内存地址,a和b指向的是同一块地方,所以a,b的地址是一样的

如果对a进行修改,则b也会随之改变,反之亦然,因为此时a,b其实是同一个对象,指向同一块内存,改变a和b其中任意一个,都是在内存上原地直接修改数据(可变对象),若这块内存的数据被改变,则a,b的数据都会改变,如下图:

a = {'x':1,'y':2}
b = a
a['x'] = 5
print("a改变之后,b的值:",b)
b['x'] =10
print("b改变之后,a的值:",a)

在这里插入图片描述
到这里,我想读者应该能够解开前面的那两个疑惑了:

annotations1 = []
image = {}
f = open('D:\\test.json',encoding='UTF-8')
content1 = json.load(f)
for i in content1:
    image['name'] = 'stage1/train/' + i['name']
    image['id'] = i['id']
    image['num'] = i['num']
    image['ignore_region'] = i['ignore_region']
    image['type'] = i['type']
    image['annotation'] = []
    for point in i['annotation']:
        position = {}
        position['x'] = point[0]
        position['y'] = point[1]
        image['annotation'].append(position)
    annotations1.append(image)

由于这里的image是字典,可变对象,且被放在了for 循环的外面,for循环内的每一次对image的修改,其实是在修改同一个image对象,被append到annotations1中的也是同一个对象,也就是说annotations1中装的3000多个字典是同一个对象,他们均指向同一块内存,这也就解释了为什么最终结果是3000个一模一样的数据,每一次for循环对image的更新都会影响之前已经被append到annotations1中的image,因为他们指向同一块内存,所以,最终结果是最后一次for循环更新的结果。
那么,解决办法是什么呢?
很简单,就是把image放入for循环内,使其每次产生的不是同一块内存的对象:就像下面的试验

m = {}
m['x'] = 1
print('id of m',id(m))

m = {}
m['x'] = 4
print('id of m',id(m))

在这里插入图片描述
关于可变对象的部分,还涉及到深复制,浅复制等知识,在此不做重点讲解,读者可自行查阅。

好了,再来看看不可变对象。

  • 不可变对象:数值类型(int和float)、字符串str、元组tuple都是不可变类型
    该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
    这里首先理解一下对象的含义。笔者所理解的对象:是实际存在的数据,如:
#所谓对象,是指具体存在的数据,如下图的 6,'abc','ABC' 至于变量a,b,x,y 他们是对象的变量名或者说对象的引用
a = 6
print('id of a',id(a))
b = 6
print('id of b',id(b))
x = 'abc'
print('id of x',id(x))
y = 'ABC'
print('id of y',id(y))
print('id of ABC',id('ABC'))

上述代码中的 6,‘abc’,'ABC’是对象, 至于变量a,b,x,y 他们是对象的变量名或者说对象的引用

对不可变对象来说,不存在前述的可变对象那样的情况:修改一个的值,会影响另一个值:

#不可变类型对象(数字,字符串,元组,不可变集合):不支持原处修改,不可在原来的内存上修改,如果修改对象的值,其实是开辟新的内存
#其中小整数存在特例,详情请自行查阅资料
a = 6
print('id of a',id(a))
b = a
print('id of b',id(b))
a = a + 1

print('a修改之后,id of a,a的值:',id(a),a)
print('a修改之后,id of b,b的值:',id(b),b)

在这里插入图片描述
可以看出:a被修改之后,a的地址已经发生变化,是因为a是不可变对象,不能在原来内存上直接修改数据,而是开辟了一块新的内存,存放一个新的对象,而b并未受a的影响,仍旧指向原来的内存,且值也未改变
再来看一个例子:

x = 'abc'
print('id of x',id(x))
x = 'ABC'
print('id of x',id(x))

y = x
print("y:",y,"id of y:",id(y))
x += "ccc"
print("x:",x,"id of x:",id(x))
print("y:",y,"id of y:",id(y))

在这里插入图片描述
到此为止,笔者大概对可变对象和不可变对象做了一个简单的介绍,其实还有更多值得深挖的内容,比如:不可变对象类型 “元组” 里面装了一些可变对象类型的元素时,对元组进行修改, 这时候会发生什么有趣的现象呢?,可变集合与不可变集合…类似的还有很多,这些就需要读者自行去试验了,
纸上得来终觉浅,绝知此事要躬行
最后,以两个简单的模拟,体现一下最开头的那个问题的两种不同写法会带来什么影响:

#一个简单对比
l=[]
n = {}
for i in range(5):
    n['y'] = i
    l.append(n)#这里面的元素皆为同一地址,同一对象,也就是说是同一个东西
for item in l:
    print(item,"id:",id(item))

print("对比:")
#对比,将n = {}放进for循环内
l=[]
for i in range(5):
    n = {}
    n = {'y':i}
    l.append(n)#这里面的元素不是同一个地址
for item in l:
    print(item,"id:",id(item))

在这里插入图片描述
(小菜鸡第一次写文章,欢迎各路大佬指正错误)

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值