Python回顾与整理4:序列2―列表与元组

1.列表


        与字符串不同的是,列表不仅可以包含Python的标准类型,还可以包含不同类型的对象,包括用户自定义的对象。下面是一些列表的最基本的操作:

  • 创建列表数据类型:由方括号([ ])定义,当然也可以用工厂方法list(iter)创建

  • 访问列表的值:通过切片操作符([ ])和索引值或索引值范围访问

  • 更新列表:可以在等号左边指定一个索引或者索引范围的方式来更新一个或几个元素,也可以用append()方法追加新元素到列表中

  • 删除列表元素或列表本身:使用del L[index]的方法,或者使用remove(value)方法

        因为有了前面字符串的知识之后,这些其实都比较简单,这里就不给出例子了。



2.操作符


(1)标准类型操作符

        在《Python回顾与整理2:Python对象》中已经有介绍过标准类型操作符,它们对于列表类型肯定也是适用的(列表就是标准类型或者说是内建类型),举例如下:

>>> list1 = ['abc', 123]
>>> list2 = ['xyz', 789]
>>> list3 = ['abc', 123]
>>> list1 < list2
True
>>> list2 < list3
False

        列表大小的比较规则是这样的:两个列表的元素分别比较,直到有一方的元素胜出。当然这里也可以使用cmp()来进行比较。


(2)序列类型操作符

  • 切片操作:跟普通序列的方法一样,主要是列表中还可以创建类似于“多维数组”的数据结构,即list[1][1],只不过在这样使用之前,应该通过`L[1] = [ ]`这样的形式来在列表中创建一个空列表

  • 成员关系操作(in, not in):判断元素是否在列表中

  • 连接操作符(+):把两个或多个列表连接起来,不过与字符串的连接操作一样,同样会创建每一个列表对象,所以可以使用list.extend()方法来代替

        有一点是需要注意的,列表可以与字符串做连接操作,只是会先把字符串转换为列表,然后再与原列表进行连接操作,即可以认为列表的“优先级”比字符串高(但这种特性不可以用在元组中),如下:

>>> aList = ['xpleaf']
>>> aList += 'clyyh'
>>> aList
['xpleaf', 'c', 'l', 'y', 'y', 'h']
  • 重复操作符(*):重复生成列表中的元素


(3)列表类型操作符和列表解析

        在Python中,没有专门用于列表类型的操作符,列表可以使用大部分的对象和序列类型的操作符。不过在列表中可以通过使用“列表解析”来快速创建有一定逻辑的列表:

>>> [ i * 2 for i in [8, -2, 5]]
[16, -4, 10]
>>> [ i for i in range(8) if i %2 == 0 ]
[0, 2, 4, 6]



3.内建函数


        需要注意的是,这里指的内建函数是指Python本身自带的函数,这与`列表类型的内建函数`是有区别的,`列表类型的内建函数`是指列表list类里定义的方法,需要通过`list.method()`的方式进行使用,前面在提及字符串时,含义也是如此的。


(1)标准类型函数

        cmp()对于列表大小的比较相对会有些复杂,需要依次遵循下面的规则:

  • 对两个列表的元素进行比较

  • 如果比较的元素是同类型的,则比较其值,返回结果

  • 如果两个元素不是同一种类型,则检查它们是否是数字

    a.如果是数字,执行必要的数字强制类型转换,然后比较

    b.如果有一方的元素是数字,则另一方的元素“大”(数字是“最小的”)

    c.否则,通过类型名字的字母顺序进行比较

  • 如果有一个列表首先到达末尾,则另一个长一点的列表“大”

  • 如果用尽了两个列表的元素而且所有元素都是相等的,则返回0

        当然,在实际当中可能并不需要对列表进行大小的比较,但还是应该去了解一下这个算法的。


(2)序列类型函数

  • len():返回列表中元素的个数

>>> L = [123, 456]
>>> len(L)
2
  • max()min():如果列表元素是同类型的,就会非常有用,当然,列表元素中混合对象的结构越复杂,则返回的结构准确性就越差

>>> min(L)
123
>>> max(L)
456
  • sorted()reversed():排序和翻转

>>> list
['abc', 123, 'efg', '456']
>>> for value in reversed(list):
...   print value,
... 
456 efg 123 abc
>>> sorted(L)
[123, '456', 'abc', 'efg']

        需要注意的是,排序是根据ASCII表中的顺序来进行排序的。

  • enumerate()zip()

