返回介绍

建议29:区别对待可变对象和不可变对象

发布于 2024-01-30 22:19:09 字数 3426 浏览 0 评论 0 收藏 0

Python中一切皆对象,每一个对象都有一个唯一的标示符(id())、类型(type())以及值。对象根据其值能否修改分为可变对象和不可变对象,其中数字、字符串、元组属于不可变对象,字典以及列表、字节数组属于可变对象。而“菜鸟”常常会试图修改字符串中某个字符。看下面这个例子:

teststr = "I am a pytlon string"
teststr[11]='h'
print teststr

字符串为不可变对象,任何对字符串中某个字符的修改都会抛出异常。修改字符串中某个字符可以采用如下方式:

>>> teststr = "I am a pytlon string"
>>> import array
>>> a = array.array('c',teststr)
>>> a[10]='h'
>>> a
array('c', 'I am a Python string')
>>> a.tostring()
'I am a Python string'

再来看下面一段程序:

class Student(object):
  def __init__(self,name,course=[]):
    self.name=name
    self.course=course
  def addcourse(self,coursename):
    self.course.append(coursename)
  def printcourse(self):
    for item in self.course:
      print item
stuA=Student("Wang yi")
stuA.addcourse("English")
stuA.addcourse("Math")
print stuA.name +"'s course:"
stuA.printcourse()
print "-----------------------"
stuB=Student("Li san")
stuB.addcourse("Chinese")
stuB.addcourse("Physics")
print stuB.name +"'s course:"
stuB.printcourse()

上述代码清单描述的是两个不同的学生选择不同课程的场景。这个程序有什么问题吗?通过运行程序得到如下输出结果:

Wang yi's course:
English
Math
-----------------------
Li san's course:
English
Math
Chinese
Physics

你会诧异地发现Li san同学所选的多了English和Math两门课程。这是怎么回事呢?通过查看id(stuA.course)和id(stuB.course),我们发现这两个值是一样的,也就是说在内存中指的是同一块地址,但stuA和stuB本身却是两个不同的对象。在实例化这两个对象的时候,这两个对象被分配了不同的内存空间,并且调用init()函数进行初始化。但由于init()函数的第二个参数是个默认参数,默认参数在函数被调用的时候仅仅被评估一次,以后都会使用第一次评估的结果,因此实际上对象空间里面course所指向的是list的地址,每次操作的实际上是list所指向的具体列表。这是我们在将可变对象作为函数默认参数的时候要特别警惕的问题,对可变对象的更改会直接影响原对象。要解决上述例子中的问题,最好的方法是传入None作为默认参数,在创建对象的时候动态生成列表。具体代码如下:

  def __init__(self,name,course=None):
    self.name=name
    if course is None:course=[]
    self.course=course

对于可变对象,还有一个问题是需要注意的。我们通过以下例子来说明:

>>> list1=['a','b','c']
>>> list2 = list1
>>> list1.append('d')
>>> list1
['a', 'b', 'c', 'd']
>>> list2        
①list2
也会发生变化
['a', 'b', 'c', 'd']
>>> list3 = list1[:]   
②切片操作相当于浅拷贝
>>> list3.remove('a')
>>> list3
['b', 'c', 'd']
>>> list1
['a', 'b', 'c', 'd']
>>> list2
['a', 'b', 'c', 'd']
>>> id(list3)      
③重新指向一块内存
14075304
>>> id(list1)
13418864
>>> id(list2)
13418864

上面的例子中对list1的切片操作实际会重新生成一个对象,因为切片操作相当于浅拷贝,因此对list3的操作并不会改变list1和list2本身。我们再来看以下不可变对象的简单例子:

a=1
a+=2
print a

我们会发现此时a的值变为3,这是理所当然的,可是仔细一想a是属于数值类型,是不可变对象,怎么会发生改变呢?实际上Python中变量a存放的是数值1在内存中的地址,数值1本身才是不可变对象。在上面的过程中所改变的是a所指向的对象的地址,数值1并没有发生改变,当执行a+=2的时候重新分配了一块内存地址存放结果,并将a的引用改为该内存地址,而对象1所在的内存空间会最终被垃圾回收器回收。上述分析的图示如图3-5所示。

图3-5 a在内存中的变化示意图

通过id()函数分析也可以证实上述变化过程。

>>> a=1
>>> id(a)
12184696
>>> id(1)        
①id(a)
和id(1)
的值并不相等
12184696
>>> a+=2
>>> a
3
>>> id(a)        
②id(a)
的值发生改变
12184672
>>> id(3)        
③id(a)
和id(3)
的值相同
12184672

id()函数分析也可以证实上述变化过程,对于不可变对象来说,当我们对其进行相关操作的时候,Python实际上仍然保持原来的值而是重新创建一个新的对象,所以字符串对象不允许以索引的方式进行赋值,当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。

>>> str1 = "hello world"
>>> str2 = str1
>>> str1 = str1[:-5]
>>> str1
'hello '
>>> str2
'hello world'
>>>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文