9. 数据类型 - 列表详解

更新 發佈閱讀 47 分鐘

Hi,大家好。我是茶桁。

raw-image

最近几节课,我们都是在详细讲解Python内的数据类型,上一节课我们详细了解了字符串,这节课,让我们来详解一下列表。

首先,我们先有一个大的概念,列表,其实就是一组有序的数据组合;另外,列表中的数据是可以被修改的。也就是说,列表是一个可变序列类型

列表定义

如何在Python的定义列表,记住以下几点就可以了:

  • 可以使用中括号进行定义[]
  • 可以使用list()函数定义
  • 还可以使用列表推导式定义: [x for x in iterable]
  • 在定义列表中的元素时,需要在每个元素之间使用逗号(英文逗号),进行分隔。[1, 2, 3]
  • 列表中的元素可以是任意类型,通常用于存放同类项目的集合。

列表的基本操作

让我们先来定义一个列表:

items = [1, 2, 3, 4]
items2 = list('1234')

print(items,'\n', items2)
---
[1, 2, 3, 4]
['1', '2', '4', '5']

我们使用了最基本的两个方式来定义列表。至于列表推导式, 先不用着急,我们后面会单独讲它。

我们可以看到,刚才我刻意将itemitems两个列表定义了不同种类的元素,那他们到底能否拼接在一起?我们尝试一下列表的相加:

print(items + items2)

---
[1, 2, 3, 4, '1', '2', '3', '4']

没问题,两种不同类型的元素拼接到了一起,组成了一个新的列表。

让我们将这段代码搞的复杂一点,新的列表对于我要的模拟数据来说太少了,我想再增加5倍的长度:

print((items + items2) * 5)

---
[1, 2, 3, 4, '1', '2', '3', '4', 1, 2, 3, 4, '1', '2', '3', '4', 1, 2, 3, 4, '1', '2', '3', '4', 1, 2, 3, 4, '1', '2', '3', '4', 1, 2, 3, 4, '1', '2', '3', '4']

没毛病,也就是说,我将小学学到的基本数学运算用到这里完全适用。

那如果用到减法呢,虽然难以想象最后的结果,试试中可以:

print(items - items2)

---
TypeError: unsupported operand type(s) for -: 'list' and 'list'

果然是我想多了,完全不支持操作数类型。

那是不是关于列表的操作也就到此为止了?并不是,列表除了利用加和乘进行拼接和循环的操作之外,还有很多其他的基本操作,比如:

items[2] = 9
print(items, "\t",items[3])

---
[1, 2, 9, 4] 4

这里,我们利用了列表的下标操作修改了列表内的下标[2]的元素(第三个),并且将修改后的列表和列表内下标[3]的元素打印了出来。

有这样一种情况大家想过没有,这个列表呢,我并不知道有多长,但是我知道最后一个数字,现在我就想把最后一个数字取出来该怎么办?用len()获取长度之后再-1? 是不是太麻烦了?

还记得之前咱们讲过,下标是可以从后往前数的吗?

items[-1]

---
4

嗯,我想再这个列表添加几个数字:

items[4] = 10

---
IndexError: list assignment index out of range

哎,我似乎想的并不对。本以为原列表下标[3]是最后一个元素,那我多加一个下标就会再多加一个元素,可是似乎并不行。那么我们该怎么在列表内最佳元素呢?

可以尝试一下专门添加元素的append()函数:

items = [1, 2, 3, 4]
items.append(2)
print(items)

---
[1, 2, 3, 4, 2]

加是加了,可是我们之前是想加10的,现在不小心加成2了,不行,我要删了它。该怎么办?随便吧,我就记得windows的CMD命令中的删除文件似乎是del,试试看:

del items[-1]
print(items)

---
[1, 2, 3, 4]

居然成了... 这就神奇了。看起来,Python并不是很难。不过我们这里不得不说,在Python中还有一个针对列表删除元素的方法:pop()

items = [1, 2, 3, 4]
items.pop()

---
[1, 2, 3]

pop([index=-1])函数专门用于移除列表中的一个元素,其中参数index为索引值,默认为-1,也就是说默认是从列表移除最后一个值。

# 将索引值改为从前数第一个
items = [1, 2, 3, 4]
items.pop(0)
print(items)

---
[2, 3, 4]

列表中的切片

在学习了列表的基本操作之后,我们来看看列表中的切片。提前说一声,在数据分析的应用中,对数据整理的过程绝大多数时候都需要用到列表的切片操作, 所以大家这部分要好好理解。

列表的切片操作基本语法其实很简单

list[开始值:结束值:步进值]