>>> name = ['xpleaf', 'clyyh', 'yonghaoye']
>>> enumerate(name)
<enumerate object at 0x7f5f5f41b5f0>
>>> list(enumerate(name))
[(0, 'xpleaf'), (1, 'clyyh'), (2, 'yonghaoye')]
>>> for value in enumerate(name):
...   print value,
... 
(0, 'xpleaf') (1, 'clyyh') (2, 'yonghaoye')
>>> for index, value in enumerate(name):
...   print index, value
... 
0 xpleaf
1 clyyh
2 yonghaoye
>>>
>>> fn = ['ian', 'stuart', 'david']
>>> ln = ['bairnson', 'elliott', 'paton']
>>> zip(fn, ln)
[('ian', 'bairnson'), ('stuart', 'elliott'), ('david', 'paton')]
  • sum()

>>> a = [6, 4, 5]
>>> sum(a)
15
>>> sum(a, 5)
20
  • list()tuple():接受可迭代对象,并通过浅拷贝数据来创建一个新的列表或元组

        list()和tuple()能在列表和元组之间进行很好的转换,这样就可以把一个元组转换成列表,进而修改列表中的元素,举例如下:

>>> aList = ['tao', 93, 99, 'time']
>>> aTuple = tuple(aList)
>>> aList, aTuple
(['tao', 93, 99, 'time'], ('tao', 93, 99, 'time'))
>>> anotherList = list(aTuple)
>>> aList == anotherList
True
>>> aList is anotherList
False
>>> [ id(x) for x in aList, aTuple, anotherList ]
[140047597735448, 140047596625512, 140047596743208]
>>> [ id(x) for x in aList[0], aTuple[0], anotherList[0]  ]
[140047628190584, 140047628190584, 140047628190584]

        通过最后一个输出其实也可以明白浅拷贝的含义,只是深拷贝了原来序列的索引,而对于序列中的元素,只是通过这些索引再一次进行引用。


(3)列表类型函数

        如果不考虑range()函数,则Python中没有特定用于列表的函数。



4.列表类型的内建函数(方法)


        这里所谓的列表类型的内建函数,指的是可以通过`list.method()`方式执行的方法。

        当使用help(list)时,会有如下结果:

class list(object)
 |  list() -> new empty list
 |  list(iterable) -> new list initialized from iterable's items
 |  
 |  Methods defined here:
 |  
 |  __add__(...)
 |      x.__add__(y) <==> x+y
 |  
 |  __contains__(...)
 |      x.__contains__(y) <==> y in x
 |  
 |  __delitem__(...)
 |      x.__delitem__(y) <==> del x[y]
 |  
 |  __delslice__(...)
 |      x.__delslice__(i, j) <==> del x[i:j]
 |      
 |      Use of negative indices is not supported.
 ……

        所以应该更加确认,这里所说的列表类型的内建函数,其中就是list类里定义的方法。

        可以查看列表类型所支持的方法:

>>> dir(list)    #或dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

        下面是列表类型支持的方法:

列表类型内建函数(方法)
列表函数(方法) 作用
list.append(obj) 向列表中添加一个对象obj
list.count(obj) 返回一个对象obj在列表中出现的次数
list.extend(seq) 把序列seq的内容添加到列表中
list.index(obj, i=0, j=len(list)) 返回list[k]==obj的k值,并且k的范围在i<=k<j;否则引发ValueError异常
list.insert(index, obj) 在索引量为index的位置插入对象obj
list.pop(index=-1) 删除并返回指定位置的对象,默认是最后一个对象
list.remove(obj) 从列表中删除对象obj
list.revese() 原地翻转列表
list.sort(func=None, key=None, reverse=False) 以指定的方式排序列表中的成员,如果func和key参数指定,则按照指定的方式比较各个元素,如果reverse标志被置为True,则列表以反序排列

        下面给出部分操作的例子:

  • insert()append()

>>> music_media = [45]
>>> music_media
[45]
>>> music_media.insert(0, 'compact disc')
>>> music_media
['compact disc', 45]
>>> music_media.append('long playing record')
>>> music_media
['compact disc', 45, 'long playing record']
>>> music_media.insert(2, '8-track tape')
>>> music_media
['compact disc', 45, '8-track tape', 'long playing record']
  • index()

