Python 列表
列表(一)
1 什么是列表?
python 中的列表可以存放几乎所有不同类型的数据。——菜篮子
相比于其他语言中的一个列表只能存放一种相同类型的数据,此时存放多种不同类型的数据时,就需要很多个列表。——背心袋
2 创建列表
使用中括号[],将所有准备放入列表中的元素给包裹起来,不同元素之间使用逗号,分隔。
兼收并蓄是列表的一个特色,可以容纳不同类型的数据。
创建完列表后应当在左值赋给一个变量,否则创建的就是匿名列表,下次是无法再次访问的。
[1, 2, 3, "hello", [1, 2]]
# 在Python Shell中使用 alt + P 复制上行的代码
myList = [1, 2, 3, "hello", [1, 2]]
列表是一种具体的数据类型,它和字符串一样,属于“序列”这种数据结构。
序列中的元素是可以按照顺序访问其中存放的元素的,此时for循环再适合不过了。
myList = [1, 2, 3, "hello", [1, 2]]
for each in myList:
print(each)
"""
1
2
3
hello
[1, 2]
"""
3 访问列表中的元素
1 访问单个元素
下标索引的办法,假设列表中共有n个元素
正向访问,从左到右 0,1,2,...,n-1
反向访问,从右到左 -1,-2,...,-n
myList = [1, 2, 3, "hello", [1, 2]]
length = len(myList)
print(myList[length -1]) # [1, 2]
print(myList[-1]) # [1, 2]
2 访问多个元素
列表切片:将原先单个索引值的表示方式换成范围表示就行
myList = [1, 2, 3, "hello", [1, 2]]
# 至死方休
print(myList[3:]) # ['hello', [1, 2]]
# 从头开始
print(myList[:3]) # [1, 2, 3]
# 全都要
print(myList[:]) # [1, 2, 3, 'hello', [1, 2]]
3 跳跃访问
[选填:选填:列表取元素的跨度值]
myList = [1, 2, 3, "hello", [1, 2]]
# 所有元素,从头开始,以2为周期取元素
myList[::2] # [1, 3, [1, 2]]
# 倒序输出
myList[::-2] # [[1, 2], 3, 1]
myList[::-1] # [[1, 2], 'hello', 3, 2, 1]
# 设置起始,注意:最后一个下标取不到!
myList[1:5:2] # [2, 'hello']
myList[1:3:2] # [2]
列表(二)
列表中的诸多方法:增 删 改 查
1 添加元素
列表可以随意向其中添加数据:
append()方法
在列表的末尾添加一个指定的元素
myList = ['one', 'two']
myList.append('three')
print(myList) # ['one', 'two', 'three']
extend()方法
当需要向列表中一次性添加多个元素时使用
允许直接添加一个可迭代对象,比如说列表就是一个可迭代对象
myList = ['one', 'two', 'three']
myList.extend(['one', 'two'])
print(myList) # ['one', 'two', 'three', 'one', 'two']
注意:extend()方法的参数必须时可迭代对象,新的内容是追加到源列表的末尾。
可以使用列表的切片实现上述的2种添加元素的方法,这时添加多少元素取决需求,只添加一个就和append()方法的效果一样;添加多个就和extend()方法的效果一样。
s = [1, 2, 3]
s[len(s):] = [4, 5] # 注意这里是以列表的形式添加,因为can only assign an iterable
print(s) # [1, 2, 3, 4, 5]
insert()方法
在列表中的任意位置添加数据,只有两个参数,所以插入的要不是单个元素,要不就是元素的集合作为一个整体数据类型插入进去
该方法有两个参数:一个指定插入位置,另一个指定待插入内容
s = [1, 3, 4]
s.insert(1, 2)
# s.insert(0, 0) ————>插在开头
# s.insert(len(s), 5) ————>插在末尾
# s.insert(1, [1, 2]) ——> [1, [1, 2], 3, 4]
print(s) # [1, 2, 3, 4]
2 删除元素
remove()方法
删除指定的元素,注意两点:
①列表中存在多个匹配的元素时,只删除下标最小的一个
②如果指定的元素不存在,则报错
s = [1, 2, 3, 4]
s.remove(4)
print(4) # [1, 2, 3]
pop()方法
删除指定位置的元素
s = [1, 2, 3, 6, 7]
s.pop(2) # 3
print(s) # [1, 2, 6, 7]
clear()方法
一次性清空列表
s = [1, 2, 3]
s.clear()
print(s) # []
列表(三)
1 更改元素
列表是可变的,字符串是不可变的
替换列表中的元素,与访问列表类似,都是使用的下标索引法,再使用赋值运算符就可以将新的元素替换进去了。
s = [1, 2, 3]
s[0] = 6
print(s) # [6, 2, 3]
当需要替换连续的多个值时,使用切片实现,此时从指定的位置为起始位置开始进行替换。
s = [1, 2, 3, 4, 5, 6]
s[3:] = [7, 8]
s # [1, 2, 3, 7, 8]
替换的过程分为两步执行:
①将赋值符号左值的内容删除
②将赋值符号右值的可迭代对象中的片段插入左值被删除的位置
直接调用列表的sort()方法就可以实现全是数字元素的列表中数字的顺序排序
s = [1, 4, 2, 6, 3]
s.sort()
s # [1, 2, 3, 4, 6]
s[::-1] # [6, 4, 3, 2, 1]
# 或者直接使用reverse()方法实现倒序排序
s.reverse()
# 在sort()方法中指定参数,也可以实现
s.sort(reverse=True)
2 查找元素
count()方法
括号中是待查找的元素,返回值是列表中待查找元素的个数
s = [1, 1, 1, 2, 3]
s.count(1) # 3
index()方法
查找指定元素的索引值,就是这个元素在列表中的什么位置。填写的参数是待查找元素,返回值是待查表元素的索引值。
如果列表中有多个待查找的元素,则返回索引的最小值。
s = [2, 5, 7]
s.index(5) # 1
s.append(2)
s.index(2) # 0
所以,当不知道待替换元素的索引值是多少时,先通过index()获取索引值,然后再赋值,替换掉其中的元素,具体就是这样进行:
s = ["你", "好", "吖"]
s[s.index("吖")] = "么"
s # ['你', '好', '么']
index()方法还有两个可选参数index(x, start, end)就可以指定查找的开始和结束位置,但是返回的仍然是这个范围内出现的第一个元素的下标索引值
copy()方法
用于对一个列表进行拷贝
s1 = ['你', '好', '么']
s2 = s1.copy()
s2 # ['你', '好', '么']
copy的功能同样可以实现切片的方法来实现:等于就是将整个列表中的元素全部取出,然后使用赋值运算。
因此掌握列表的访问很重要,这样就可以灵活的使用列表的切片功能。此时切片后的结果赋给新的变量,就能拿出来展示了
s1 = ['你', '好', '么']
s2 = s1[:]
s2 # ['你', '好', '么']
上面使用copy()方法和使用切片的方法在python中均称之为“浅拷贝”——shallow copy
理解“深拷贝”的前提是理解嵌套列表,也就是多维列表。
列表(四)
1 列表的加法和乘法
与字符串一样,列表同样允许加法和乘法的运算。
列表的加法,其本质就是列表的拼接,所以要求加号两边均为列表。
s1 = [1, 2]
s2 = [3, 4]
s = s1 + s2
s # [1, 2, 3, 4]
s*2 # [1, 2, 3, 4, 1, 2, 3, 4]
列表的乘法,则是重复列表中的元素若干次
2 嵌套列表(nested list)
所谓嵌套列表就是在列表里面还有列表
# 这两种写法是等价的,结果都是二维列表
m = [[1,2],
[3],
[6,7]]
m = [[1,2],[3],[6,7]]
# [[1, 2], [3], [6, 7]]
3 访问嵌套列表(access nested list)
访问嵌套列表中的元素可以使用循环来实现,迭代一层列表就要使用一层循环,所以迭代嵌套列表时,也就应当使用嵌套循环。根据列表的嵌套的次数或者说是列表的维数来确定循环的层数。
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
for each in matrix:
for i in each:
print(i, end = ' ')
print()
"""
内层循环以空格分开,外层循环则换行分开
1 2 3
4 5 6
7 8 9
"""
同样使用下标也是可以访问嵌套列表的:
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
matrix[0] # [1, 2, 3],先索引行
# 下标索引自左向右是由外向内层进行访问
matrix[0][0] # 1,再索引列
上面的方法是通过每个元素的值来创建二维列表,下面展示使用循环的方式来创建并初始化二维列表,循环中可以改变嵌套列表的形式,初始化的目的就是限定它是一个二维的列表:
# 注意首先应当初始化一个一维列表
# 再使用循环
A = [1]*3
for i in range(3):
A[i] = [1]*2
A # [[1, 1], [1, 1], [1, 1]]
注意:下面这样写也可以创建一个类似的二维列表
B = [[0] * 3] * 3
B # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
该方法只是对同一个列表的三次引用,并不是3个相互独立的列表。
# 但是当你修改这两者创建的列表时
A[1][1] = 1
A # [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
B[1][1] = 1
B # [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
所以说B的创建方式是错误的,是"Bug"。
解释这种现象出现的原因,要介绍运算符 is
4 is operator
is运算符 也称为 同一性运算符,它用于检查两个不同的变量是否指向同一个对象
x = "lin"
y = "lin"
x == y # True
x is y # True
a = [1,2,3]
b = [1,2,3]
a == b # True
a is b # False
这是因为Python对于不同对象的存储机制是不一样的,因为字符串是不可变的,所以只需要在内存中开辟一个内存空间来存放就行。
如果有多个变量名指向同一个字符串,则不同变量之间的关系是这样的:

