Skip to content

Latest commit

 

History

History
1546 lines (1304 loc) · 58.2 KB

File metadata and controls

1546 lines (1304 loc) · 58.2 KB

3. 数据容器

3.1. 序列基本认识

Python中常见的三大序列:str, tuple, list

序列:包含字符串str,列表list,元组tuple,字节串bytes,字节数组bytearray等。

序列特点:元素成员有序排列,有索引下标,因而能被索引切片(如:字典、集合不是序列,不能切片)

3.1.1. 字符串str

3.1.1.1. 基本概念及构建

一种存储多个字节字符的不可变数据类型。
它是序列的一种,不可改变。
增添拼接将会生成新对象。

  • 字符串构建

    1. 直接定义
    s = "abc"
    s = ""  # 定义空字符串
    s = ''  # 定义空字符串
    1. 构造函数:
      str([obj]) 将任意对象obj转换为字符串。obj缺省默认生成空字符串,等同于 ""

    示例

    >>> ""
    ''
    >>> "123"
    '123'
    >>> str()
    ''
    >>> str(3.14)
    '3.14'
    >>> str(None)
    'None'
    >>> str(2+3j)  # 将复数转换为字符串
    '(2+3j)'
  • 表示方法:
    引号:
    (非#号注释中, 凡是用引号括起来的部分都是字符串。)

'    单引号
"    双引号
'''  三单引号
"""  三双引号

注:

  1. 空字符串也是字符串中的一种, 不同于None等。

    >>> ''
    ''
    >>> ""
    ''
    >>> ''''''
    ''
    >>> """"""
    ''
    >>> bool('') == 0 == False
    True
  2. 单引号中可以包含双引号, 双引号中可以包含单引号。
    (当然, 也可以使用转义字符, 见下节)

    >>> 'This is text, "hello"' 
    'This is text, "hello"'
    >>> "You're beautiful"          
    "You're beautiful"
  3. 三单(双)引号可以实现隐式换行

    >>> print('''I
    ... love
    ... you''')
    I
    love
    you
  4. 隐式字符串字面值(一条语句中的多段字符串)可自动拼接, 可应用于语句块中长字符串优雅换行。

    >>> '3344|''1314'    '|520'
    '3344|1314|520'
    >>> ('3344|'                  
    ...  '1314'
    ...  '|520')
    '3344|1314|520'

  • 关于长字符串的换行
    前面第一章我们已经提到过 Python换行规则, 这里我们直接将显式换行和隐式换行引用过来,示例如下:
    # 显式换行
    a = "我是龙的传人" \
        "我为我的名族骄傲"
    print(a)  # 我是龙的传人我为我的名族骄傲
    
    # 隐式换行
    b = ("我是龙的传人"
         "我为我的名族骄傲")
    print(b)  # 我是龙的传人我为我的名族骄傲
    print(a == b)  # True

3.1.1.2. 字符串中每个字符的编码

  • 计算机中,每一个字符,其实是由对应的一个数值来对应表示的。这些定义“数值——字符”的对象关系,被称为编码规则。

  • 常见的编码规则有:ASCIIGBKUTF8、...

  • 如ASCII编码中

    • Linux中查看ASCII 编码表命令: man ascii<回车>

    • 常用的ASCII编码:

        字符     十进制   十六进制
        '!'      33       0x21
        '0'      48       0x30
        'A'      65       0x41
        'a'      97       0x61
      
  • “字符编码值”与“对应字符”互相转换的函数:

    • 查看字符对应的ASCII编码值:

      • ord(c) 返回一个字符的Unicode值,c-->单个字符

        print(ord('0'))  # 48
        print(ord('A'))  # 65
        print(ord('牛'))  # 29275
    • 查看ASCII编码值对应的字符:

      • chr(i) 返回编码值对应的字符,i-->整数

        print(chr(48))  # 0
        print(chr(65))  # A
        print(chr(29275))  # 牛

3.1.1.3. 字符串格式化表达式

格式:

'...%d...%f...%s' % (整数, 浮点数, 字符串)
占位符%d —— 整数
    %f —— 浮点数
    %s —— 字符串等

注:只有一个占位符时后面%()可省略括号, 括号的实质代表元组。
示例

>>> '输入:%d, 输出:%s'%(123, 'out') 
'输入:123, 输出:out'

>>> t = (1,)  # 创建元组,元组细节见下节
>>> type(t)  
<class 'tuple'>
>>> '%s' % t    
'1'
>>> '%s' % (1,) 
'1'
>>> '%s' % 1   
'1'

常见占位符和类型码(占位符类型码):

类型码 占位输入类型 意义 示例 结果
%s 任意类型 返回实际表示字符串, 实质是使用str(x) "%s"%"33" 33
%r 任意类型 返回表达式字符串, 实质使用repr(x)而不是str(x) "%r"%"33" '33'
%c int, 字符的Unicode编码值 返回编码值转换后的字符, 实质使用chr(x) "%c"%33 !
%d int/float/bool, 任意数值 返回十进制整数, 实质是使用int(x) "%d"%33 33
%o int, 整数 返回八进制整数, 实质是使用oct(x) "%o"%33 41
%x int, 整数 返回十六进制整数(字符a-f小写), 实质是使用hex(x) "%x"%33 21
%X int, 整数 返回十六进制整数(字符A-F大写), 实质是使用hex(x) "%X"%33 21
%e int/float/bool, 任意数值 返回指数型浮点数(e小写), 如2.9e+10 "%e"%33 3.300000e+01
%E int/float/bool, 任意数值 返回指数型浮点数(E大写), 如2.9E+10 "%E"%33 3.300000E+01
%f, %F int/float/bool, 任意数值 返回浮点数十进制形式 "%f"%33 33.000000
%g, %G int/float/bool, 任意数值 返回浮点数十进制形式 "%g"%33 33
%% 无需输入 等同于一个%字符, 如'10%%/%s'%10 ==> '10%/10' "%%" %%

注:

根据数值大小自动选择浮点数%f或指数浮点%e转换, 最多保留6位有效数字

格式化字符串的高级应用:

    1. 占位符中的各种参数设置

    语法:

    %[-][+][0][N][.n]x
    
    [-]   当输入字段长度小于设定总占位数, 填充后居左, 缺省默认居右
    [+]   %d, %f, %e 是否显示数值正号
    [0]   %d, %f, %e 中当输入字段长度小于设定总占位数, 前面补零, 默认空格填充
    [N]   占位多少个字符
    [.n]  %f中表示小数后面保留几位小数%d中失效x     占位符类型码, s/r/d/f/e/o/x...

    示例:

    # -居左, +显示数的正号, 10总共占位10个字符, .2保留两位小数
    print('%f' % 123.1)        # '123.1',
    print('%.2f' % 123.1)      # '123.10', 保留两位小数
    print('%+.2f' % 123.1)     # '+123.10', 显示正号
    print('%+10.2f' % 123.1)   # '   +123.10', 总共占位10个字符
    print('%+010.2f' % 123.1)  # '+000123.10', 左边填充0
    print('%-+10.2f' % 123.1)  # '+123.10   ', 居左
    1. 字典方法占位

    格式:'%(键)s'%{'键':值}
    优势:无需按位置对应传入格式化字符串对应位置, 当需要格式化的部位有多次重复时, 可以使用该方法
    示例

    print('%(pb)s-%(pa)s-%(pb)s...' % {'pa': 123, 'pb': 'ceshi'})
    # ceshi-123-ceshi...

注意:

  1. 浮点数格式化默认保留6位小数, 多余补零, 使用中尽量自定义小数位数
>>> '%f'%777.67    # 默认保留6位小数
'777.670000'
>>> '%.3f'%777.67  # 手动定义小数点后保留3位
'777.670'
>>> '{}'.format(777.67)   # format函数可以避免这个问题
'777.67'
  1. 格式化字符串表达式中的%尽量用%%代替
    格式化字符串中%可能让结果无意替换, 使用时需特别注意, 尽量将想输入的%用%%替换。
>>> '30%(state)sport  %(state)s' % {'state':'doing'}  
'30doingport  doingport'
>>> '30%%(state)sport  %(state)s' % {'state':'doing'}
'30%(state)sport  doing'

当然, 这个特点也可以应用到一些特殊处理方面:
示例:字符串多重格式化, 对齐字符串显示到右边

s1 = '123'
s2 = '123456'
s3 = '123456789'
number = max(len(s1), len(s2), len(s3))
print(('%%%ds' % number) % s1)     #       123
print(('%%%ds' % number) % s2)     #    123456
print(('%%%ds' % number) % s3)     # 123456789
print(('%'+str(number)+'s') % s1)  #       123
print(('%'+str(number)+'s') % s2)  #    123456
print(('%'+str(number)+'s') % s3)  # 123456789

3.1.1.4. format()格式化函数

格式:

'{[i]:[x][<^>][+][N][.n]}'.format(x1, x2, x3, ..., kwargs=xx)
[i]    1.缺省时, 代表依次对应函数传入位置读取
       2.当为int整数时, 按索引去传入函数第i个位置的值读取
       3.可自定义关键字传参
[x][<^>]   当填充字符串长度的大于总长时便可激活该[x][<^>]功能
    [x]   定义x去填补需要补位的字符, 缺省默认为空格
    [<^>] 定义传入字符居中(^)、(<)或右(>),
[+]    定义是否显示数值正号
[N]    定义传入字符串的占位字节数, 
[.n]   对浮点数进行小数保留位数处理

示例, 填充的三种方式:

# 按默认位置填充
>>> '1_{}; 2_{}'.format('a', 'b')
'1_a; 2_b'
>>> '1_{}; 2_{}'.format('a', 'b', 'c')
'1_a; 2_b'

# 按索引填充
>>> '1_{2}; 2_{1}'.format('a', 'b', 'c')
'1_c; 2_b'
>>> '1_{2:>10}; 2_{1}'.format('a', 'b', 'c')   # 填充10位居右
'1_         c; 2_b'

# 按关键字填充
>>> '1_{av1}; 2_{av2}'.format(av1='a', av2='b')
'1_a; 2_b'
>>> '1_{av1:^10}; 2_{av2}'.format(av1='a', av2='b')   # 填充10位居中
'1_    a     ; 2_b'
>>> '1_{av1}; 2_{av2}'.format(**{'av1':'a', 'av2':'b'})  # 高级字典传参, 函数用法, 后续会学习到
'1_a; 2_b'

示例: format的常用数值处理

数字 格式 输出 描述
3.1415926 {:.2f} 3.14 保留小数点后两位
3.1415926 {:+.2f} 3.14 带符号保留小数点后两位
-1 {:+.2f} -1 带符号保留小数点后两位
2.71828 {:.0f} 3 不带小数
5 {:0>2d} 5 数字补零 (填充左边, 宽度为2)
5 {:x<4d} 5xxx 数字补x (填充右边, 宽度为4)
10 {:x<4d} 10xx 数字补x (填充右边, 宽度为4)
1000000 {:,} 1,000,000 以逗号分隔的数字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00E+09 指数记法
13 {:10d}         13 右对齐 (默认, 宽度为10)
13 {:<10d} 13 左对齐 (宽度为10)
13 {:^10d} 13 中间对齐 (宽度为10)
11 {:b} 1011 二进制
11 {:d} 11 十进制
11 {:o} 13 八进制
11 {:x} b 十六进制
11 {:#x} 0xb 十六进制(小写)
11 {:#X} 0XB 十六进制(大写)

附:format还可以这样写: (仅在版本 python>=3.6 可用)

a = 6
f"-- {a} --"
# '-- 6 --'
f"--{(6)}--"
# '--6--'
f"--{(sum([1,2,3]))}--"
# '--6--'

3.1.1.5. 转义字符与raw字符串

为什么需要学习转义字符?

  • 有时候我们想要表达的内容会被机器优先识别为特殊字符处理。
  • 具体是什么规则会被优先处理呢。如下:

1) 转义字符:

正常字符串中,用转义序列代表特殊字符:

转义格式 意义
\' 单引号'
\" 双引号"
\\ 反斜杠\
\n 换行
\r 返回光标至行首
\f 换页符
\t 水平制表符
\v 重直制表符
\b 倒退
\0 空字符,字符值为零
\000 000为三位八进制表示的字符
\xXX XX为两位十六进制表示的字符
\uXXXX Unicode 16的十六进制表示的字符
\UXXXXXXXX Unicode 32的十六进制表示的字符

2) raw字符串(原始字符串)
使输入字符串按照输入的样子存储而不进行转义。
如字符串表达式中,双反斜杠实际表示的是一个反斜杠


示例1:如何传达真实想表达的内容ABC\bD给计算机

  • 转义字符与raw字符串对比。
>>> "ABC\bD"  # 字符串表达式,可以看见\b细节
'ABC\x08D'
>>> print("ABC\bD")   # 打印可以看见输入实际效果倒退一格
ABD
>>> "ABC\\bD"  # 当使用\\后可以转义\,打印原样
'ABC\\bD'
>>> print("ABC\\bD")  # 当使用\\后可以转义\,打印原样
ABC\bD
>>> print(r"ABC\bD")  # 当使用raw字符串,打印原样
ABC\bD

示例2:不同进制ascii编码对应的换行符

# 通过不同进制的编码值表达\n
#  原始  8进制 16进制  16进制 16进制
>>> '\n  \012  \x0A  \u000A  \U0000000A'
'\n  \n  \n  \n  \n'

示例3:转义字符和raw字符串的应用——路径输入

>>> s1 = "c:\\now_folder\file"       # \x0c的ascii对应的是换页符
'c:\\now_folder\x0cile'

>>> print(s1)                   # 打印出来,转义字符会转换为实际值
c:\now_folder 
              ile

# 让原有转义字符无效:
>>> s2 = r"c:\\now_folder\file"  # 返回字符串及转义字符,

>>> s2
'c:\\\\now_folder\\file'

>>> print(s)
c:\\now_folder\file

# 为何s2表达式中双斜杠会出现4个重复?表达式中的\\表示实际一个\
>>> s='\\'
>>> s
'\\'
>>> print(s)
\

练习: 口算

'1234abcd'     有几个字符?
'5\'4"'        有几个字符?
'\"A\x34\056'  有几个字符?
'\a\bc\td\n'   有几个字符?

答案:

8
4
4
6

3.1.2. 元组tuple

3.1.2.1. 基本概念及构建

一种可以存放任意类型的有序不可变数据容器。
元组是一种序列,有序,可被索引切片。
元组是一种不可变数据对象,同字符串一样,增添拼接新元组将会生成新元组对象。

  • 表示方法: 用()括起来,单个元素括起来后内部加逗号。注意区分单个数据对象还是元组,如(20)不同于(20,)

  • 元组构建

  1. 直接定义
t = x,
t = (x,)
t = x,y
t = (x,y)

示例

t = 20,        # (20,)
t = (20,)      # (20,)
t = 20,30      # (20,30)
t = (20,30)    # (20,30)

注意:
单个元素的元组创建需要利用逗号。若无逗号则为普通的数据给予运算优先级。
如:

t = (20)
print(type(t))  # <class 'int'>
# 整数,不是元组, 等同于表达式 `t=20`, 单个元素构建元组时需要加","
# 正确方法:
t = 20,  
print(type(t))  # <class 'tuple'>
t = (20,)  
print(type(t))  # <class 'tuple'>
  1. 构造函数:
tuple([iterable])
  任意<可迭代对象>转换生成一个元组
  默认为空生成一个空的元组,等同于()

示例

>>> tuple(range(1,10)) 
(1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> tuple([x**2 for x in range(1,10)])
(1, 4, 9, 16, 25, 36, 49, 64, 81)

3.1.3. 列表list

3.1.3.1. 基本概念及构建

列表:一种可以存储任意数据类型并可供增删改查的有序可变数据容器。
作用:存储数据,同时方便添加,删除,修改等操作。
特征:存放任意数据对象的容器;创建的列表可以改变元素对象,列表长度;由一系列有顺序的元素组成;元素之间可没有任何联系;可以进行比较;

  • 列表构建
    1. 直接定义
    L = []  # 创建空列表的字面值
    L = [1, 2, 3]  # 创建非空列表的字面值
    1. 构造函数:
    list()  生成一个空的列表等同于[]
    list(iterable)  <可迭代对象>创建一个列表
    示例
    >>> list(range(1,5))
    [1, 2, 3, 4]
    >>> list('1234')
    ['1', '2', '3', '4']

3.1.3.2. 如何理解对象可变与不可变?

两个层次考虑:对象长度,对象本身。例如元组:

  1. 长度:不能增删新的数据成员,大小不可改变;
  2. 对象本身:不能改数据成员为另一个数据成员,每一个索引绑定的对象不可改变,即id不可变。

关于第2点, 如果元组内元素对象本身是可变的, 对象内部的数据操作并不引起外部对象的改变,此操作被允许。

t = ([1],[2])   
print(type(t))  # <class 'tuple'>
print(t[0])  # [1]
t[0].append(1)
print(t)  # ([1, 1], [2])  内部数据可操作改变

3.2. 序列的基本操作

3.2.1. 序列多重赋值

关于容器的赋值:

x, y, z = 100, 200, 300    # 序列赋值等同于如下
x, y, z = (100, 200, 300)  # 序列赋值
x, y, z = [100, 200, 300]  # 序列赋值
x, y, z = "ABC"            # 序列赋值
# 序列对象可以直接多元赋值
a, b, c = "abc"
a, b, c = (1024, 2048, 4096)  # 元组
a, b, c = [1024, 2048, 4096]  # 列表
# 前面可以是元组和列表
(a, b, c) = (1024, 2048, 4096)
[a, b, c] = (1024, 2048, 4096)
# 多元赋值到一个变量
t = a, b, c = (1024, 2048, 4096)  # 元组
print(type(t))   # <class 'tulpe'>
t = [a, b, c] = [1024, 2048, 4096]
print(type(t))   # <class 'list'>

3.2.2. 序列的运算

3.2.2.1. 算术运算

+    拼接字符如运算x+=y
*    仅能和整数相乘生成重复的序列'123'*3,[1,2]*2
+=   增量赋值序列增值
*=   增量赋值同上
  • 特殊情况:列表中的+=格式必须为:列表 += <可迭代对象>
>>> L = [1, 2]
>>> L += '123'
>>> L
[1, 2, '1', '2', '3']

>>> L = [1, 2]
>>> L += range(3)
>>> L
[1, 2, 0, 1, 2]
  • 注:
  1. 列表不可和其他类型直接相加

    >>> [1, 2] + '123'  # 类型错误
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can only concatenate list (not "str") to list
  2. 增量赋值中,若起始对象为不可变对象,增量复制结果将重新生成一个新对象。

    s = "12"
    print(id(s))  # 2123634715120
    s += "34"
    print(id(s))  # 2123647765424  改变
    
    t = (1, 2)
    print(id(t))  # 2123644750216
    t += (3, 4)
    print(id(t))  # 2123644788808  改变
    
    L = [1, 2]
    print(id(L))  # 2123646523592
    L += [3, 4]
    print(id(L))  # 2123646523592  不改变

3.2.2.2. 比较运算

比较运算符:
格式:obj1 运算符 obj2
运算符:<<=>>===!=
返回:布尔值

序列之间比较
规则:从左往右依次迭代单个元素比较,直到出现不同时进行比较返回布尔值。

比较的最终本质是相同位置的不同大小的数值大小比较。

字符串的比较亦是如此,是使用当前单个字符对应的ascii数值大小去进行数值比较,字符串比较的本质亦是数值比较。

示例

>>> ord('A') # 返回字符A所对应的Unicode值(整数)
65
>>> chr(65)  # 返回Unicode值为65所对应编码的字符
'A'

# 从左往右第二位出现不同,比较Unicode数值大小
>>> 'ab'<'adca'
True
>>> print(ord('b'), ord('d'))  # 98<100因此'ab'<'adca'
98 100
# 此处足以见得数值和字符串类型的差异,实际编程定要转换类型避免
>>> "1000" < "11"
True
>>> print(ord("0"), ord("1"))  # 48<49因此"1000" < "11"
48 49

# 列表及元组原理也一样,逐个迭代取出
>>> ['ab', [123, 'cde']] < ['ab', [123, 'dde']] 
True

# 数值类型不同比较时会出错。但可以用!=返回true
>>> '0' < 1  # Python2中会直接返回False,可能造成某些时候人为检测不到位出现BUG,这也是推荐Python3的原因所在
TypeError...
>>> '0' != 1
True

3.2.2.3. in / not in 运算符比较判断

in 用于序列,字典,集合等容器类中,用于判断某个值是否存在于容器中,如果存在返回True,不存在返回False
not in 与 in运算符返回的布尔值相反

格式:对象 in 序列
示例:

x = 'welcome to beijing'
'to' in x  # True
'hello' in x # False

3.2.2.4. 索引和切片取值

  • 索引(Index):
    语法:
    序列[整数表达式]
    注:
    整数表达式处返回结果必须为整形数; 字符串每个字符对应一个下标,元组和列表每个元素对应一个下标;
    说明:
    python序列都可以用索引(index)来访问序列中的对象
    python序列正向的索引从0开始,第二个索引为1,最后一个索引为len(s)-1
    python序列的反向索引从-1开始,-1代表最后一个,-2代表倒数第二个,以此类推,第一个是-len(s)

    • 序列元素查找获取起始索引值
    X.index(v[, begin[, end]])
    返回对应元素的索引下标序列方法X为字符串元组或列表选项v      字符串中v表示一段字符串列表和元组中v表示单个元素不存在时触发ValueErorr错误begin  为开始索引
        end    为结束索引
  • 切片(slice)
    作用: 从序列中取出一部分相应的元素重新组成一个序列
    返回:
    类型保持同原类型一致的,浅层复制的,新对象。
    语法:
    序列[(开始索引b):(结束索引e)(:(步长s))]
    注: () 内括起的部分代表可以省略
    说明:

    1. 开始索引是切片开始切下的位置。0代表第一个元素,-1代表最后一个元素。缺省时补0(步长为正)或-1(步长为负)。
    2. 结束索引是切片的终止索引(但不包含终止索引)。缺省时补0(步长为正)或-1(步长为负)
    3. 步长是切片每次获取完当前索引后,移动的方向和偏移量
    1. 没有步长,相当于取值完成后向后移动一个索引的位置(默认为1)
    2. 当步长为正整数时,取正向切片:
      步长默认值为1, 开始索引默认值为0, 结束索引的默认值为len(s)
    3. 当步长为负整数时,取反向切片:
      反向切片时,默认的起始位置为最后一个元素,默认终止位置为第一个元素的前一个位置

    切片规则示例

    # 分析:
    #列表数据:  0   1  2  3  4   5  6  7   8   9
    #正向索引:  0   1  2  3  4   5  6  7   8   9
    #反向索引: -10 -9 -8 -7 -6  -5 -4 -3  -2  -1
    #
    s='0123456789'
    
    # 正向切片,步长为1时可省略
    s[2:8]      # '234567' 切片取第一位,停止于最后一位前。[a,b)
    s[2:8:1]    # '234567' 默认步数为1可省略
    s[2:8:2]    # '246' 步长为2,从开始索引处每取一个索引+2
    
    # 逆向切片,需保持起止索引位置反向,并且步长为负
    s[1:8:-1]  # ''  从1:8取序列,向左步进无结果,返回空字符串
    s[8:1]     # ''  从8:1取序列,向右步进无结果,返回空字符串
    s[-2:-9]     # ''
    s[8:1:-1]    # '8765432'  反向切不报错,使用负步数可以打印结果
    s[-2:1:-1]   # '8765432'  可用反向索引替代正向索引
    s[-2:-9:-1]  # '8765432'  
    s[-2:-9:-2]  # '8642'
    
    # 关于省略
    s[:5]  # '12345'  等同于s[0:5:1]
    s[5:]  # '67'     等同于s[5:-1:1]
    s[::]   # '0123456789'  等同于s[0:-1:1]
    s[::1]  # '0123456789'  等同于s[0:-1:1]
    s[::-1] # '9876543210'  等同于s[-1:0:1]  生成反向序列
    s[::2]  # '02468'       等同于s[0:-1:2]
    # 可用于迅速判断回文序列
    x = "12321"
    x == x[::-1]  # True
    
    # 关于越界,开始和结束索引都能越界,返回有值结果,无则空
    "123"[-100:100]    # '123'
    (1,2,3)[-100:200]  # (1, 2, 3)  元组
    [1,2,3][-100:200]  # [1, 2, 3]  列表

3.2.2.5. 索引和切片赋值

特殊情况:
索引和切片的赋值必须是对<可变对象>操作,即列表。

  • 列表的索引赋值
    列表是可变的,可以通过索引赋值改变列表中的元素
    语法: 列表[整数表达式] = <任意对象>

    示例

    L = [1, 2, 3, 4]
    L[2] = 3.1
    print(L)
  • 列表切片的赋值
    语法:列表[切片slice] = <可迭代对象>
    作用:可以改变列表对应的元素的值,改变原列表的排列,可以插入数据和修改数据
    说明:切片的赋值运算符(=)的右侧必须是一个"可迭代对象"

  • 示例

# 替换
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(L[1:3])  # [1, 2]
L[1:3] = [11, 22]
print(L)  # [0, 11, 22, 3, 4, 5, 6, 7, 8, 9]

# 删除
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(L[1:3])  # [1, 2]
L[1:3] = []
print(L)  # [0, 3, 4, 5, 6, 7, 8, 9]

# 增加
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(L[1:1])  # []
L[1:2] = [11, 22, 33, 44]
print(L)  # [0, 11, 22, 33, 44, 2, 3, 4, 5, 6, 7, 8, 9]

# 逆序一部分,并多重赋值
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a = L[1:5] = L[1:5][::-1]
print(L)  # [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]
print(a)  # [4, 3, 2, 1]
  • 切片的一些应用技巧:
# 插入元素
L[0:0] = [5]    # 第一位后
L[-1:-1] = [5]  # 最后一位前
# 反转元素
L = L[::-1]     # 式1
L[:] = L[::-1]  # 式2,切片赋值应用,可用于不可变对象的操作中。
'''
式1生成逆序新列表后,
  直接将新对象列表赋值绑定到L变量,
  因此L变量会改变id
式2生成逆序新列表后,
  直接在原对象列表上覆写,
  因此L变量不会改变id
'''
  • 切片注意事项:
    对于步长不等1的切片赋值,赋值运算符的右侧的可迭代对象提供的数据元素的个数一定要等于切片切出的段数。
    例如:
L = [1, 2, 3, 4, 5, 6]
L[::2] = 'ABC'  # 正确
L[::2] = 'ABCDE' # 错误

3.2.2.6. del语句

对于可变对象——列表,del可用来删除列表元素
语法:
del 列表[索引]
del 列表[切片]
示例

>>> L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del L[-1]
>>> L
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> del L[1:8]
>>> L
[0, 8]

3.2.3. 序列可用的内建函数

语法 返回 说明
序列属性判断函数  
max(iterable) int 返回<可迭代对象>最大值
min(iterable) int 返回<可迭代对象>最小值
len(iterable) int 返回<可迭代对象>的长度
any(iterable) bool 真值测试,判断所有元素,一个为真,返回True
all(iterable) bool 真值测试,判断所有元素,都为真,返回True
序列计算的函数  
sum(iterable[,x]) int 返回<可迭代对象>与初始值x相加的和

iterable 可以是列表、元组、字典、集合等任意可迭代对象

其他高级函数:如排序函数sorted,见函数章节-->高阶函数。

3.2.4. 序列对象的方法

方法和函数都是表达式,方法可以说是一种特殊的函数。方法与函数不同的是方法是类对象内的函数,使用需要通过类对象调用。(函数后续有一章节专门探讨,方法在类对象一章探讨)

格式:对象.方法名(*args, **kwargs)

3.2.4.1. 字符串对象操作方法

  • 常用字符串对象方法 格式: 包括:
方法 返回 说明
基本判断  
S.isdigit() bool 判断字符串中的字符是否全为数字。如:123
S.isalpha() bool 判断字符串是否全为英文字母。如:abc
S.isalnum() bool 判断字符串是否全为数学/英文字母,如:a1
S.isnumeric() bool 判断字符串是否全为数字字符。如:1二三4
S.isspace() bool 判断字符串是否全为空白字符。如:\n\t
S.islower() bool 判断字符串所有字符是否全为小写英文字母
S.isupper() bool 判断字符串所有字符是否全为大写英文字母
开始与结束匹配判断  
S.startswith(prefix[, start[, end]]) bool 返回S是否是以prefix开头,如果以prefix开头返回True,否则返回False,
S.endswith(suffix[, start[, end]]) bool 返回S是否是以suffix结尾,如果以suffix结尾返回True,否则返回False
字符串修改  
S.strip([fill]) str 返回去掉左右空白字符的字符串,fill默认一切空字符,可为单个或多个字符
S.lstrip() str 返回去掉左侧空白字符的字符串
S.rstrip() str 返回去掉右侧空白字符的字符串
S.replace(old, new[,count]) str 将原字符串的old用new代替,生成新字符串,count是替换结束的位置索引
S.upper() str 生成将英文转换为大写的字符串
S.lower() str 生成将英文转换为小写的字符串
S.title() str 生成每个英文单词的首字母大写字符串
字符串填充  
S.rjust(width[,fill]) str 将原字符串居右,fill左右默认填充空格,width总宽度
S.ljust(width[,fill]) str 将原字符串居左,fill左右默认填充空格,width总宽度
S.center(width[,fill]) str 将原字符串居中,fill左右默认填充空格,width总宽度
统计相关  
S.count(sub[,start[,end]]) int 获取一个字符串中子串的个数, 如统计某段字符占多少个。
查找相关  
S.find(sub[, start[,end]]) int 获取字符串中子串sub的索引,失败返回-1
S.index(str, beg=0, end=len(string)) int 跟find()方法一样,只不过如果str不在字符串中会报一个异常.
字符串的文本解析方法  
str.join(iterable) str 返回对象连接的字符串。对<可迭代对象>用字符str连接。
str.split([sep=None [, maxsplit]]) list 返回字符分割的列表。
sep为分割符,默认为一切空白字符;
maxsplit为表示最多切割份数,默认为无限制。
str.splitlines() list 返回字符分割的列表,以\r\n或\n为分隔符,

以上为列举的常用方法,更多请参考官网

示例

# 判断是否全为英文字母
'abc'.isalpha()  # True

# 字符串.startswith(),判断字符串的首字符是否符合要求
s = "this is string example....wow!!!"
print(s.startswith( 'this' ))        # True
print(s.startswith( 'is', 2, 4 ))    # True
print(s.startswith( 'this', 2, 4 ))  # False

# 大小写转换
s = "www.runoob.com"
print(s.upper())          # WWW.RUNOOB.COM 把所有字符中的小写字母转换成大写字母
print(s.lower())          # www.runoob.com 把所有字符中的大写字母转换成小写字母
print(s.capitalize())     # Www.runoob.com 把第一个字母转化为大写字母,其余小写
print(s.title())          # Www.Runoob.Com 把每个单词的第一个字母转化为大写,其余小写 
print (s.swapcase())      # WWW.RUNOOB.COM 大小写转换

# 高级示范  寻找某个字符串之后的索引
s = '0123456789'
s.find(s.split('456')[1])  # 7

3.2.4.2. 列表对象操作方法

  • 列表对象方法
方法 返回 说明
L.append(x) None 向列表中追加单个元素
L.extend(lst) None 向列表追加另一个列表lst,或其他<可迭代对象>
L.insert(index, obj) None 将某个元素插放到列表中指定的位置(索引后面一位)
L.remove(x) None 从列表中删除第一次出现在列表中的值x
L.pop( [index] ) 相应数据对象 删除索引对应的元素,默认删除最后元素(不加索引),同时返回删除元素的引用关系(对应删除的元素)
L.clear() None 清空列表,等同于 L[:] = []
L.copy() None 复制此列表(只复制一层,不会复制深层对象)
L.sort(reverse=False) None 将列表中的元素进行排序,默认顺序按值的小到大的顺序排列
L.reverse() None 列表的反转,用来改变原列表的先后顺序
L.index(v [, begin[, end]]) 数值int 返回对应元素的索引下标。begin为开始索引,end为结束索引,当 value 不存在时触发ValueError错误
L.count(x) 数值int 返回列表中元素x的个数

列表的操作方法一般无返回,在函数中的返回用None替代,返回值为None的问题详见后续章节:函数function。

示例:列表LIST添加元素的几种方法对比

  1. 列表.append()方法
    将“待插入任意元素对象”作为一个元素添加到原数据
>>> data = [1,2]
>>> data.append(['123','abc'])
>>> data
[1, 2, ['123', 'abc']]
  1. 列表.extend()方法
    将“待插入可迭代对象”的各个元素连接到原列表数据对象末尾
>>> data = [1,2]
>>> data.extend(['123','abc'])
>>> data
[1, 2, '123', 'abc']
>>> L = [1, 2, 3]
>>> L.extend("123")   # 理解:隐藏的将对象"123"进行了一次list("123")
>>> L
[1, 2, 3, '1', '2', '3']
>>> L = [1, 2, 3]
>>> L.extend((1,2,"123"))
>>> L
[1, 2, 3, 1, 2, '123']
  1. 列表.insert()方法
    将“待插入任意元素对象”作为一个元素添加到原列表对象特定位置
>>> data = [1,2]
>>> data.insert(1,'abc')
>>> data
[1, 'abc', 2]
  1. 加号
    前面三种方法(append, extend, insert)是直接对列表增加元素操作,他们没有返回值,是直接修改了原数据对象,id不变。
  • 直接用加号:将两个list相加,会返回到一个新的list对象。
  • 增量赋值:+=符号可以直接在原始对象后面连接新的可迭代对象,保持对象不变化,与extend相似。

总结:list增加元素时,尽量选择不要创建新list对象的方式,创建新的list对象需要消耗额外的内存,特别是当list较大时,尽量不要直接使用“+”来添加list,而应该尽可能使用“+=”或list的三个自带方法,其中追加元素最好的方法是L.append(x)(验证见 附录-列表速度探索:Python3列表追加元素之"L+=[x]"与"L.extend([x])"与"L.append(x)"谁更快? )。

L1 = ['a', 'b']
print(id(L1))  # 2582312078088
L2 = ['x', 'y', 'z']
L1 = L1 + L2  # 直接相加
print(id(L1))  # 2582311138440  id发生改变
print(L1)      # ['a', 'b', 'x', 'y', 'z']

L1 = ['a', 'b']
print(id(L1))  # 2582311919752
L2 = ['x', 'y', 'z']
L1 += L2   # 增量赋值
print(id(L1))  # 2582311919752  id不改变
print(L1)      # ['a', 'b', 'x', 'y', 'z']

3.2.4.3. 元组对象操作方法

元组为不可变对象,不可增删改。因而无对应数据变更方法。
只有两种

方法 返回 说明
T.index(v[, begin[, end]]) None 同list
T.count(x) int 同list

3.3. 字典dict

3.3.1. 基本概念与使用

什么是字典?理解:

  1. 字典是一种可变的容器,可以存储任意类型的数据
  2. 字典中的每个数据都是用"键"(key) 进行索引,而不同于序列可以用下标来进行索引
  3. 字典中的数据没有先后顺序关系,字典的存储是无序的
  4. 字典中的数据以键(key)-值(value)对形式进行映射存储
  5. 字典的键不能重复,且只能用不可变类型作为字典的键
  • 特征:
    字典无序排列,无重复;
    键必须为绝对不可变对象,即其中不能嵌套可变对象。
    值可以是任意类型数据(可变不可变均可);

3.3.1.1. 字典构建

  1. 直接定义:
d = {}	# 空字典
d = {key:value, key2:value2, ...}
  1. 字典构造函数:
dict()    # 创建空字典,等同于 {}
dict(iterable)  用可迭代对象初始化一个字典
dict(**kwargs)  关键字传参形式生成一个字典

示例

# 1) 直接定义
d = {1:123,'a':123}		# {1: 123, 'a': 123}

# 2) 二元可迭代对象转换字典
d = dict([(1,123),('a',234)])  # {1: 123, 'a': 234}

# 3) 关键词传参
d = dict(name='abc',age=14)  # {'name': 'abc', 'age': 14}

# 其他:
# 批量创建相同值的字典
d = dict.fromkeys((1, 2, 3), 0)
print(d)  # {1: 0, 2: 0, 3: 0}

示例:关于键必须为绝对不可变对象的展示

# 正常创建
key = ((1,2), (3,4))
d = {key:True}

# 错误创建
key = ((1,2), [3,4])
d = {key:True}  # 键内嵌套包含有可变对象list,创建报错
# TypeError: unhashable type: 'list'

3.3.1.2. in / not in查找字典

in / not in:判断键是否在字典内
in 判断字典实际是判断键的集合对象

>>> d = {'a':1, 'b':2, 'c':3}
>>> 'a' in d
True
>>> 'a' in d.keys()  # 查找时无需多此一举,直接in字典最好,以免生成新对象增加不必要的存储资源浪费
True
>>> d.keys()
dict_keys(['a', 'b', 'c'])

关于in速度的测试结果见附录Python3中'in操作'在列表,字典,集合中的速度对比

3.3.1.3. 获取键-值数据

键索引:d[键],返回键的值。

迭代访问:for i in d,得到的是键i,可通过d[i]得到键值。
(关于迭代使用见下一章)

d = {1:True, 2:False, 3:True}
for key in d:
    print(key, d[key])
"""
1 True
2 False
3 True
"""

3.3.1.4. 基本增删修改字典

键赋值修改操作d[键] = 值,可用于创建与修改键值对。
删除操作del d[键],删除键值对

3.3.2. 字典对象的方法

D代表已创建的字典对象 常用方法:

函数 返回 说明
D.clear() None 清空字典
D.pop(key) 相应值对象 移除键值,同时返回此键所对应的值
D.copy() 新复制对象 返回字典D的副本,只复制一层(浅拷贝)
D.update(D2) None 将字典D2对D进行升级,加入不存在的键值对,升级已存在的键值对
D.get(key[, value]) 相应值对象 返回键key对应的值, 若键不存在, 则返回value(默认值为None)
D.setdefault(key[, value) 相应值对象 返回键key对应的值, 若键不存在, 则创建新键值对D[key]=value, 并返回value(默认值为None)。
D.keys() 可迭代对象 返回可迭代的 dict_keys 集合对象,所有键
D.values() 可迭代对象 返回可迭代的 dict_values 值对象,所有值
D.items() 可迭代对象 返回可迭代的 dict_items 对象,键值组合的元组

示例:D.items()返回可迭代的 dict_items 对象

d = {1: 'a', 2: 'b', 'A': 'a', 'B': 'b'}
print(d.items())  # dict_items([(1, 'a'), (2, 'b'), ('A', 'a'), ('B', 'b')])

3.3.3. 字典可用的内建函数

len(x)
max(x)
min(x)
any(x)
all(x)
sum(x)

同序列,只对键比较和运算,需要键类型一致。

3.3.4. 字典的应用

  • 用于数据查找
    字典查找替换等操作

  • 存储结构化数据

citys={
    '北京':{
        '朝阳':['国贸','CBD','天阶','我爱我家','链接地产'],
        '海淀':['圆明园','苏州街','中关村','北京大学'],
        '昌平':['沙河','南口','小汤山',],
        '怀柔':['桃花','梅花','大山'],
        '密云':['密云A','密云B','密云C']
    },
    '河北':{
        '石家庄':['石家庄A','石家庄B','石家庄C','石家庄D','石家庄E'],
        '张家口':['张家口A','张家口B','张家口C'],
        '承德':['承德A','承德B','承德C','承德D']
    }
}

3.4. 集合set

3.4.1. 基本概念与使用

特征:

可变容器;
无重复(数据对象具有唯一性);
无序储存结构,无先后顺序关系(插入,删除等操作很快);
元素必须是不可变对象(如列表字典不能作为元素);
相当于只有键的字典。

3.4.1.1. 集合构建

  1. 直接定义:
s = {1, 2 ,3 ,4}
  1. 构建函数:
set()	  创建一个空的集合对象不能用{}来创建空集合因为{}默认创建的是字典set(iterable)   <可迭代对象>创建一个集合
                iterable必须为绝对不可变对象同字典的键规则一样
                若iterable是字典只取键若是字符串则分割

示例

s = set("ABC")  # {'B', 'A', 'C'}	 集合无序

# iterable必须为绝对不可变对象,同字典的键规则一样
s = {1, 2, 3, (4, (5, []))}  # 创建元素中不能包含可变对象 TypeError

3.4.1.2. in / not in判断某元素是否在集合中

同上

3.4.2. 集合的运算:


  交  并  补  对补
  &   |   -   ^

  ==  !=  判断集合是否相同
  
  <=  >=  判断包含与被包含关系
  • 示例
>>> s = {1, 2, 3}
>>> s1 = {1, 2, 3}
>>> s2 = {2, 3, 4}
>>> print(s1 & s2)		# & 生成两个集合的交集
{2, 3}
>>> print(s1 | s2)		# | 生成两个集合的并集
{1, 2, 3, 4}
>>> print(s1 - s2)		# - 生成两个集合的补集
{1}
>>> print(s2 - s1)
{4}
>>> print(s2 ^ s1)		# ^ 生成两个集合的对称补集
{1, 4}
> <
s1 = {4, 5, 6}
s1 = {4, 5}
>>> print(s1 > s2)		# > 判断一个集合是否是另一个集合的超集
Ture
>>> print(s1 < s2)		# < 判断一个集合是否是另一个集合的子集
False

>>> {1,2,3} == {2,1,3} 
True

3.4.3. 内建函数

集合的内建函数操作: len(x), max(x), min(x), sum(x), any(x), all(x)

3.4.4. 集合常用方法

方法 返回 意义
S.add(e) None 在集合中添加一个新的元素e;如果元素已经存在,则不添加
S.remove(e) None 从集合S中删除一个元素e,如果元素e不存在于集合中,则会产生一个KeyError错误
S.discard(e) None 从集合S中移除一个元素e,如果元素e不存在与集合中,则什么都不做;
S.pop() 数值 从集合S中删除一个随机元素;如果此集合为空,则引发KeyError异常。
S.clear() None 清空集合内的所有元素
S.copy() 新集合 将集合进行一次浅拷贝
S.update(s2) None 更新原集合,用 S与s2得到的全集更新变量S(相当于并和运算)。
S.difference(s2) 集合 用S - s2 运算,返回S中的特有元素
S.difference_update(s2) None 更新原集合,运算等同于 S = S - s2
S.intersection(s2) 集合 等同于 S & s2
S.intersection_update(s2) None 更新原集合,运算等同于S = S & s2
S.symmetric_difference(s2) 集合 返回对称补集,等同于 S ^ s2
S.symmetric_difference_update(s2) None 更新原集合,用 S 与 s2 的对称补集更新 S
S.union(s2) 集合 生成 S 与 s2的全集
S.isdisjoint(s2) 布尔值 如果S与s2交集为空返回True,非空则返回False
S.issubset(s2) 布尔值 如果S与s2交集为非空返回True,空则返回False
S.issuperset(...) 布尔值 如果S为s2的子集返回True,否则返回False

注意:s1=s1|s2是赋予一个新对象, s1|=s2(别称并和运算)则是直接更新原集合

实例:

enter = input('enter 0 or 1:')
s1 = {1, 2, 3}
s2 = {2, 3, 4}
s = s1
print(s is s1)
if enter == '0':
    s1 = s1 | s2  # 生成新对象{1,2,3,4}
    print(s is s1)
    print(s1)  # {1,2,3,4}
    print(s)  # {1,2,3}
elif enter == '1':
    s1 |= s2  # 将对象更新变为{1,2,3,4},相当于方法S.update(s2)
    print(s is s1)
    print(s1)  # {1,2,3,4}
    print(s)  # {1,2,3,4}

3.4.5. 集合的应用

  • 迭代对象 集合是可迭代对象 用for语句可以得到集合中的全部数据元素

  • 1、去重复

a = [1, 1, 2, 2, 3]
a = list(set(a))
  • 2、判断无序相等
a = ["a","b","c"]
a = ["a","c","b"]
a == b    #Fulse
set(a) == set(b)  #True

3.5. 固定集合frozenset

作用: 固定集合可作为字典的键,也可以作为集合的值。(保持了集合的无重复查找速度等特性,又让对象变为不可变对象。) 创建空的固定集合:

fz = frozenset()

创建非空的固定集合:

>>> fz = frozenset([1])
>>> fz
frozenset({1})

固定集合运算,同set运算一致。参见set运算 相当于集合的方法去掉修改集合的方法。

固定集合可以和集合进行比较,如 set(1,2,3)=frozenset… True

3.6. 数据容器深入探索

3.6.1. 字典vs列表

  • 比较
  1. 都是可变对象
  2. 索引方式不同,列表用整数索引,字典用键索引
  3. 字典的查找速度可能会快于列表(重要) (字典机制:不会一一比对)
  4. 列表的存储是有序的,字典的存储是无序的
  • 列表和字典的内部存储原理
  1. 列表是顺序存储
    尾插很方便,但头插会将后面的数据都后挪一位,很麻烦。查找也是顺序逐个比对。优点是存储有序,内存消耗更低
  2. 字典是映射存储
    添加,删除,查找速度都很快
  • 集合和字典的优点 in / not in 运算符的查找速度快; 插入、删除快。

查找速度测试对比,见附录Python3中“in操作”在列表,字典,集合中的速度对比

3.6.2. 序列的存储本质

  • 序列的存储本质:序列的索引本质上也是一个变量名。
    序列中的数据存储,仍然是变量与对象的绑定关系。只不过这里的变量是序列的索引。如,在L = [1, 2, 3]中,可以理解为有三个变量L[1], L[2], L[3],分别被绑定于数据对象1, 2, 3
    示例:(帮助理解)
    # 以下代码输出是什么呢?
    L = [1, 2, [3, 31, 32]]
    a = L[2]
    a.append(33)
    print(a)  # __(1)__
    print(L[2])  # __(2)__
    
    """答案:
    [3, 31, 32, 33]
    [3, 31, 32, 33]
    """

3.6.3. 浅拷贝 与 深拷贝 理解

(浅层复制 与 深层复制 理解)

浅拷贝 shallow copy
深拷贝 deep copy

总结:
浅拷贝: 是指在复制过程中,只复制一层变量绑定关系,不会复制深层变量绑定的对象的复制过程
深拷贝: 对所有可变对象进行层层复制,实现对象的各自独立。

3.6.3.1. 语法及使用方法:

基本语法:

import copy   # 导入copy模块
L = "任意类型数据"
L2 = copy.copy(L)     # 浅拷贝
L2 = copy.deepcopy(L) # 深拷贝

特别的,在列表中还可以通过切片和自身的copy方法进行浅层复制,如下:

# 浅拷贝
# 1) 利用切片方法。切片——返回切片后的新对象。如
L2 = L[:]
L2 = L[::]
# 2) 利用列表自身方法
L2 = L.copy()

3.6.3.2. 原理理解

浅层复制只复制最外一层的数据或容器对象,内部直接被赋值绑定于新对象对应位置

示例:如何解释Out4和Out5相同,Out9和Out10部分相同,以及后面的测试结果?

In [1]: import copy

In [2]: L = [-10,  (-20, -30),   [-40, -50, (-60, -70)]]


# 浅拷贝测试
In [3]: X = L.copy()

In [4]: [id(x) for x in L]
Out[4]: [1684750713584, 1684750470920, 1684749580488]

In [5]: [id(x) for x in X]
Out[5]: [1684750713584, 1684750470920, 1684749580488]

In [6]: id(L), id(X)
Out[6]: (1684750584648, 1684750627144)


# 深拷贝测试
In [7]: Y = copy.deepcopy(L)

In [8]: [id(x) for x in L]
Out[8]: [1684750713584, 1684750470920, 1684749580488]

In [9]: [id(x) for x in X]
Out[9]: [1684750713584, 1684750470920, 1684749580488]

In [10]: [id(x) for x in Y]
Out[10]: [1684750713584, 1684750470920, 1684748555272]


# 数据测试
In [11]: L.append("测试1")

In [12]: L[2].append("测试2")

In [13]: L, X, Y
Out[13]: 
([-10, (-20, -30), [-40, -50, (-60, -70), '测试2'], '测试1'],
 [-10, (-20, -30), [-40, -50, (-60, -70), '测试2']],
 [-10, (-20, -30), [-40, -50, (-60, -70)]])

图解:

  1. 创建L列表 创建3个数据对象,放入列表中(绑定于数据容器对应位置)
L = [-10,  (-20, -30),   [-40, -50, (-60, -70)]]

绑定关系如下:

# 索引(变量)绑定关系,索引也可看做特殊变量
      L[0]  L[1]           L[2]
       |     |             |
       |     |             |
L = [-10,  (-20, -30),   [-40, -50, (-60, -70)]]
  1. 对L浅拷贝绑定X
X = L.copy()
# 或
X = L[::]
# 或 其他...

绑定关系如下:

      L[0]  L[1]           L[2]
        |     |            |
        |     |            |
L = [-10,  (-20, -30),   [-40, -50, (-60, -70)]]
        |     |            |
        |     |            |
      X[0]  X[1]           X[2]     
        |     |            |
        |     |            |
X = [                                          ]
# 1)重新创建最外层可变数据容器,浅拷贝创建新的最外层对象
# 2)内部所有对象正常赋值绑定
# 绑定原始对象关系,3个数据对象同时被L[N]和L[X]绑定

最外层可变数据容器重新在内存中另一区域开辟创建, 而内一层所有数据对象, 都只是在原来的基础上简单赋值,因而改变最外层新拷贝对象对原始对象无影响,而改变拷贝后的数据对象如X[2],将会使L[2]也同时发生改变

  1. 对L深拷贝绑定Y
    可以简单理解为所有的数据对象全部在内存中拷贝一份,并重新对新数据对象指定绑定关系。
    因而,深拷贝后的数据无论如何操作都将不会影响到之前的数据对象,即改变拷贝后的数据对象如Y[2],不会影响L[2]同时发生改变
    (不过深拷贝的实际情况为了节省计算机资源,只是复制和重新绑定了新的可变数据对象,而其中不可变的数据对象依然保留,重新赋值绑定,这样节省内存资源的同时亦达到了完全拷贝的同样效果。)

练习

练习1:

import copy  # 导入复制模块
L = [3.1, 3.2]
L1 = [1, 2, L]
L2 = copy.deepcopy(L1)  # 实现深拷贝
L[0] = 3.14
print(L1)  # _________
print(L2)  # _________

# [1, 2, [3.14, 3.2]] #此列表由于浅拷贝受影响
# [1, 2, [3.1, 3.2]] #此列表不受影响

练习2:

import copy

a = [1, 2, 3, 4, ['a', 'b']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)
a[4].append('c')

print(a)  # _________
print(b)  # _________
print(c)  # _________
print(d)  # _________

# [1, 2, 3, 4, ['a', 'b', 'c'], 5]
# [1, 2, 3, 4, ['a', 'b', 'c'], 5]
# [1, 2, 3, 4, ['a', 'b', 'c']]
# [1, 2, 3, 4, ['a', 'b']]
"""解析: 
> a: a加了5,a[4]加了'c'  
> b: 赋值绑定操作,与a完全一致  
> c: 浅拷贝了最外层可变对象,最外层不受影响,但内部可变对象受影响  
> d: 深拷贝,所有可变对象不受影响"""