Python中的可变对象和不可变对象

Python中有可变对象和不可变对象之分。可变对象建立后可改变但地址不会改变,即变量指向的仍是原来的变量;不可变对象建立以后便不能改变,若是改变则会指向一个新的对象。python

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

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

字符串

来看一个字符串的例子:微信

>>> 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会建立一个新的字符串对象:app

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指向了不一样的对象。ui

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

函数默认值中的对象

下面来看个有趣的例子:code

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

微信公众号:小鑫的代码平常