相比于字符串而言,列表是可变的,用户时不时地会对列表做出改变,那么如果不同的变量名指向同一个内存空间,假如说a,b都指向同一个内存空间,那么我需要修改a时,b也发生了变化,但是b是不应当或者说不需要改变的,这样一来就乱套了。所以可变对象是存放在不同的内存空间,即使它们一开始的初始值是相同的。

所以使用 is 来判断指向的内存空间地址是否相同,有点类似指针的作用。
A[0] is A[1] # False
A[1] is A[2] # False
B[0] is B[1] # True
B[1] is B[2] # True
所以A、B的内存空间布局是这样的:

B的问题就是它企图通过乘号对一个嵌套列表进行拷贝,但是它拷贝的只是对同一个列表的引用,也就是指向了它的地址,并没有开辟新的内存空间。
也就是说它不是拷贝(拷贝的话会开辟出新的内存空间,来存放拷贝的值),而是重复引用(反复指向这个内存空间,取用其中的值)。
所以说乘法只是拷贝了它的引用,不开辟新的内存空间来进行物理上的拷贝。
列表(五)
1 赋值和copy()的区别
在python中变量不是一个盒子,只是一个标签,通过变量名去访问相应的数据。
x = [1, 2, 3]
y = x
执行上述代码时,内存中发生的事情:
当赋值运算发生的时候,python并不是将数据放到变量里去,而只是将变量与数据进行挂钩,这种行为也就是“引用”。

