Python札记3:可变对象和不可变对象

Python中有可变对象和不可变对象之分。可变对象创建后可改变但地址不会改变,即变量指向的还是原来的变量;不可变对象创建之后便不能改变,如果改变则会指向一个新的对象。

Python中dict、list是可变对象,str、int、tuple、float是不可变对象。

本文只介绍list和str,其他的同理。

字符串

来看一个字符串的例子:

>>> a = "hello"
>>> a[0] = 'a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

上面提示,字符串类型是不支持元素赋值的,也就是说字符串是不可变对象。而当我们对字符串拼接的时候,Python会创建一个新的字符串对象:

a = "hello"
print(id(a)) # 2240543256736

a = a + " world"
print(id(a)) # 2240542770544

执行a = a + " world"时,先计算等号右边的表达式,生成一个新的对象赋值到变量a,因此a指向的对象发生了改变,id(a)的值也与原先不同。

列表

再来看一个列表的例子:

a = [1, 2, 3]
print(id(a)) # 2240541319816

a[0] = 5
print(id(a)) # 2240541319816

a.append(6)
print(id(a)) # 2240541319816

b = a
print(id(b)) # 2240541319816

c = b[:]
print(id(c)) # 2240541319880

c[3]=0
print(a) # [5, 2, 3, 6]
print(b) # [5, 2, 3, 6]
print(c) # [5, 2, 3, 0]

我们可以看到,当更改列表元素的值、追加元素等,列表始终指向同一个对象。我们把a赋给b以后,b也指向了同一个对象。而当把b的切片赋给c时,改变c之后,a和b未受影响,说明c指向了不同的对象。

切片操作是浅拷贝。而普通的赋值只是复制对象的索引(对象标识符、内存地址)给等号左边的变量。

函数默认值中的对象

下面来看个有趣的例子:

class Group(object):
  def __init__(self, group_id, members=[]):
    self.group_id = group_id
    self.members = members
  
  def add_member(self, member):
    self.members.append(member)

group1 = Group(1)
group1.add_member('Zhang')
group1.add_member('Li')

print(id(group1))       # 139975434248880
print(group1.members)   # ['Zhang', 'Li']

group2 = Group(2)
group2.add_member('Wang')
group2.add_member('Chen')

print(id(group2))       # 139975434248992
print(group2.members)   # ['Zhang', 'Li', 'Wang', 'Chen']

我们可以看到,虽然group1和group2是不同的对象,但是group2中的members列表,group1中的members列表也变了。难道他们是同一个列表?我们来验证一下:

print(id(group1.members)) # 139662719359368
print(id(group2.members)) # 139662719359368

果然是同一个列表,原因是__init__函数的第二个参数是默认参数,默认参数的默认值在函数创建的时候就生成了,每次调用都是用了这个对象的缓存。

所以,group1.membersgroup2.members指向了同一个对象,对group2.members的修改也会影响group1.members

那么如何解决这个问题呢?方法很简单,我们将默认值设为None即可:

class Group(object):
  def __init__(self, group_id, members=None):
    self.group_id = group_id
    if members is None:
        self.members = []
  
  def add_member(self, member):
    self.members.append(member)

group1 = Group(1)
group1.add_member('Zhang')
group1.add_member('Li')

print(id(group1))       # 139879060173432
print(group1.members)   # ['Zhang', 'Li']

group2 = Group(2)
group2.add_member('Wang')
group2.add_member('Chen')

print(id(group2))       # 139879060173544
print(group2.members)   # ['Wang', 'Chen']

print(id(group1.members)) # 140296455689224
print(id(group2.members)) # 140296462760328

这样对于不同的group对象,它们的members是在函数被调用时才被创建,不同的group对象中的members不再引用同一个对象,所以不会再出现更新一个group对象的members也会更新另外一个group对象的members了。


我的知乎:奔三的鑫鑫
欢迎关注微信公众号:小鑫的代码日常
欢迎加入Python学习交流群:532232743,这里有各路高手等着你~

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页