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

简洁胜于复杂

posted @ 2023-02-18 20:07  努力生活的小林  阅读(269)  评论(0)    收藏  举报