也就是说,将一个变量赋值给另一个变量的时候,只是将这个变量的引用传递给另一个变量,说白了,就是传递了地址值,是一种引用传递,并不是值传递。
所以,此时修改x中的值时,再去访问y,会发现y的值也发生了一样的变化。
因此想要得到两个相互独立的列表,需要使用拷贝copy()来完成,开辟一个新的内存空间,而不是简单的使用赋值运算符,想想也是,如果说每一次赋值运算都是去开辟新的物理内存,那么显然是不划算的,所以赋值运算只是单纯的进行引用的传递,赋值的只是引用,也就是地址值,不会单独开辟新的空间来存放。
2 深拷贝和浅拷贝(shallow and deep copy)
列表的浅拷贝就是单纯的调用列表的copy()方法或者使用切片的语法灵活实现,但是深拷贝则不然:

copy()方法拷贝的是完整的列表对象,不仅仅是变量的引用,同样使用切片的方法也可以实现拷贝的效果,二者均为浅拷贝。处理一维列表是可以的,但是当涉及到嵌套列表时,则无能为力。

这是因为浅拷贝只拷贝外层对象,如果包含嵌套对象时,则此时拷贝的只是它的引用。
解决这个嵌套对象拷贝的问题需要使用到深拷贝,其中主要使用的是copy模块,这个模块有两个函数。一个是copy函数实现的是浅拷贝,当然使用该模块之前应当先导入进来。
copy模块的另一个函数是deepcopy函数,实现的就是深拷贝。这个过程中,会在对元对象拷贝的同时,也将对象中所有引用的子对象也一并拷贝。
import copy
x = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
y = copy.copy(x) # 第一个copy是copy模块的意思,第二个copy是copy函数的意思,x是待拷贝的参数
z = copy.deepcopy(x)

