>>> from env_helper import info; info()
页面更新时间: 2023-12-27 09:26:24
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.11. 区别对待可变对象和不可变对象

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

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

TypeError                                 Traceback (most recent call last)

Cell In[24], line 1
----> 1 teststr[11]='h'


TypeError: 'str' object does not support item assignment

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

>>> import array
>>> a = array.array('u',teststr)
>>> a[10]='h'
>>> print(a)
array('u', 'I am a python string')

使用 tounicode() 转换为字符串。旧版本中有 tostring() 函数,但在新版本中已经被移除。

>>> a.tounicode()
'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("MEnglishw")
>>> stuA.addcourse("Math")
>>> print(stuA.name+"'s cource:")
>>> stuA.printcourse()
>>> print( "-----------------")
>>> stuB=Student("MLi sann")
>>> stuB.addcourse ("*fChineset,")
>>> stuB.addcourse ("rPhysics")
>>> print (stuB.name +"s course:" )
>>> stuB.printcourse()
Wang yi's cource:
MEnglishw
Math
-----------------
MLi sanns course:
MEnglishw
Math
*fChineset,
rPhysics

你会诧异地发现Li san同学所选的多了 English和Math两N课程。这是怎么回事呢? 通过査看id(stuA,course>和id<stuB,couree),我们发现这两个值是一样的,也就是说在内存中 指的是同一块地址.但stuA和stuB本身却是两个不同的对象e在实例化这两个对象的时候, 这两个对象被分配了不同的内存空间,并且调用inU〇函数进行初始化。但由于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
['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)
139887224486656
>>>
>>> id(list1)
139887225655232
>>> id(list2)
139887225655232

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

>>> a=1
>>> a+=2
>>> print (a)
3

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

_images/29-1.png

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

>>> a=1
>>> id(a)
10861192
>>> id(1)
10861192
>>> a+=2
>>> a
3
>>> id(a)
10861256
>>> id(3)
10861256

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

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