>>> music_media
['compact disc', 45, '8-track tape', 'long playing record']
>>> music_media.index(45)
1
>>> music_media.index('8-track tape')
2
>>> 
>>> music_media.index('xpleaf')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 'xpleaf' is not in list

        可以看到如果元素不在列表中会引发ValueError异常,虽然可以通过异常处理进行捕获,但更好的方法应该是先判断元素是否在列表中,然后再调用index()方法:

>>> 45 in music_media
True
>>> 'xpleaf' in music_media
False
>>> for eachMediaType in (45 ,'xpleaf'):
...   if eachMediaType in music_media:
...     print music_media.index(eachMediaType)
... 
1
  • sort()reverse()

>>> music_media
['compact disc', 45, '8-track tape', 'long playing record']
>>> music_media.sort()
>>> music_media
[45, '8-track tape', 'compact disc', 'long playing record']
>>> 
>>> music_media.reverse()
>>> music_media
['long playing record', 'compact disc', '8-track tape', 45]

        另外,需要注意的是:那些可以改变对象值的可变对象的方法是没有返回值的

      当执行music_media.sort()时,没有返回值:

>>> music_media.sort()
>>>

      在使用可变对象的方法如sort()、extend()和reverse()的时候要注意,这些操作会在列表中原地执行操作,也就是说现有的列表内容会被改变,但是没有返回值!与之相反,字符串方法是有返回值的:

>>> 'leanna, silly girl!'.upper()
'LEANNA, SILLY GIRL!'

    那是因为,字符串是不可变对象,而不可变对象的方法是不能改变它们的值的,所以它们必须返回一个新的对象。如果你确实需要返回一个对象,可以使用reversed()和sorted()内建函数,它们像列表的方法一样工作,不同的是它们可以用做表达式,因为它们生成并返回一个新的对象,即原来的那个对象还存在,并且没有发生任何改变。

        这里需要提的另一点是,sort()方法,它默认的排序算法是归并排序的衍生算法,时间复杂度是O(lg(n!)),如果学过C语言数据结构,相信对此并不会很陌生。

  • extend()

        extend()方法接受一个列表的内容,然后把它的所有元追加到另一个列表中去:

>>> music_media
['long playing record', 'compact disc', '8-track tape', 45]
>>> new_media = ['24/96 digital audio disc', 'DVD Audio disc', 'Super Audio CD'] 
>>> music_media.extend(new_media)
>>> music_media
['long playing record', 'compact disc', '8-track tape', 45, '24/96 digital audio disc', 'DVD Audio disc', 'Super Audio CD']

        extend()方法不仅可以接受序列作为参数,它也可以接受任何可迭代对象,举例如下:

xpleaf@leaf:~$ echo 'Welcome to GDUT' > welcome.txt
xpleaf@leaf:~$ python
……
>>> welcome = []
>>> welcome.append('Welcome Message')
>>> f = open('welcome.txt', 'r')
>>> welcome.extend(f)
>>> f.close()
>>> welcome
['Welcome Message', 'Welcome to GDUT\n']



5.列表的特殊特性


        列表有容器和可变的特性,因此可以用列表来构建其他数据结构,比如堆栈和队列。


(1)堆栈

        堆栈的核心是:先进后出(FILO),后进先出(LIFO)。关于更多的理论知识这里不提及,可以看下面用列表实现的例子:

#!/usr/bin/env python

stack = []


def pushit():
    stack.append(raw_input('Enter New string: ').strip())


def popit():
    if len(stack) == 0:
        print 'Cannot pop from an empty stack!'
    else:
        print 'Removed [', `stack.pop()`, ']'


def viewstack():
    print stack


CMDs = {'u': pushit, 'o': popit, 'v': viewstack}


def showmenu():
    pr='''
p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: '''

    while True:
        while True:
            try:
                choice = raw_input(pr).strip()[0].lower()
            except (EOFError, KeyboardInterrupt, IndexError):
                choice = 'q'

            print '\nYou picked: [%s]' % choice
            if choice not in 'uovq':
                print 'Invalid option, try again'
            else:
                break

        if choice == 'q':
            break
        CMDs[choice]()

if __name__ == '__main__':
    showmenu()

        执行如下:

xpleaf@leaf:~$ python stack.py 

p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: u

You picked: [u]
Enter New string: clyyh

p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: u