所以说不嵌套的时候,用浅拷贝就ok,但是存在嵌套情况时,请使用深拷贝,否则会出现单纯复制引用的情况,并不会重新开辟内存空间。
但是深拷贝的执行效率相比于浅拷贝是较低的,所以说,python默认的是使用浅拷贝的。
注意,在不涉及类和对象时,可以认为函数和方法是一回事。
实现浅拷贝时,使用copy()方法或者切片都行,无所谓。
列表(六)
列表推导式(list comprehension expression)
Q:如何将列表中的元素的值都变为原来的2倍?
A:使用列表的下标索引,循环遍历列表中的每一个元素,然后乘2对每一值进行更新。
A = [1, 2, 3]
for i in range(len(A)):
A[i] *= 2
A # [2, 4, 6]
B = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
for each in range(len(B)):
for i in range(len(B[0])):
B[each][i] *= 2
B # [[2, 4, 6], [8, 10, 12], [14, 16, 18]]
但是在python中有一种更为简洁的方法实现该要求——列表推导式:列表推导式的效率比循环语句快一倍左右,是因为列表推导式在python解释器中使用更快的C语言来运行的,要比使用python的脚本虚拟机pvm以步进的形式来运行for循环要快很多。
A = [1, 2, 3]
A = [i*2 for i in A]
A # [2, 4, 6]
列表推导式的一维构建
基本语法:[expression for target in iterable]
首先列表推导式的结果一定是一个列表,因此要先写上[];由于列表推导式的结果是使用一组数据来填充这个列表,因此需要使用for语句来搭配;最后在for语句的左侧有一个表达式,相当于循环体,经过运算后决定放入列表中的数据。
x = [i*3 for i in range(10)]
# [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
# for语句通过迭代获得了0-9十个整数,每一次迭代就会将数值存放到一个叫i的变量中,但是这个变量中存放的这个数值是什么,取决的是for左侧的这个表达式
# 也即是说for右侧负责开辟存储空间,获取一定数量的变量名(实际上就是物理内存),for左侧则是负责返给物理内存中每个位置的具体值,即是每个变量的值
y = [i*2 for i in "lin"]
y # ['ll', 'ii', 'nn']
# 将每一个字符都转化为对应的Unicode编码并保存为列表,调用ord内置函数(将单个字符串转化为对应的Unicode编码)
code = [ord(c) for c in "abc"]
code #[97, 98, 99]
列表推导式的多维构建
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
col2 = [row[1] for row in matrix]
col2 # [2, 5, 8]
# for row in matrix 获取出矩阵的每一行
diag = [matrix[i][i] for i in range(len(matrix))]
diag # [1, 5, 9]
循环是遍历原先的列表,然后修改其中的值;而列表推导式由于开辟了新的内存空间,因此它是创建了一个新的列表,并不是在原先列表的基础上进行更改的。
也就是有新的需求的需求时,有的人想着修,有的人想着丢。
for循环是修改,列表推导式则是直接更换。
但是从最后的实现效果上来说,列表推导式基本上可以满足我们的需求;这也一定程度上可以说明为什么列表推导式的速度要比for循环速度执行速度要快。
一个是直接开辟空间然后往里写,另一个则是先删除原先的内存空间中的值再去写。
直接买一个的速度大抵是要比修复的速度是要快的(狗头)。
列表(七)
再谈列表推导式
# 1 一开始创建二维列表时,此时是对同一个列表的多次引用,不是开辟了对立的列表,因此改变其中一个元素的值时,有些元素也会同时改变,因为是相同的引用
A = [[0] * 3] * 3
A[1][1] = 1
A # [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
# 2 进而使用循环表示二维列表
B = [0] * 3
B[0] # 0
B[0] = [0] * 3
B[0] # [0, 0, 0]
for i in range(len(B)):
B[i] = [0] * 3
B # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
B[1][1] = 1
B # [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
# 3 使用列表推导式
S = [[0]*3 for i in range(3)]
# [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
S[1][1] = 1
S # [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
筛选功能
列表推导式中可以添加一个用于筛选的if分句
[expression for target in iterable if condition]
even = [i + 1 for i in range(10) if i % 2 == 0] # [1, 3, 5, 7, 9]
执行顺序:
① for语句 开辟空间
② if语句 筛选空间
③ for左侧的表达式 expression,向空间中填入值
# 筛选出列表中以'h'开头的单词
words = ["hello", "world", "hi"]
h_words = [i for i in words if i[0] == 'h']
h_words # ['hello', 'hi']
嵌套功能
# 从上往下执行,上面的是外层,下面的是内层,从外到里,一步步地打开,道理是嵌套循环一样的
[expression for target1 in iterable1
for target2 in iterable2
...
for targetN in iterableN]
# 将二维列表降级为一维列表
# 先定义一个二维列表
matrix = [[1, 2, 3],[4, 5, 6],[7, 8, 9]] # 横竖写都一样,在内存中就是按照横向存储的
flatten = [col for row in matrix for col in row]
flatten # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 用嵌套循环实现一样的功能
# 先建立一个空列表
matrix = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
flatten = []
for row in matrix:
for col in row:
flatten.append(col)
flatten # [1, 2, 3, 4, 5, 6, 7, 8, 9]
[x+y for x in "LIN" for y in "hi"] # ['Lh', 'Li', 'Ih', 'Ii', 'Nh', 'Ni']
# 当某个变量是临时的,或者无关紧要时,可以使用下划线 _ 作为变量名
_ = [x * y for x in [1,2] for y in [3,4]]
_ # [3, 4, 6, 8]
# 实现向量的点积运算
x = [1,2]
y = [3,4]
pro=0
# 注意:in后面的必须是可迭代对象
for i in range(len(x)):
for j in range(len(y)):
if i==j:
pro += x[i] * y[j]
pro # 11
终极语法(条件筛选+循环嵌套)
[expression for target1 in iterable1 if condition1
for target2 in iterable2 if condition2
...
for targetN in iterableN if conditionN]
使用条件嵌套循环,可以实现一样的功能。

程序设计原则——KISS
KISS——Keep It Simple & Stupid
简洁胜于复杂

浙公网安备 33010602011771号