Python基本类型详解与应用场景(4)-- 列表

背景

对于很多 Python 初学者来说,搞懂我们每个数据的结构与作用是一件非常困难的事。

因此了解我们程序中各个基本类型的结构,表现方式,使用方法与应用是一件非常有必要的事。本文致力通过简单明了的语言来阐述基本类型列表的相关信息

介绍

在官方介绍中如此说道:列表由一系列按【特定顺序】排列的元素组成。非常抽象,咋一看很难理解到底是个什么东西。

我们可以肤浅的将列表理解为一个无限长,无限宽的抽屉,它可以装任何我们想要装的元素(自动扩容),并且所有装进去的元素都是根据装进去时间先后排序的,我们也可以手动整理抽屉,按照我们想要的排列规则排序(排序),抽屉内的东西可以是重复的(元素可重复)可以放另一个抽屉(可嵌套列表)可以随时取出,放入,替换(可新增删除更新

列表是可迭代的类型,在 python 中我们通常用列表的英文list来表示

列表的表现形式

在 python 中列表用方括号[]包裹元素,元素之间用英文标点符号逗号,分割,其中元素可以是任意类型的数据。例如:字符串,数字,列表,布尔值等。如下方代码所示

l = ["hello", 1, 12, False, [21, "world", True]]
print(type(l))

# 输出:
# <class 'list'>

列表的作用

在程序中,我们所要处理的任务往往不是单纯的某一项任务,而是一个批量的过程。列表可以将大量的任务装在其中,通过循环我们可以取出其中的数据,并且使用相同的代码结构来处理任务。对于简化我们的代码量,减轻我们的工作有着巨大的帮助。列表是我们最常用的数据结构之一

列表访问

上面说过,列表是可迭代的类型,因此我们可以通过索引和切片(在字符串一节我们讲过)来获取其内部元素,如下方代码所示

l = ["hello", 1, 12, False, [21, "world", True]]
print(l[0])
print(l[-1])
print(l[1:4])

# 输出:
# hello
# [21, "world", True]
# [1, 12, False]

上面可以看到,在我们获取-1索引的元素时,获取到的是一整个列表作为元素输出了,这是因为列表本身也可以作为元素存在于其它列表中。那如果我们想要获取列表内部列表中的值应该怎么办?如下方代码所示

l = ["hello", 1, 12, False, [21, "world", True]]
print(l[-1][0])

# 输出:
# 21

使用

列表作为一个抽象的抽屉,我们需要了解如何往里面放东西,取东西,替换东西和查看东西(增,删,改,查

往列表中新增元素

新增单个元素

列表提供了内置函数list.append(obj)来作为新增元素的入口,其中list代表一个列表,obj可以是任意对象。如下方代码所示

l = ["a", "b"]
print(l)

# 输出:
# ['a', 'b']

l.append("c")
print(l)

# 输出:
# ['a', 'b', 'c']

可以看到,我们定义了一个初始列表["a", "b"]并将其赋值给了变量l。因此,变量l则代表一个列表["a", "b"]。上面我们讲过,通过list.append(obj)来往列表中新增元素,因此变量l作为列表,替换list,再其后跟.append(obj)即可实现对列表l内部元素的新增。

新增多个元素

在实际运用中,我们面对的可能不仅仅是只新增一个元素这么简单,可能需要一次性新增多个元素。如果每新增一个元素就写一行list.append(obj)这样的代码,会显得非常臃肿累赘。

因此列表提供了内置函数list.extend(interable)来作为新增多个元素的入口,其中list代表一个列表,interable代表一个可迭代对象(字符串,列表,字典,元祖,集合都是python中内置的可迭代对象)。如下方代码所示

l = ["a", "b"]
print(l)

# 输出:
# ['a', 'b']

# 使用列表
l.extend(["c", "d"])
print(l)

# 输出:
# ['a', 'b', 'c', 'd']

# 使用字符串
l.extend("你好")
print(l)

# 输出:
# ['a', 'b', 'c', 'd', "你", "好"]

在指定位置插入元素

有些时候我们想让插入的元素不要每次都放在列表的最后面,列表同样提供了内置函数list.insert(index, obj)来作为插入元素的入口。其中index代表插入的索引位置,obj代表任何对象。如下方代码所示

l = ["a", "b"]
print(l)

# 输出:
# ['a', 'b']

l.insert(1, "f")
print(l)

# 输出:
# ['a', 'f', 'b']

根据索引删除列表中的元素

列表提供了内置函数list.pop([index])来作为删除元素的入口,其中index代表索引位置,方括号代表index是可选参数,列表会根据我们提供的索引来删除指定索引位置的元素并返回删除的值,若不传index,则会默认删除最后一个元素。如下方代码所示

l = ["a", "b", "c", "d"]
print(l)

# 输出:
# ['a', 'b', 'c', 'd']

print(l.pop(1))
print(l)

# 输出:
# "b"
# ['a', 'c', 'd']

print(l.pop())
print(l)

# 输出:
# "d"
# ['a', 'c']

python同样还有del关键字,可以支持我们删除列表指定索引的内容。如下方代码所示

l = ["a", "b", "c", "d"]
print(l)

# 输出:
# ['a', 'b', 'c', 'd']

del l[1]
print(l)

# 输出:
# ['a', 'c', 'd']

删除指定元素

除了根据索引来删除对应的元素之外,python还提供了内置函数list.remove(obj)来删除我们指定对象的元素,obj指列表中实际存在的值。如下方代码所示

l = ["a", "b", "b", "c", "d"]
print(l)

# 输出:
# ['a', 'b', 'b', 'c', 'd']

l.remove("b")
print(l)

# 输出:
# ['a', 'b', 'c', 'd']

可以看到,我们可以直接指定一个值来删除对应的元素,不需要知道其所在位置。但是值得注意:list.remove(obj)只会删除列表中满足条件的第一个元素

获取列表中元素

我们知道print(obj)是打印对象的值,如果对列表使用,则会打印出列表所拥有的所有值。如果想要打印某一个元素的值也很简单,只需要在列表后面加索引即可。我们在介绍-列表的访问中已经讲过,此处不再赘述

更改列表中元素

有些时候我们需要对列表中的元素进行修改替换,列表同样提供了对应的操作方式来支持我们进行修改。如下代码所示

l = ["a", "b", "c", "d"]
print(l)

# 输出:
# ['a', 'b', 'c', 'd']

l[1] = "f"
print(l)

# 输出:
# ['a', 'f', 'c', 'd']

获取元素的索引

列表中元素的索引是非常有用的值,我们可以通过它来查询,插入,删除等等各种操作,因此获取指定元素所在列表的索引也非常有必要。列表为我们提供了list.index(obj)来作为获取索引的入口。如下方代码所示

l = ["a", "b", "c", "d"]
print(l.index("b"))

# 输出:
# 1

列表的运算符

同字符串和数字,列表也可以通过简单的运算符来实现一些功能,如下表所示

Python 运算符 示例 描述
+ >>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
组合列表
* >>> [‘Hi!’] * 4
[‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’]
重复列表元素
in >>> “a” in [“a”, “b”, “c”]
True
判断给定元素是否存在列表

获取列表的长度

python提供内置函数len(obj)来获取对象所拥有的长度信息。如下方代码所示

l = ["a", "b", "c", "d"]
print(len(l))

# 输出:
# 4

列表的排序

列表提供内置函数list.sort(key=None, reverse=False)作为排序的入口,其中keyreverse都有默认值为NoneFalsekey用来指定排序所使用的值,默认是每一个元素。reverse用来控制升序排列还是降序排列,默认False代表升序排列。注:用来排序的列表,其中的元素key必须是可以进行大小比较的。如下方代码所示

# 普通排序
l = ["d", "c", "f", "a"]
l.sort()
print(l)

# 输出:
# ['a', 'c', 'd', 'f']

# 降序排序
l = ["d", "c", "f", "a"]
l.sort(reverse=True)
print(l)

# 输出:
# ['f', 'd', 'c', 'a']

自定义排序的方式

上面说过key可以用来指定排序所使用的值,我们想要让列表排序不是简单的升序排列或降序排列,就需要用到指定key值来自定义排序方式。如下方代码所示

# 正常的排序
l = [[3,3], [2, 1], [1, 2], [4, 4]]
l.sort()
print(l)

# 输出:
# [[1, 2], [2, 1], [3, 3], [4, 4]]


# 指定key自定义排序
def list_sort(x):
    return x[1]
    
l = [[3,3], [2, 1], [1, 2], [4, 4]]
l.sort(key=list_sort)
print(l)

# 输出:
# [[2, 1], [1, 2], [3, 3], [4, 4]]

可以看到我们定义了一个嵌套列表,每一个元素都是有包含两个元素的列表组成。我们如果直接使用sort()方法来排序,只会针对每个列表中第一个元素的大小进行排序,但是key可以指定一个函数将列表中每一个元素作为参数传递到函数中。

代码中list_sort就是我们定义的函数,x是我们指定的形参,它接收列表l中的元素作为实参并返回其索引为1的值用于排序。用更详细的代码输出来更容易理解

def list_sort(x):
    print("接受的每个参数x的值是:", x)
    return x[1]
    
l = [[3,3], [2, 1], [1, 2], [4, 4]]
l.sort(key=list_sort)
print(l)

# 输出:
# 接受的每个参数x的值是: [3, 3]
# 接受的每个参数x的值是: [2, 1]
# 接受的每个参数x的值是: [1, 2]
# 接受的每个参数x的值是: [4, 4]
# [[2, 1], [1, 2], [3, 3], [4, 4]]

获取列表的最大值和最小值

通过python的内置方法max(obj)min(obj)可以获取所有实现了__max__属性和__min__属性的对象的最大值和最小值。而列表正好支持这种用法,如下方代码所示

l = [1, 2, 3, 4]
print(max(l))
print(min(l))

# 输出:
# 4
# 1

获取指定元素在列表中存在多少个

列表提供内置函数list.count(obj)来作为获取列表中指定元素存在个数的入口,如下方代码所示

l = [1, 1, 1, 2, 2, 3]
print(l.count(1))
print(l.count(2))
print(l.count(3))
print(l.count(4))

# 输出:
# 3
# 2
# 1
# 0

可以看到通过count()方法可以获取元素存在列表中的个数,如果不存在则返回0

深拷贝与浅拷贝

特别注意:在python中列表存在一个新手非常容易犯的错误:深拷贝与浅拷贝

我们来看下面一段代码,大家可以猜测一下它会输出什么

l1 = [1, 2, 3, 4]
l2 = l1

l2.append(5)
print(l1)
print(l2)

也许大家会觉得会输出

[1, 2, 3, 4]
[1, 2, 3, 4, 5]

但是实际上却是输出的

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

也就是说,我们改变l2的值的时候同时改变了l1的值。在实际生产过程中,如果我们不注意到这个问题,很容易导致各种古怪的bug

原理

在python中,一切的事物都是对象,它们都由python分配的内存地址储存,并通过指针指向对应的地址来确保我们能够获取到它。浅拷贝会导致两个对象的指针指向了同一个内存地址,当我们改动这个地址上的内容时,两个对象的值都会发生同样的变更。

听起来有点抽象,我们可以理解每个对象都是一个活生生的人,我们使用身份证来标识每个人的身份,只要记录了每个人的身份证号,就可以通过身份证号来找到对应的人。而在列表这里,浅拷贝,也就是上方代码通过简单地等于符号赋值,相当于自己的身份证被另一个人盗用,这个时候它们两人共用一个身份证号。当另一个人使用这个身份证号干了任何事,都会被记录。身份证的原主人也会因此背上不属于自己的信息变更。

通过等于符号=将一个对象的值赋值给另一个变量实际上都是共享内存地址的引用

在python中,内置函数id(obj)是用于获取对象的内存地址的方法,我们可以实际输出内存地址看看这些有趣的变化

l1 = [1, 2, 3, 4]
l2 = l1
print(id(l1))
print(id(l2))

# 输出:
# 2671302358080
# 2671302358080

a = 123
b = a
print(id(a))
print(id(b))
b = 321
print(b)
print(a)

# 输出:
# 140726032479856
# 140726032479856
# 321
# 123

可以看到这里,列表通过等于符号赋值,两个变量内存地址变为一致了,同样数字类型的变量也出现了同样的现象,表明大家都会出现浅拷贝的情况。

可是为什么后面的数字变量b修改了值,却没有令变量a的值跟着发生修改呢?

原因在于字符串,数字,布尔值等变量修改值都是通过等于符号来修改值的,实际上是在内存中新创建了一个地址,并将该地址通过指针指向对应的变量。这个时候变量a和变量b就不再共用一个内存地址了,因此它们之间的值变化并不会互相影响。

而列表新增删除等操作会导致两个变量的值都发生一致改变的原因在于,当我们新增和删除元素时,都是对于该内存地址上的内容进行修改,而非新建一块内存进行赋值

解决方案

既然我们知道列表的赋值会出现这样的问题,那么我们应该如何创建一个和原列表保持一致的内容但是互不干扰的新列表呢?列表内置的函数list.copy()为我们提供解决方案,它会生成一个内容一模一样但是互不干扰的新列表给我们,如下方代码所示

l1 = [1, 2, 3, 4]
l2 = l1.copy()
print(id(l1))
print(id(l2))

# 输出:
# 2671302443968
# 2671302456512

l2.append(5)
print(l1)
print(l2)

# 输出:
# [1, 2, 3, 4]
# [1, 2, 3, 4, 5]

猜你喜欢

转载自blog.csdn.net/a914541185/article/details/124677206