You picked: [u]
Enter New string: clyyh

p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: v

You picked: [v]
['clyyh', 'clyyh']

p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: o

You picked: [o]
Removed [ 'clyyh' ]

p(U)sh
p(O)p
(V)iew
(Q)uit

Enter choice: q

You picked: [q]

        可以看到只用了append()和pop()方法就实现了进栈出栈的操作,但如果同样的操作用C语言来实现的话就会复杂很多。


(2)队列

        队列的核心是:先进先出(FIFO)

        直接看下面的一个例子:

#!/usr/bin/env python

queue = []


def enQ():
    queue.append(raw_input('Enter New string: ').strip())


def deQ():
    if len(queue) == 0:
        print 'Cannot pop from an empty stack!'
    else:
        print 'Removed [', `queue.pop(0)`, ']'


def viewQ():
    print queue


CMDs = {'e': enQ, 'd': deQ, 'v': viewQ}


def showmenu():
    pr='''
(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: '''

    while True:
        while True:
            try:
                choice = raw_input(pr).strip()[0].lower()
            except (EOFError, KeyboardInterrupt, IndexError):
                choice = 'q'

            print '\nYou picked: [%s]' % choice
            if choice not in 'devq':
                print 'Invalid option, try again'
            else:
                break

        if choice == 'q':
            break
        CMDs[choice]()

if __name__ == '__main__':
    showmenu()

        执行如下:

xpleaf@leaf:~/PycharmProjects/Python_book/6$ python queue.py 

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: e

You picked: [e]
Enter New string: xpleaf

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: e

You picked: [e]
Enter New string: clyyh

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: v

You picked: [v]
['xpleaf', 'clyyh']

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: d

You picked: [d]
Removed [ 'xpleaf' ]

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: v

You picked: [v]
['clyyh']

(E)nqueue
(D)equeue
(V)iew
(Q)uit

Enter choice: q

You picked: [q]

        依然是只使用了append()和pop()方法,相对来说,在C语言中实现队列也要复杂很多,虽然并不难实现。



6.元组


        元组和列表有着非常大的相似性,但有下面两个区别:

  • 定义:元组使用圆括号定义

  • 功能:元组是不可变类型的数据结构

        基于元组是一种不可变类型,它能做一些列表不能做的事情,比如用做一个字典的key。另外,当处理一组对象时,这个组默认是元组类型。使用元组的好处是,当你需要把你的数据交给一些API处理,而又不想它修改你的数据时,使用元组就可以达到这个目的。

        前面在总结字符串和列表这两种类型时,都是以下面的一个思路进行:

  • 介绍可用于大部分对象的操作符和内建函数

  • 介绍针对序列类型的内建函数

  • 介绍针对特定序列类型的方法

        但因为元组和列表有太多的相似性,所以对于元组的总结思路是:介绍元组和列表在应用于每一组操作符和内建函数上的区别,然后讨论一下元组的不变性和其他的特性。

        下面是元组的基本操作:

  • 创建元组并赋值

        跟列表完全一样,只是只有一个元素的元组需要在圆括号里加一个逗号(,),以防止跟普通的分组操作符混淆:

>>> aTuple = ('xpleaf',)
>>> type(aTuple)
<type 'tuple'>
>>> nTuple = ('xpleaf')
>>> type(nTuple)
<type 'str'>
>>> tuple('xpleaf')
('x', 'p', 'l', 'e', 'a', 'f')
  • 访问元组中的值:跟列表一样,通过切片操作符([ ])和索引值或索引值范围访问

  • 更新元组:因为元组是不可变类型,所以更新元组实际上是重新生成一个新的元组对象,这点和字符串是一样的

  • 删除元组元素以及元组本身:同样的,不能只删除元组中的某一个元素,因为它是不可变对象,当然要删除一个元组,只需要使用del即可,只是一般情况下并不需要这样做



7.元组操作符和内建函数


(1)标准类型操作符、序列类型操作符和内建函数

        元组的对象和序列类型操作符和内建函数跟列表的完全一样,仍然可以对元组进行切片操作、合并操作,以及多次拷贝一个元组,还可以检查一个对象是否属于一个元组,进行元组之间的比较等,列举如下:

  • 创建、重复、连接操作

        关于连接操作需要注意的是,元组是不能直接和字符串做连接操作的,这点与列表不同:

>>> aList = ['xpleaf']
>>> aList += 'clyyh'
>>> aList
['xpleaf', 'c', 'l', 'y', 'y', 'h']
>>> aTuple = ('xpleaf',)
>>> aTuple += 'clyyh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "str") to tuple
  • 成员关系操作、切片操作

  • 内建函数:主要是str()、len()、max()、min()、cmp()、list()

  • 操作符:主要是<、>和==

>>> (3, 4) < (5, 6)    #即可以这样来比较数的大小
True


(2)元组类型操作符和内建函数、内建方法

        像列表一样,元组也没有自己专用的操作符和内建函数,前面提及的列表方法都跟列表对象的可变性有关,比如说排序、替换、添加等,因为元组是不可变的,所以这些操作符对元组来说就是多余的,这些方法并没有被实现。



8.元组的特殊特性


(1)不可变性给元组带来的影响

        虽然在Python中不可变类型有数字、字符串和元组,但不可变性对元组的影响更大,因为数字和字符串可以认为是一种标量类型,而元组则是一种容器,但因为元组的不可变性,我们不能修改其元素对象。


(2)元组也不是那么不可变

        主要是指下面的几个方面:

  • 连接操作:可以使用连接操作,这跟字符串是一样的,但本质上也是没有改变元组

>>> t = ('xpleaf',)
>>> id(t)
140420395249936
>>> t += ('clyyh',)
>>> t
('xpleaf', 'clyyh')
>>> id(t)
140420395089432
  • 修改元组的可变元素对象:这看上去就像可以修改元组的元素

        虽然元组对象本身是不可变的,但这并不意味着元组包含的可变对象也不可变:

>>> aTuple = ([123, 'xpleaf'], 'clyyh')
>>> aTuple
([123, 'xpleaf'], 'clyyh')
>>> id(aTuple)
140420395089576
>>> aTuple[0][1] = 'yonghaoye'
>>> aTuple
([123, 'yonghaoye'], 'clyyh')
>>> id(aTuple)
140420395089576

        虽然本质上元组对象本身并没有改变,但从某种意义上讲,也是“改变”了元组类型变量。


(3)默认集合类型

        主要是下面两个方面:

  • 所有的多对象的、逗号分隔的、没有明确用符号定义的(比如用方括号表示列表和用圆括号表示元组),这些集合默认的类型都是元组

>>> 'xpleaf',
('xpleaf',)
>>> 'xpleaf', 'clyyh'
('xpleaf', 'clyyh')
>>> x, y = 1, 2
>>> x, y
(1, 2)
  • 所有函数返回的多对象(不包括有符号封装的)都是元组类型,注意,有符号封装的多对象集合其实是返回的一个单一的容器对象

>>> def reMutileObj():
...   return 'xpleaf', 'clyyh'
... 
>>> def reOneObj():
...   return ['xpleaf', 'clyyh']
... 
>>> def reMutileObj2():
...   return ('xpleaf', 'clyyh')
... 
>>> reMutileObj()
('xpleaf', 'clyyh')
>>> reOneObj()
['xpleaf', 'clyyh']
>>> reMutileObj2()
('xpleaf', 'clyyh')

        reMutileObj()返回两个对象,默认的作为一个二元组类型;reOneObj()返回一个单一对象,一个包含两个对象的列表;reMutileObj2()返回一个跟reMutileObj()相同的对象,只是这里的元组是显式定义的。

        基于元组的这些特性,建议总是显式地用圆括号表达式表示元组或者创建元组,比如用下面的这种方法定义元组就不是很直观:

>>> 4, 2 < 3, 5
(4, True, 5)


(4)单元素元组

        只需要在圆括号的基础上再加一个逗号(,),或者隐式定义也可以:

>>> ('xpleaf',)
('xpleaf',)
>>> 'xpleaf',
('xpleaf',)


(5)字典的关键字

        不可变对象的值是不可改变的,这就意味着它们通过hash算法得到的值总是一个值,这是作为字典键值的一个必备条件,因此元组变量符合这个条件。



9.相关模块


        主要是和序列类型相关的模块,这里只列出几种:

  • array:一种受限制的可变序列类型,要求所有的元素必须都是相同的类型

  • copy:提供浅拷贝和深拷贝的能力

  • operator:包含函数调用形式的序列操作符,比如operator.concat(m, n)就相当于连接操作(m+n)

  • re:Perl风格的正则表达式(和匹配)