看起来很熟悉对吧?在我们之前介绍字符串相关的操作的时候,就是这种方式。其用法和字符串中也是如出一辙:

  • list[开始值:] 从开始值索引到列表的最后
  • list[:结束值]从列表最前面索引到结束值之前
  • list[开始值:结束值]按照给到的开始值开始索引,到结束值之前为止。

当然,除了这三个基本的操作之外还有list[::], list[::步进值], list[开始值::步进值], list[:结束值:步进值],list[开始值:结束值:步进值],我们下面一一的来看看,在字符串相关操作中没有特别理解的没关系,这里再来加深下印象:

# 先来定义一个列表方便后续操作
items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']

从开始值索引到最后:

print(items[2:])

---
['Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP']

从下标[2]开始,也就是从第三个Ruby开始向后索引。

从最前面索引到结束值之前:

print(items[:2])

---
['Python', 'Java']

现在我们让这两个语言单独露脸,算是对它们进行补偿了。

从开始值索引到结束值之前:

print(items[2:3])

---
['Ruby']

哎,为什么只索引出来一个值?因为结束值为3,它之前不就是2吗。开始值也是2,那可不就只有一个值而已。

这回,我们把步进值加上:

# 加上步进值
print(items[0:-1:2])

---
['Python', 'Ruby', 'C++', 'JavaScript']

从最前面索引到最后,步进值为2,所以是隔一个索引一个。那为什么PHP没索引到?估计你又忘了,是索引到结束值之前,不包含结束值,自然PHP就没被索引到。

只有步进值会是什么情况?

# 只有步进值
print(items[::-2])

---
['PHP', 'JavaScript', 'C++', 'Ruby', 'Python']

步进值为负数,那显然是从后向前索引了。隔一个索引一个,等等,为啥第一个Python被索引到了? 那是因为,当我们开始值和结束值都没取值的情况下,默认是从头到尾索引,现在嘛,应该是从尾到头索引。也就是包含了头尾,不存在最后一个值之前,所以列表内的所有值都索引了一个遍,只是因为有步进值的关系,所以变成隔一个索引一个。

再让我们将所有值都去掉,只留下[::]试试看:

# 删掉所有值试试
print(items[::])
print(items[:])

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP']
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP']

从结果上看,中括号内一个冒号和两个冒号出来的结果是一样的。

在索引查找之后,我们来看看,利用切片的方式是否可以对列表内的元素进行更新和删除?

从指定下标开始,到指定下标前结束,并替换为对应的数据(容器类型数据,会拆分成每个元素进行赋值)

items = [1, 2, 3, 4, 5]
items[2:4] = [7, 8]
print(items)

---
[1, 2, 7, 8, 5]

指定的切片内的元素被替换掉了。

刚才我们使用切片替换元素的时候元素是一一对应的,那如果我们没有对应的话会发生什么?

# 切片范围大于添加元素的个数
items = [1, 2, 3, 4, 5]
items[2:6] = [7]
print(items)

---
[1, 2, 7]

结果并没有报错,而是将切片范围内的元素都移除之后添加了一个元素7。我们再试试其他的:

# # 切片范围小于添加元素的个数
items = [1, 2, 3, 4, 5]
items[2:3] = [7, 8, 9, 0]
print(items)

---
[1, 2, 7, 8, 9, 0, 4, 5]

可以看到,比起原本的列表,我们的值增加了。原本下标[2]的元素被移除之后,在这个位置插入了[7,8,9,0]四个元素。

以此,我们可以总结切片更新列表,实际上就是删除掉切片范围内的元素,再在原来的位置上插入新加的元素,并且将之后的元素向后移动。

那既然这样的话,我们是不是可以利用这种特性对列表内的元素进行删除?

items = [1, 2, 3, 4, 5]
items[2:4] = []
print(items)

---
[1, 2, 5]

没毛病,确实可以这么用。

当然,除了这种插入空列表的方式之外,还有其他方式可以删除列表内的指定元素, 还记得前面我们介绍的del方法吗?

items = [1, 2, 3, 4, 5]
del items[2:4]
print(items)

---
[1, 2, 5]

那既然我们可以用添加空列表的方式来删除列表内的元素,del是不是就没用武之地了?实际上,并非如此。del有一个特殊的用法,就是在利用步进值来跳着删除元素:

items = [1, 2, 3, 4, 5]
del items[0:6:2]
print(items)

---
[2, 4]

那聪明的小伙伴肯定想,添加空列表的方式也加上步进值就不行吗?我们来试试:

items = [1, 2, 3, 4, 5]
items[0:5:2] = []
print(items)

---
ValueError: attempt to assign sequence of size 0 to extended slice of size 3

报错提示我们,序列分配不正确。说明我们不能这样使用。如果要这样使用的话,替换的元素个数必须对应才行:

items = [1, 2, 3, 4, 5]
items[0:4:2] = [9, 10]
print(items)

---
[9, 2, 10, 4, 5]

列表相关函数(✨ 重点)

除了以上介绍的关于列表的一些方法之外,Python还为我们提供了一些列表常用的相关函数:

len()

这个函数可以检测当前列表的长度,列表中元素的个数:

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
len(items)

---
9

count()

这个函数可以检测当前列表中指定元素出现的次数:

items.count('Python')

---
1

append()

这个函数前面我们已经介绍过了,就是向列表尾部追加新的元素,返回值为None。

items.append('SQL')
print(items)

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP', 'SQL']

insert()

这个函数可以向列表中指定的索引位置添加新的元素。

items.insert(4, 'Go')
print(items)

---
['Python', 'Java', 'Ruby', 'Rust', 'Go', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP', 'SQL']

pop()

还记得我们之前删除列表中元素的时候介绍pop()函数吗?其实,pop()函数是对指定索引位置上的元素做出栈操作,然后返回出栈的元素

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
print(items.pop())
print(items.pop(2))
print(items)

---
PHP
Ruby
['Python', 'Java', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node']

默认的情况下,pop()是把列表的最后一个元素出栈,当给值之后,是将指定索引的元素进行出栈。

remove()

这个函数是专门删除特定元素用的,可以指定列表中的元素进行删除,只删除第一个,如果没有找到,则会报错。

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
items.remove('PHP')
print(items)
items.remove('Go')

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node']
ValueError: list.remove(x): x not in list

可以看到,第一个remove成功删除了PHP,但是第二个remove并未在列表中找到Go,所以报错。

index()

这个函数可以查找指定元素在列表中第一次出现的索引位置

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
items.index('PHP')

---
8

除此之外,index()还能接收索引值,当输入索引值的时候,index()会在指定范围内查找元素的索引位置:

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
print(items.index('Ruby', 0, 5))
items.index('PHP', 0, 5)

---
2
ValueError: 'PHP' is not in list

可以看到,指定范围内没有说要查找的元素的时候就会报错,告知元素不在列表内。

extend()

这个函数接收一个容器类型的数据,把容器的元素追加到原列表中

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
a = ['Go', 'MATLAB']
items.extend(a)
print(items)

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP', 'Go', 'MATLAB']

看到这,是不是感觉很像两个列表相加?那既然我们可以将两个列表相加了,这个方法似乎有些多余了。

这么想的小伙伴们,我们再来看两段示例:

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
a = (1, 2, 3)
items.extend(a)
print(items)

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP', 1, 2, 3]

另外一段:

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
a = (1, 2, 3)
items = items + a
print(items)

---
TypeError: can only concatenate list (not "tuple") to list

可以看到,第二段代码直接报错了。那说明,相加这个操作必须两个都是列表才可以,不支持列表和元组相加。可是extend()方法是支持将任意一个容器类型的数据中的元素追加到原列表中的。

我们再来多看一段:

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
a = '1234'
items.extend(a)
print(items)

---
['Python', 'Java', 'Ruby', 'Rust', 'C++', 'Swift', 'JavaScript', 'Node', 'PHP', '1', '2', '3', '4']

a定义为一段字符串,一样可以使用extend()来接收并追加到原列表内。

clear()

这个函数比较简单,就是清空列表内容

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
items.clear()
print(items)

---
[]

reverse()

这个函数可以对列表进行翻转

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
items.reverse()
print(items)

---
['PHP', 'Node', 'JavaScript', 'Swift', 'C++', 'Rust', 'Ruby', 'Java', 'Python']

sort()

该函数将对列表进行排序, 在默认的情况下,会对元素进行从小到大的排序

items = ['Python','Java','Ruby','Rust','C++','Swift','JavaScript','Node','PHP']
items.sort()
print(items)

---
['C++', 'Java', 'JavaScript', 'Node', 'PHP', 'Python', 'Ruby', 'Rust', 'Swift']

额,这样似乎并不明显,我们重新换个案例。不过大家也可以想想现在这段代码中,为什么会有这样的结果。

items = [9, 3, 5, 2, 1, 7, 8, 0, 6]
items.sort()
print(items)

---
[0, 1, 2, 3, 5, 6, 7, 8, 9]

嗯,这回明显了。

除了从小到大排序外,我们还可以将其从大到小排序,利用关键参数reverse来开启:

items = [9, 3, 5, 2, 1, 7, 8, 0, 6]
items.sort(reverse=True)
print(items)

---
[9, 8, 7, 6, 5, 3, 2, 1, 0]

OK,现在让我们来回过头来解释一下第一段代码中的结果:['C++', 'Java', 'JavaScript', 'Node', 'PHP', 'Python', 'Ruby', 'Rust', 'Swift'], 之所以会产生这样的结果,不是因为它按英文字母来排序,当然这么想也对但是不全对,而是因为它的排序依据是ASCII码,之前我们学习过,ASC II码只包含了128个字符,仅仅是美国的标准,128个字符里面都是西文码,那么如果中间包含了中文会怎样呢?

不如我们直接来看看:

items = [9, 3, 5, 2, 1, 7, 8, 0, '茶', '桁']
items.sort(reverse=True)
print(items)

---
TypeError: '<' not supported between instances of 'int' and 'str'

完了,直接报错。不过这个似乎和编码无关,而是数据类型的问题,告诉我们字符和整型之间不能排序。别问我怎么看懂的,我也是查字典。

知道是数据类型的问题就好办了,我们将数据类型变成一致的再试试:

items = ['9', '3', '5', '2', '1', '7', '8', '0', '茶', '桁']
items.sort(reverse=True)
print(items)

---
['茶', '桁', '9', '8', '7', '5', '3', '2', '1', '0']

居然成功了,那既然是ASCII码,那为什么还会支持中文排序呢?还记得我们除了介绍ASCII码之外,还介绍过一个Unicode编码。那即是说,Python中的sort()排序的依据是Unicode编码。

当然,除了默认规则之外,我们还可以自己对排序进行干预,加上你想要的规则。sort(key)内的key参数可以接收一个函数,按照函数的处理结果进行排序:

items = [-5, -3, 5, 2, 0, -9, 12, 14, -1, -6]
items.sort(key=abs)
print(items)

---
[0, -1, 2, -3, -5, 5, -6, -9, 12, 14]

这一段是不是让小伙伴们想到之前我们在Python的内置函数中介绍高阶函数的内容?没错,就是一样的。所以,我们这次就不对函数内部排序过程进行分析了,有兴趣的小伙伴可以回去看看第七节的内容。

深拷贝与浅拷贝

接着,让我们来看看关于拷贝的问题,先说浅拷贝。

说到浅拷贝,实际上是仅拷贝了列表中的一维元素,如果列表中存在二维元素或容器,则为引用而不是拷贝。使用copy函数或者copy模块中的copy函数拷贝的都是浅拷贝。

items = [1, 2, 3]
res = items.copy()
print(items, '\t', res)

---
[1, 2, 3] [1, 2, 3]

copy()之后的新列表和原列表内容上是一样的。

接着让我们来操作一下copy之后的res

items = [1, 2, 3]
res = items.copy()
del res[2]
print(items)
print(res)

---
[1, 2, 3]
[1, 2]

可以看到,对res进行操作完全不影响原列表的内容。这就说明,copy产生的新列表和原列表并不是一个列表,我们可以验证一下看看:

print(id(items))
print(id(res))

---
4636359872
4636086464

当我们用id()函数的时候,可以看到他们是两个完全不同的id

刚才我们定义的items是一个一维列表,接着让我们再来定义一个多维列表来尝试一下:

items = [1, 2, 3, 4, ['a', 'b', 'c']]
res = items.copy()
del res[3]
print(items)
print(res)

---
[1, 2, 3, 4, ['a', 'b', 'c']]
[1, 2, 3, ['a', 'b', 'c']]

我们可以看到,做删除操作之后,res内容变了,但是原列表items没变化。似乎和之前的并没有什么不同,让我们再继续试试:

del res[3][1]
print(res)
print(items)

---
[1, 2, 3, ['a', 'c']]
[1, 2, 3, 4, ['a', 'c']]

发生了什么?我们明明是操作的res而不是原列表items, 为什么items也发生了变化?难道是id是相同的吗?来,试试就知道了。

print(id(items))
print(id(res))

---
4636427264
4636085824

似乎并不相同。那既然不是同一个元素,为什么我们操作res的时候,items也会跟着一起变化?

别着急,让我们接着看下面的操作:

print(id(items[4])) # items这个位置是列表['a', 'c']
print(id(res[3])) # res这个位置是列表['a', 'c']

---
4635245952
4635245952

如何,一模一样对吧?这就说明,在items以及它的copy列表res中,这个嵌套的列表是同一份。这也就能解释为什么我们对res内的嵌套列表进行操作的时候, items也发生了变化。

这个就是我们在一开始说到的,copy仅仅是拷贝了列表中的一维元素,对二维元素和容器仅仅是引用,这个应用对象当然还是原来那个对象。所以,两者的id才是是同一个。

浅拷贝我们理解完之后,才看看什么是深拷贝。

深拷贝和浅拷贝比起来就有深度的多,嗯,这么讲是因为深拷贝不仅仅是拷贝了当前的列表,同时还把列表中的多维元素或容易也拷贝了一份,而不是像浅拷贝一样仅仅是引用。完成深拷贝的函数是copy模块中的deepcopy函数。

items = [1, 2, 3, ['a', 'b', 'c']]
res = items.deepcopy()

---
AttributeError: 'list' object has no attribute 'deepcopy'

额,尴尬。居然报错了... 似乎deepcopy并不是和copy函数一样的用法。

细心的小伙伴应该之前就注意到了,在介绍copy函数和deepcopy函数的时候,我都在强调是copy模块中的这句话,确实,我们在使用deepcopy的时候,是需要先引用模块再使用的,并且,使用方式也有一些不同:

import copy
items = [1, 2, 3, ['a', 'b', 'c']]
res = copy.deepcopy(items)
print(res)

---
[1, 2, 3, ['a', 'b', 'c']]

没错,我们这就对items完成了深拷贝,生成了新的列表res

那到底是否是真的深拷贝呢?让我们试一试:

print(id(items))
print(id(res))

print(id(items[3]))
print(id(res[3]))

---
4636282048
4634799872
4636285120
4637491072

没问题,我们打印出来的id各不一样,包括items内的二维列表以及res内的二维列表,id也都不同,说明确实是深拷贝。

不放心的小伙伴,我们再来更改列表元素测试一下:

del res[3][0]

print(res[3])
print(items[3])

---
['b', 'c']
['a', 'b', 'c']

可以看到,当我们更改res内的二维列表时,items并未发生改变。说明二维列表我们也一样完成了拷贝,而不是像浅拷贝一样仅是引用了。

列表推导式

在本文最开始,我们介绍列表的时候提过三种列表生成方式,包括直接定义列表, 用list函数,最后一个就是列表推导式。那我们接下来,就要详细讲讲列表推导式。

列表推导式提供了一个更简单的创建列表的方法,常见的用法是把某种操作应用于序列或可迭代的每个元素上,然后使用其结果来创建列表,或者通过满足某些特定条件元素来创建子序列。

采用一种表达式的当时,对数据进行过滤或处理,并且把结果组成一个新的列表。

哎,最怕就是定义和文字过多,让我们直接上示例吧。

基本的列表推导式使用方式

结果变量 = [变量或变量的处理结果 for 变量 in 容器类型数据]

现在,假设我们想要创建一个平方列表:

# 使用普通方法完成
items = []
for i in range(10):
items.append(i**2)

print(items)

---
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这是用我们所学过的内容来进行创建,当然,我们还学过另外一种方式:

# 使用 map函数和list完成
items = list(map(lambda x: x**2, range(10)))
print(items)

---
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这里似乎有一点复杂,咱们还是来分析一下吧。

首先,我们创建了一个匿名函数lambda x:x**2, 再创建了一个可迭代对象range(10)

接着,我们给map函数传入了这两个参数,分别传给了func*iterables, 关于map函数,我们在第七节:内置函数中有讲解过,完了的小伙伴可以翻看前面复习一下。

map函数在对传入的可迭代数据中的每一个元素进行处理,然后返回一个新的迭代器, 最后用list函数将这个新的迭代器转换成了一个列表。

然后,我们将传入的func函数用一个匿名函数

没错,我们使用map函数和list也可以进行实现。

那么最后,让我们来看看列表推导式如何完成这个需求:

# 列表推导式
items = [i**2 for i in range(10)]
print(items)

---
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

简简单单一句话,比用map函数更简单的逻辑,就完成了我们的需求。这一句话的代码其实逻辑桁清晰,也很好理解:

首先,我们用for来进行循环传值给i, 接着,我们用i**2来得到我们期望的值,最后生成列表。本质上,其实和我们用的第一种普通方法是一样的。

接着我们再来看一个, 我们现在有一个字符串'1234', 想要得到这样一个列表[2, 4, 6, 8]。照例,从普通方法开始:

# 普通方法
str = '1234'
items = []
for i in str:
items.append(int(i)*2)

print(items)

---
[2, 4, 6, 8]

OK,没问题。我们继续:

items.clear()
print(items)

items = list(map(lambda x:int(x)*2, str))
print(items)

---
[]
[2, 4, 6, 8]

可以看到,我们先将items清空之后再继续操作的,这次我们用了list+map的方式,一样得到了我们想要的结果。

最后,当然是用列表推导式的方式:

items.clear()
print(items)

items = [int(i)*2 for i in str]
print(items)

---
[]
[2, 4, 6, 8]

同样,我们得到了想要的结果。

讲到这里了,我给大家秀一个小技巧,俗称骚操作,就是我们其实可以运用位运算操作符:

items.clear()
print(items)

items = [int(i) << 1 for i in str]
print(items)

---
[]
[2, 4, 6, 8]

具体代码执行的时候发生了什么,就算是给大家留个小思考题。提示:可以回头翻看下我们之前讲到的位运算符。

带有判断条件的列表推导式

除了基本的列表推导式,我们还有一种带有判断条件的列表推导式。

结果变量 = [变量或变量的处理结果 for 变量 in 容器类型数据 条件表达式]

相比起基本的列表推导式,我们现在多了一个条件表达式,那么我们该怎么利用呢?来个需求:从0 ~ 9,求所有的偶数并且形成一个新的列表。这回,我们就只完成常规方法和列表推导式,对比着来观察一下:

# 常规方式
items = []
for i in range(10):
if i % 2 == 0:
items.append(i)

print(items)

---
[0, 2, 4, 6, 8]

很好,我们完成了需求。接下来,大家试试不看我下面写的代码,自己从常规方式思考下该怎么写,然后自己运行一下试试写对了没,最后,再和我写的对比一下看看咱们写的有没有区别。

items = [i for i in range(10) if i % 2 == 0]
print(items)

---
[0, 2, 4, 6, 8]

没错,就是这么简单,你做对了吗?

带有条件判断的多循环推导式

现在有这样一个需求,我们拿到两个列表[1,2,3], [3,1,4], 要将这两个列表中的元素两两组合,要求组合的元素不能重复:

# 常规方法
items = []
for x in [1, 2, 3]:
for y in [3, 1, 4]:
if x != y:
items.append((x,y))
print(items)

---
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

这样,我们就完成了刚才的需求。那用推导式该如何实现呢?

items = [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
print(items)

---
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

没毛病,我们完全实现了刚才的需求。这个很好理解对吧?

让我们接着来看最后一个推导式的形式。

对于嵌套循环的列表推导式

这次我们直接写需求,然后上示例。

需求为,现在我们有一个3x4的矩阵,由3个长度为4的列表组成,我们现在要交换其行和列。

注意哦,这个行转列需求在处理数据的时候经常会用到。

# 需求样例
'''
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]

==>

[
[1, 5, 9],
[2, 6, 10],
[3, 7, 11],
[4, 8, 12]
]
'''

来,让我们尝试着实现一下:

# 首先,定义初始数据,大家可以直接copy我给到的矩阵数据

# 先定义数据
arr = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]

# 使用常规方法
items = []
for i in range(4):
res = []
for row in arr:
res.append(row[i])
items.append(res)
print(items)

# 使用列表推导式, 我们从内向外来写
items = [[row[i] for row in arr] for i in range(4)]
print(items)

---
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

这样,我们就完成了刚才的需求。我们拆解呢,还是从外层开始讲起:

首先,因为我们发现数据是4列,所以我们设定了一个range(4)来进行4次迭代,将0,1,2,3这四个下标分别传到内层循环。

然后我们开始在arr内循环找到当前的row, 循环会依次去寻找[1,2,3,4],[5,6,7,8],[9,10,11,12]。然后将每一个row中的寻找当前的row[i],并且填入一个新列表内。那么这三组列表中的row[i]就会是这样的:

row[1]分别为1, 5, 9row[2]分别为2, 6, 10.... 依次类推。当外层循环完成之后,就正好是组成了新的四个新的列表,最后再将新列表依次传到items这个空列表内,就完成了。

那同样都是两次for循环嵌套,为什么上面那个案例就是顺序写的,内层for循环写在了后面,而下面这个案例的内层for循环就写到了前面呢?

好的,让我们来看看,如果将下面这个案例的内存for循环写在后面会是怎样的:

items = [row[i] for i in range(4) for row in arr]
items

---
[1, 5, 9, 2, 6, 10, 3, 7, 11, 4, 8, 12]

看到了吗?顺序还是对的,只是依次传入了数据,并未形成矩阵。那有小伙伴就说了,那是不是因为没在row[i]上加[]从而形成列表呢?

好的,让我们再来做一个实验:

items = [[row[i]] for i in range(4) for row in arr]
items

---
[[1], [5], [9], [2], [6], [10], [3], [7], [11], [4], [8], [12]]

可以看到,列表是形成了,但是却是一个元素占一个列表,并没形成我们想要的矩阵。

估计小伙伴们看出来了,在推导式中,因为变量或变量的处理结果必须放在前面,所以我们为了要形成矩阵内层新的row,所以才必须将处理结果和内层循环方法放在一起,并加上[]来确保这组结果能形成一个列表, 也就是我们现在这样:

items = [[row[i] for row in arr] for i in range(4)]
items

---
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

看一次没看懂的小伙伴,可以多看看,尝试着自己去分解,将其理解透彻。因为这个方法在我们后续的数据清洗中使用非常频繁。

小练习

  1. 为了能达到练习的目的,从这一节开始,所有练习可以不在课程中展示了。大家先做一下,然后可以在我下一节课中的源码中去找答案,然后来看看和自己做的是否一样。
  2. 以下所有练习必须使用列表推导式来实现
  3. 有些练习不止一个方法,大家尝试用多种方法来实现一下。
  4. 做完的小伙伴可以在课程后面留言讨论。
# 1. 让我们尝试将字典中的健值对转成`key = value`的数据格式
{'user':'admin', 'age':'20', 'phone':'133'} 转为 ['user=admin','age=20','phone=133']

# 2. 把列表中的所有字符全部转为小写
['A', 'CCCC', 'SHIss', 'Sipoa','Chaheng', 'Python','dsAhio']

# 3. x是05之间的偶数,y是0~5之间的奇数,把x,y组成一个元组,放到列表中

# 4. 使用列表推导式完成九九乘法表

# 5.M, N中矩阵和元素的乘积
'''
M = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]

N = [
[2, 2, 2],
[3, 3, 3],
[4, 4, 4]
]
'''

最后,大家记得做练习并且留言,下课。

留言
avatar-img
茶桁的沙龍
9會員
62內容數
从基础开始,再到Python,然后是CV、BI、NLP等相关技术。从头到尾详细的教授一边人工智能。
茶桁的沙龍的其他內容
2023/08/22
虽然是最后一节课了,但是本节课的任务却是一点也不轻松。相比较而言,如果你以后从事的是数据治理和分析工作,那么本节课的内容可能会是你在今后工作中用到的最多的内容。我们需要学习行列索引的操作,数据的处理,数据的合并,多层索引,时间序列,数据的分组聚合(重点)。最后,我们会有一个案例的展示。
Thumbnail
2023/08/22
虽然是最后一节课了,但是本节课的任务却是一点也不轻松。相比较而言,如果你以后从事的是数据治理和分析工作,那么本节课的内容可能会是你在今后工作中用到的最多的内容。我们需要学习行列索引的操作,数据的处理,数据的合并,多层索引,时间序列,数据的分组聚合(重点)。最后,我们会有一个案例的展示。
Thumbnail
2023/08/21
Hi,大家好。我是茶桁。 上一节课中,我们学习了matplotlib. 实际上,我们已经进入了数据可视化阶段。 可是在上一节课中,所有的数据都是我们固定写好的,包括两个电影的数据展示的案例(柱状图和直方图),都是我们将数据手动写成了数据列表,然后直接使用。 在我们平时的工作中,不太有那么多的机
Thumbnail
2023/08/21
Hi,大家好。我是茶桁。 上一节课中,我们学习了matplotlib. 实际上,我们已经进入了数据可视化阶段。 可是在上一节课中,所有的数据都是我们固定写好的,包括两个电影的数据展示的案例(柱状图和直方图),都是我们将数据手动写成了数据列表,然后直接使用。 在我们平时的工作中,不太有那么多的机
Thumbnail
2023/08/21
Hi, 大家好。我是茶桁。 在上一节课中,我们结束了Python正式的所有内容,但是咱们的Python课程还未结束。从这节课开始,我们要来学习一下Python的第三方库。 Python的生态非常完善也非常活跃,我们不太可能讲目前所有的第三方库全部都介绍一遍,只介绍几个有影响力并且和处理数据相关的
Thumbnail
2023/08/21
Hi, 大家好。我是茶桁。 在上一节课中,我们结束了Python正式的所有内容,但是咱们的Python课程还未结束。从这节课开始,我们要来学习一下Python的第三方库。 Python的生态非常完善也非常活跃,我们不太可能讲目前所有的第三方库全部都介绍一遍,只介绍几个有影响力并且和处理数据相关的
Thumbnail
看更多
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
在 Python 中,tuple 與 List有一個關鍵的不同點:tuple 是不可變的,這意味著一旦創建了 tuple,就無法更改其內容。 這與 List的可變性形成了對比,list 可以新增、刪除或修改元素。 元素的意思: 元素:指的是 List 中的每一個獨立的項目或值。
Thumbnail
在 Python 中,tuple 與 List有一個關鍵的不同點:tuple 是不可變的,這意味著一旦創建了 tuple,就無法更改其內容。 這與 List的可變性形成了對比,list 可以新增、刪除或修改元素。 元素的意思: 元素:指的是 List 中的每一個獨立的項目或值。
Thumbnail
Array可以說是各種語言除了基本型別之外,最常用的資料型別與容器之一了。 Array 這種連續格子狀的資料結構,在Python要怎麼表達呢? 建立一個空的陣列 最簡單也最直接的寫法就是 array = [] # Python list [] 就對應到大家熟知的array 陣列型態的資料結
Thumbnail
Array可以說是各種語言除了基本型別之外,最常用的資料型別與容器之一了。 Array 這種連續格子狀的資料結構,在Python要怎麼表達呢? 建立一個空的陣列 最簡單也最直接的寫法就是 array = [] # Python list [] 就對應到大家熟知的array 陣列型態的資料結
Thumbnail
List 清單 和 Tuple元組 清單在Python裡面非常的常用,大家一定要熟練這些基礎的元素。 在Python中,列表(List)是一種常用的資料類型,用於儲存一組有序的元素。列表是可變的(Mutable),這意味著你可以在列表中新增、刪除和修改元素。列表使用方括號 []
Thumbnail
List 清單 和 Tuple元組 清單在Python裡面非常的常用,大家一定要熟練這些基礎的元素。 在Python中,列表(List)是一種常用的資料類型,用於儲存一組有序的元素。列表是可變的(Mutable),這意味著你可以在列表中新增、刪除和修改元素。列表使用方括號 []
Thumbnail
sort reverse count index copy len min max sum any all
Thumbnail
sort reverse count index copy len min max sum any all
Thumbnail
list跟tuple 應用場景跟常用函式:append extend insert remove clear pop del
Thumbnail
list跟tuple 應用場景跟常用函式:append extend insert remove clear pop del
Thumbnail
Hi,大家好。我是茶桁。 关于Python的数据类型,我们已经详细讲解了三种,字符串,列表和元组。那么今天,我们再来讲一种:字典。 字典也是一种数据的集合,由健值对组成的数据集合,字典中的键是不能重复的。 字典中的键必须是不可变的数据类型,常用的键主要是:字符串,整型... 实际上,在之前字
Thumbnail
Hi,大家好。我是茶桁。 关于Python的数据类型,我们已经详细讲解了三种,字符串,列表和元组。那么今天,我们再来讲一种:字典。 字典也是一种数据的集合,由健值对组成的数据集合,字典中的键是不能重复的。 字典中的键必须是不可变的数据类型,常用的键主要是:字符串,整型... 实际上,在之前字
Thumbnail
Hi,大家好。我是茶桁。 之前两节分别介绍了字符串和列表,今天,我们来讲讲另外一个常用到的数据类型:元组。 元组和列表很像,两者都是一组有序的数据的组合。但是也有很多不同点,比如元组内的元素一旦定义了就不可以再修改,因此元组称为不可变数据类型。 元组定义 元组的定义方式包括以下要点: 定义
Thumbnail
Hi,大家好。我是茶桁。 之前两节分别介绍了字符串和列表,今天,我们来讲讲另外一个常用到的数据类型:元组。 元组和列表很像,两者都是一组有序的数据的组合。但是也有很多不同点,比如元组内的元素一旦定义了就不可以再修改,因此元组称为不可变数据类型。 元组定义 元组的定义方式包括以下要点: 定义
Thumbnail
Hi,大家好。我是茶桁。 最近几节课,我们都是在详细讲解Python内的数据类型,上一节课我们详细了解了字符串,这节课,让我们来详解一下列表。 首先,我们先有一个大的概念,列表,其实就是一组有序的数据组合;另外,列表中的数据是可以被修改的。也就是说,列表是一个可变序列类型。 列表定义 如何在
Thumbnail
Hi,大家好。我是茶桁。 最近几节课,我们都是在详细讲解Python内的数据类型,上一节课我们详细了解了字符串,这节课,让我们来详解一下列表。 首先,我们先有一个大的概念,列表,其实就是一组有序的数据组合;另外,列表中的数据是可以被修改的。也就是说,列表是一个可变序列类型。 列表定义 如何在
Thumbnail
我們將會學習 Python 中的數據結構。 主要的數據結構包括列表 (List)、元組 (Tuple)、字典 (Dictionary) 以及集合 (Set)。
Thumbnail
我們將會學習 Python 中的數據結構。 主要的數據結構包括列表 (List)、元組 (Tuple)、字典 (Dictionary) 以及集合 (Set)。
Thumbnail
探索Python學習筆記中列表的建立、存取和常用方法。從使用中括號定義列表到了解索引、新增、刪除、修改等操作,並介紹append、remove、count等常用方法。
Thumbnail
探索Python學習筆記中列表的建立、存取和常用方法。從使用中括號定義列表到了解索引、新增、刪除、修改等操作,並介紹append、remove、count等常用方法。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News