10.拷贝Python对象、浅拷贝和深拷贝


        先看看浅拷贝的概念:

  • 浅拷贝:对一个对象进行浅拷贝其实是新创建了一个类型跟原对象一样,其内容还是原来对象元素的引用,换句话说,这个拷贝的对象本身是新的,但是它的内容不是

        序列类型对象的浅拷贝是默认类型拷贝,有以下几种实现方式:

  • 完全切片操作:下面操作会有

  • 利用工厂函数:比如list()、dict()等

  • 使用copy模块的copy()函数

        其实如果是真正理解了Python对象或者说理解了可变对象和不可变对象,再根据上面的理论知识,浅拷贝和深拷贝基本上算是比较好的掌握了。所以这里不按照书上的思路来进行总结,当然书上的例子作为入门也是非常不错的。下面给出三个例子,如果都可以理解,那么对Python浅拷贝和深拷贝的掌握到这个程度也就可以了。

        第一个例子:列表中的元素都是原子类型,即不可变对象

>>> person = ['age', 20]
>>> xpleaf = person[:]  #浅拷贝
>>> cl = list(person)      #浅拷贝
>>> [id(x) for x in person, xpleaf, cl]   #虽然是浅拷贝,但是其实也是生成了新的对象
[140205544875144, 140205544893688, 140205544996232]
>>> [id(x) for x in person]
[140205545021232, 32419728]
>>> [id(x) for x in xpleaf]
[140205545021232, 32419728]
>>> [id(x) for x in cl]
[140205545021232, 32419728]
#但是可以看到列表中的元素的还是原来对象元素的引用

    上面做了浅拷贝的操作,然后下面修改两个浅拷贝的值:

>>> xpleaf[1] = 22
>>> cl[1] = 21
>>> person, xpleaf, cl
(['age', 20], ['age', 22], ['age', 21])
>>> [id(x) for x in person]
[140205545021232, 32419728]
>>> [id(x) for x in xpleaf]
[140205545021232, 32419680]
>>> [id(x) for x in cl]
[140205545021232, 32419704]

    修改了两个浅拷贝的值,然后发现内容并没有相互影响,而且后来的id值也发生改变了,怎么会这样?不要忘了,列表中的元素都是不可变对象,修改不可变对象的值,其实就相当于是新生成了一个该对象,然后让列表元素重新指向新生成的不可变对象,在这里是数字对象。

    理解这个例子本身并不难,但需要对Python对象和序列类型本身有一定的理解。

        第二个例子:列表中包含容器类型变量,即可变对象

>>> person = ['name', ['age', 20]]
>>> xpleaf = person[:]
>>> cl = list(person)
>>> person, xpleaf, cl
(['name', ['age', 20]], ['name', ['age', 20]], ['name', ['age', 20]])
>>> [id(x) for x in person, xpleaf, cl]
[140205544995944, 140205544893688, 140205544875144]
# 查看大列表的元素id值
>>> [id(x) for x in person, xpleaf, cl]
[140205544996160, 140205544875144, 140205544996520]
>>> [id(x) for x in person]
[140205546176112, 140205544995944]
>>> [id(x) for x in xpleaf]
[140205546176112, 140205544995944]
>>> [id(x) for x in cl]
[140205546176112, 140205544995944]
# 查看小列表的元素id值
>>> [id(x) for x in person[1]]
[140205545021232, 32419680]
>>> [id(x) for x in xpleaf[1]]
[140205545021232, 32419680]
>>> [id(x) for x in cl[1]]
[140205545021232, 32419680]

    三个列表的第一个元素的id值都是一样的,这是引用传递,没有什么问题,跟第一个例子类似,因此修改这个值不会有什么问题。但注意看第二个元素,它是一个列表,可以肯定的是,三个列表中的两个元素的id也肯定是相同的,也是引用传递的道理,但现在关键是看第二个元素,也就是这个列表本身,三个大列表(指的是person这个列表)中的这三个小列表的id值都是一样的,于是,浅拷贝对于对象值的影响就会体现出来了,我们尝试去修改其中一个小列表中的值:

>>> xpleaf[1][1] = 22
>>> person, xpleaf, cl
(['name', ['age', 22]], ['name', ['age', 22]], ['name', ['age', 22]])
>>> [id(x) for x in person, xpleaf, cl]
[140205544995944, 140205544893688, 140205544875144]
# 查看大列表的元素id值
>>> [id(x) for x in person]
[140205546176112, 140205544995944]
>>> [id(x) for x in xpleaf]
[140205546176112, 140205544995944]
>>> [id(x) for x in cl]
[140205546176112, 140205544995944]
# 查看小列表的元素id值
>>> [id(x) for x in person[1]]
[140205545021232, 32419680]
>>> [id(x) for x in xpleaf[1]]
[140205545021232, 32419680]
>>> [id(x) for x in cl[1]]
[140205545021232, 32419680]

    可以看到问题就出来了,即对一个小列表进行修改,会影响到其它的小列表。我们先抛开所谓的浅拷贝,去思考这个问题本身:有可能不会影响其它小列表吗?肯定没有可能的,因为三个小列表的id都一样,三个小列表里的元素的id也一样,即其实这三个小列表是完全指向同一个对象的,因此,无论修改哪一个,肯定都会影响其它小列表的。

    这就是所谓浅拷贝出现的问题。

        第三个例子:使用深拷贝来解决第二个例子出现的问题

>>> person = ['name', ['age', 20]]
>>> xpleaf = person[:]
>>> from copy import deepcopy as dcp
>>> cl = dcp(person)
>>> person, xpleaf, cl
(['name', ['age', 20]], ['name', ['age', 20]], ['name', ['age', 20]])
>>> [id(x) for x in person, xpleaf, cl]
[140205544995944, 140205544893688, 140205544875144]
# 查看大列表的元素id值
>>> [id(x) for x in person]
[140205546176112, 140205544996520]
>>> [id(x) for x in xpleaf]
[140205546176112, 140205544996520]
>>> [id(x) for x in cl]
[140205546176112, 140205544571320]
# 查看小列表的元素id值
>>> [id(x) for x in person[1]]
[140205545021232, 32419728]
>>> [id(x) for x in xpleaf[1]]
[140205545021232, 32419728]
>>> [id(x) for x in cl[1]]
[140205545021232, 32419728]

   可以看到虽然是进行了深拷贝,但发现跟前面的其实并没有什么不同,下面我们再来修改其中一个小列表:

>>> xpleaf[1][1] = 22
>>> person, xpleaf, cl
(['name', ['age', 22]], ['name', ['age', 22]], ['name', ['age', 20]])
# 查看大列表的元素id值
>>> [id(x) for x in person]
[140205546176112, 140205544996520]
>>> [id(x) for x in xpleaf]
[140205546176112, 140205544996520]
>>> [id(x) for x in cl]
[140205546176112, 140205544571320]
# 查看小列表的元素id值
>>> [id(x) for x in person[1]]
[140205545021232, 32419680]
>>> [id(x) for x in xpleaf[1]]
[140205545021232, 32419680]
>>> [id(x) for x in cl[1]]
[140205545021232, 32419728]

    此时可以看到,cl的小列表的第二个元素的id跟原来是一样的,但是xpleaf和person的小列表元素的id发生了改变,同时值也是我们修改的那样。那是因为xpleaf是person的浅拷贝,但是cl是person的深拷贝。

    这就是所谓的深拷贝。

        其实只要理解了上面三个例子(这意味着对Python对象本身和序列类型本身也有比较深刻的理解),所以的浅拷贝和深拷贝也不是什么问题了。

        至于是否明白,可以参考下面这个例子:

>>> person = ['name', ('hobby', [1, 2])]
>>> xpleaf = person[:]
>>> from copy import deepcopy as dcp
>>> cl = dcp(person)
>>> 
>>> xpleaf[0] = 'xpleaf'
>>> cl[0] = 'cl'
>>> person, xpleaf, cl
(['name', ('hobby', [1, 2])], ['xpleaf', ('hobby', [1, 2])], ['cl', ('hobby', [1, 2])])
>>> 
>>> xpleaf[1][1][0] = 'clyyh'
>>> person, xpleaf, cl
(['name', ('hobby', ['clyyh', 2])], ['xpleaf', ('hobby', ['clyyh', 2])], ['cl', ('hobby', [1, 2])])

        如果对这个例子的输出觉得完全没有问题的,那么也就OK了!



10.序列类型小结


        可以参考书上的序列类型的操作符、内建函数和方法的列表。


你可能感兴趣的:(python,序列,列表)