每篇前言:
🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者
- 🔥🔥本文已收录于Python全栈系列专栏:《Python全栈基础教程》
- 🔥🔥热门专栏推荐:《Django框架从入门到实战》、《爬虫从入门到精通系列教程》、《爬虫高级》、《前端系列教程》、《tornado一条龙+一个完整版项目》。
- 📝📝本专栏面向广大程序猿,为的是大家都做到Python从入门到精通,同时穿插有很多很多习题,巩固学习。
- 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
- 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!
Python数据类型(三)
1 list列表
List(列表) 是 Python 中使用最频繁的数据类型。
列表可以完成大多数集合类的数据结构实现。它支持字符,数字,字符串甚至可以包含列表(所谓嵌套)。
列表用[ ]标识。是python最通用的复合数据类型。
列表中的值得分割也可以用到变量[头下标:尾下标],就可以截取相应的列表,从左到右索引从0开始的(跟字符串一样)。
加号(+)是列表连接运算符,星号(*)是重复操作。
基本概念:在Python程序中使用中括号"[]"来表示列表,并用逗号分隔其中的元素。
books = ['python从入门到放弃', 'C从入门到放弃', '汇编从入门到放弃'] # 创建一个名为books的列表
print(books) # 输出列表books中的信息
代码讲解:在上述代码中,创建一个名为“books”的列表,在列表中存储了三个元素,执行后会将列表打印输出。
1.1 第一小节课 之 列表中的索引及切片(分片)
注意:索引及切片(分片)对于任何数据类型都有效哦!
①如何取列表中的元素(聪明的同学都已经答出来是通过索引取值了!)
在python程序中,因为列表是一个有序集合,所以要想访问列表中的任何元素,只需要将该元素的位置或索引告诉Python即可(但是要注意的是:列表中元素的索引是从0开始!)。要想访问列表元素,可以指出列表的名称,再指出列表的索引,并将其放在方括号内。
骚操作来袭:如果在一个项目中,我们不知道存储项目数据的这个列表到底多长,而我们就是要它最后一个元素该怎么做呢?
其实方法很多很多哦!但是老师在这里讲一个通过下标取它的骚操作:
a = [1,2,3,4,5,6,7,8,9,10]
print(a[-1]) # 输出为 10。 从列表右侧向左侧第一个是-1,以此类推。
②首先将列表切片的万能公式交给大家
- (一听万能公式是不是感觉可牛逼,但是同学们不要形成惯性思维,一看到万能公式就想着背住就完事了哦!要通过老师下面的讲解彻底理解它哦!)
切片
(万能公式一大注意点:左闭右开!)
第一个:
正方向[起始位置:结束位置+1:步长]
第二个:
注(如果步长为负数,则逆序)
反方向[开始位置:结束位置-1:负数]
是不是看了万能公式后一头懵,那就对了,都给我打起精神,咱们一起来好好研究这个万能公式究竟是个啥意思!
(1)第一个万能公式讲解——正方向[起始位置:结束位置+1:步长]
知识补给站(一个简便的创建数字列表的方法——使用方法range(),注意这个方法也是左闭右开哦!):
上代码:
numbers = list(range(0,11))
print(numbers) # 输出[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- 第一个代码片段讲解:
正方向的意思是从列表左到右取值!
起始位置是想要获取的列表元素的起始索引;
结束位置是想要获取的列表元素的结束索引加一。
步长是每一次取值跳几个索引,默认值是1。
numbers = list(range(0,11))
print(numbers[0:5]) # 等价于:print(numbers[0:5:1])
- 第二个代码片段讲解:
给定列表a = [1,2,3,4,5,6,7,8,9,10],我们需求是取出列表中所有奇数——这就用到了正向切片!
a = [1,2,3,4,5,6,7,8,9,10]
print(a[0:(9+1):2]) # print(a[0(起始位置):9+1(结束位置+1):2(隔两个取一次)])
需求完美完成!
课上小问题(会的举手快快快——加平时成绩哦!)
如何取该列表中的所有偶数呢?
答案:
print(a[1(起始位置):9+1(结束位置+1):2(隔两个取一次)])
运行结果为 [2,4,6,8,10]
(2)第二个万能公式讲解——反方向[开始位置:结束位置-1:负数]
注:如果步长为负数,则逆序!
- 第一个代码片段讲解:
反方向的意思是从列表右到左取值!
起始位置是想要获取的列表元素的结束索引(包括这个元素哦!);
结束位置是想要获取的列表元素的起始索引减一。
【上面是按照正向索引讲的,不是很好理解。其实反方向这个公式,我们完全可以理解为是从列表右侧向左侧取值。第一个值就是从右侧的起始位置,第二个值就是从右侧开始的结束位置,就要加一,而对于列表索引来说就是减一。这也说明了遵循左闭右开!】
步长是每一次取值跳几个索引,如果是逆序切片就必须给负数的步长!。
a = [1,2,3,4,5,6,7,8,9,10]
print(a[9:1:-1])
- 第二个代码片段讲解:
给定列表a = [1,2,3,4,5,6,7,8,9,10],我们需求是倒序取出列表中所有偶数——这就用到了逆序切片!
a = [1,2,3,4,5,6,7,8,9,10]
print(a[::-2]) # 简写了,起始位置和结束位置两个地方没写,则为整个列表
需求完美实现:
总结升华:
L[m:n]表示:从索引m开始直到索引n(不包含n)取数据;
L[m:n:p]表示:从索引m开始直到索引n(不包含n)取数据,每p个取一个。
-
m、n、p、可以为负数:
①p>0 从首部往尾部方向取,p<0是从尾部向首部取。
②m、n大于0表示索引值。
③m、n小于0表示倒数第几个。
L[-5:-2] 表示倒数第5到倒数第2元素(不包含倒数第2个元素)从首到尾的顺序取。
L[-2:-5:-2] 表示倒数第2到倒数第5元素(不包含倒数第5个元素)从尾到首的顺序取,每2个取一个。
一般流程:m、n如果为负数先转为正数然后根据p的方向取值(初学可能不是很理解这句话,可以用多了再来揣度)。 -
m、n、p可以缺省:L[::]
m缺省为p表示的方向的首元素,n缺省为p表示的方向的尾元素的后一个,p缺省表示1
L = [‘Google’, ‘Baidu’, ‘Taobao’, ‘edu360’, ‘xiaoniu’]
1.2 第二小节课 之 列表中的骚方法操作
①list.append(obj)
- 在列表末尾添加新的对象 列表专属
a = [1,2,3]
a.append('s')
print(a) # 输出为:[1, 2, 3, 's']
②list.insert(x,y)
- 自定义位置添加元素,将元素插入到指定的索引 加入单个值!!!(参数含义:第一个是所添加的下标指定位置,第二个是需要添加的元素)
a = [1,2,3]
a.insert(1,'new_num') #
print(a) # 输出为:[1, 'new_num', 2, 3]
③list.extend(sep)
- 在列表末尾一次性追加另一个序列(字符串,列表,元组)中的多个值(用新列表扩展原来的列表)
a = [1,2,3]
a.extend('abc')
print(a) # 输出为:[1, 2, 3, 'a', 'b', 'c']
④list.pop()
- 没有传参数的情况下会删除列表的最后一个元素,如果带了参数的话就删除指定索引位置的元素
# 未加参数——删除最后一个元素!
a = [1,2,3]
a.pop()
print(a) # 输出为[1,2]
加参数——删除指定索引位置的元素!
b = [1,2,3]
a.pop[1]
print(a) # 输出为[1,3]
⑤list.remove()
- 删除指定元素,但是在有多个相同项的情况下只删除第一个
a = [1,2,3,4,1,1,1]
a.remove(1)
print(a) # 输出为[2,3,4,1,1,1]
⑥list.clear()
- 清空列表,删除列表里所有的值,括号内不需要带参数
a = [1,2,3,4,1,1,1]
a.clear()
print(a) # 输出为[]
⑦list.count(obj)
- 统计某个元素在列表中出现的次数,计数(可以赋值可以print)
a = [1,2,3,4,1,1,1]
num = a.count(1)
print(num) # 输出为 4
⑧list.index(x,y)
- 第一个参数是要查找的值,第二个参数是查找开始的下标位置。每次查找只会显示查找到的第一个值的下标。不加第二个参数的话默认从下标0开始,没有查找到的时候会报错 (可以赋值可以print)
a = [1,2,3,4,1,1,1]
index1 = a.index(1)
index2 = a.index(1,2)
print(index1) # 输出为 0
print(index2) # 输出为 4
⑨join()
- 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。使用方法:str.join(sequence),sequence为要连接的元素序列。返回通过指定字符连接序列中元素后生成的新字符串。
a = ['a', 'b', 'c']
end = "-".join(a)
print(end) # 输出为a-b-c
⑩list.reverse()
- 反向列表中元素
⑩①list.sort([func])
- 对原列表进行排序
⑩②列表操作中还有深浅复制的方法
拓展:列表排序的方法!
①sort() --->对列表里的数值从小到大排序,只能是数字,如果列表里有字符串的话那么需要在括号内加入:key=str 如a.sort(key=str) 。字符串的排序根据于ASCII的字母排序。
上代码:
a = ['a', 'd', 'f', 'b']
a.sort(key=str)
print(a) # 输出为:['a', 'b', 'd', 'f']
拓展:使用sort()方法对列表里的数值从大到小排序;如果列表里有字符串的话加入参数key=str,如a.sort(key=str,reverse=True)。
上代码:(列表含有字符串)
a = ['a', 'g', 'b', 'c']
a.sort(key=str,reverse=True)
print(a) # 输出为:['g', 'c', 'b', 'a']
上代码:(列表元素都是数字)
a = [1,9,3,7]
a.sort(reverse=True)
print(a) # 输出为:[9,7,3,1]
②reverse() --->对列表里的数值从大到小反序,只能是数字。
上代码:
a = [1, 4, 3, 8]
a.reverse()
print(a) # 输出为: [8,3,4,1]
一些在项目中常用的列表小操作!
(1)找出列表中出现次数最多的元素
如果有一天,同学们在工作,Boss突然抛出来一个需求:**在python程序中如何找到列表中出现次数最多的元素!**难道你要使用循环啥的一步步瞎搞?效率低B格也不高。这里为师教你们一种简单易上手B格高的方法——使用collections模块中的Counter类,调用Counter类中的most_common()函数来实现!
直接上代码:
from collections import Counter
words = [
'look', 'into', 'my', 'AAA', 'look', 'into', 'my', 'AAA',
'the', 'AAA', 'the', 'eyes', 'not', 'BBB', 'the', 'AAA',
"don't", 'BBB', 'around', 'the', 'AAA', 'look', 'into',
'BBB', 'AAA', 'BBB', 'under'
]
word_counts = Counter(words)
print('统计所有元素出现次数:',word_counts)
top_three = word_counts.most_common(3)
print('统计出现次数最多的三个元素:',top_three)
输出为:
统计所有元素出现次数: Counter({'AAA': 6, 'the': 4, 'BBB': 4, 'look': 3, 'into': 3, 'my': 2, 'eyes': 1, 'not': 1, "don't": 1, 'around': 1, 'under': 1})
统计出现次数最多的三个元素: [('AAA', 6), ('the', 4), ('BBB', 4)]
(2)排序类定义的实例
项目背景:如果我们在一个项目中,一个类定义的实例有很多个,而我们的需求是将这些实例排序该怎么做呢?
使用内置函数sorted()可以接收一个用来传递可调用(callable)对象的参数key,而这个可调用对象会返回待排序对象中的某些值,sorted()函数则利用这些值来比较对象。
假如在程序中存在多个User对象的实例,如果想通过属性user_id来对这些实例进行排序,可以提供一个可调用对象,它将User实例作为输入,然后返回user_id。下面代码演示排序上述User对象实例的过程:
class User:
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return 'User({})'.format(self.user_id)
# 原来的排序
users = [User(91), User(17), User(18)]
print(users)
# 根据user_id排序——两种方法
# ①使用lambda表达式:
print(sorted(users, key=lambda u: u.user_id))
# ②使用内置函数operator.attrgetter()进行处理:
from operator import attrgetter
print(sorted(users, key=attrgetter('user_id')))
(3)命名切片(高阶用法)
项目问题背景:
在python程序中,有时会发现编写的代码由于过度的使用硬编码的切片索引(就像上面第二小节课讲的那样!),而使得我们的项目代码变得杂乱无章而无法阅读,此时就需要清理它们。同时过度的使用硬编码的索引值,也会降低代码的可读性和可维护性。
解决方法:
在python程序中,使用函数slice()可以实现切片对象,能够在切片操作函数中实现参数传递功能,可以被用在任何允许进行切片操作的地方。
使用函数slice()的语法格式:
class slice(stop)
class slice(start, stop, step)
start:起始位置;
stop:结束位置;
step:间距。
上代码讲解:
items = [0, 1, 2, 3, 4, 5, 6]
a = slice(2,4) # 定义一个slice对象实例a
print(items[2:4]) # 使用常用的切片取列表值
print(items[a]) # 使用slice对象实例a取列表值
items[a] = [10, 11]
print(items)
print(a.start) # 分别通过属性a.start,a.stop,a.step获取该slice对象的信息
print(a.stop)
print(a.step)
s = 'verygoodman'
# 使用indices(size)函数将切片映射到特定大小的序列上,这将会返回一个(start,stop,step)元组,
# 所有的值都已经正好限制在边界以内,这样当进行索引操作时可以避免出现IndexError异常。
print(a.indices(len(s)))
print(*a.indices(len(s))) # 分解元组
for i in range(*a.indices(len(s))):
print(s[i])
深入讲解——使用indices(size)函数的好处,以及上述注释中写的为何使用此函数当进行索引操作时可以避免出现IndexError异常!
直接上代码讲解:
a = slice(2,4) # 定义一个slice对象实例a
s = 'ver' # 此时序列s
# 使用indices(size)函数将切片映射到特定大小的序列上,这将会返回一个(start,stop,step)元组,
# 所有的值都已经正好限制在边界以内,这样当进行索引操作时可以避免出现IndexError异常。
print(a.indices(len(s)))
print(*a.indices(len(s))) # 分解元组
for i in range(*a.indices(len(s))):
print(s[i])
一节课让你彻底搞懂python中的单星号(*)和双星号(**)的区别及项目实际用法——给我学!
观察运行结果会发现:我们虽然定义的slice对象实例的切片范围是2-4,但是由于映射到的序列整体范围只有0-3(左闭右开),因为我们使用了indices(size)函数,它会将切片范围限制在这个映射的序列范围边界以内:2-3,这样虽然我们定义的slice对象实例范围超出了此序列s的范围,但是因为indices(size)函数的使用并不会报错,而是类似于动态的自适应变化!
(4)生成list相关函数(重点!!!!)
一、列表生成式
列表推导式(List Comprehension)是一种简化代码的优美方法。官方文档——列表推导式提供了一种创建列表的简洁方法。 使用列表推导式能够非常简洁的构造一个新列表,只需要用一个简洁的表达式即可对得到的元素进行转换变形。
# 使用Python列表推导式的语法格式:
variable = [out_exp_res for out_exp in input_list if out_exp == 2]
out_exp_res:列表生成元素表达式,可以是有返回值的函数;
for out_exp in input_list:迭代input_list,将out_exp传入out_exp_res表达式中;
if out_exp == 2:判断根据条件可以过滤哪些值。
先来讲讲—range函数list序列迭代对象
- Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表。
- Python3 list() 函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表。
①range 语法:
range(stop)
range(start, stop[, step])
②参数说明:
start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
end: 计数到 end 结束,但不包括 end。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
# coding=utf-8
print(list(range(10))) # 产生一个0-9的序列
print(list(range(3, 10))) # 产生一个3-9的序列
print(list(range(0, 10, 3))) # 产生从0开始,按3递增,最大值为9的序列
print(list(range(10, -1, -1))) # 产生从10开始,最小值为0的递减序列
第一个:基本使用
- 我先来实战讲解一下,然后下面出两道很简单的小题目:
#coding=utf-8
print([x * x for x in range(1, 11)])
print([x * x for x in range(1, 11) if x % 2 == 0])
#还可以使用两层循环,可以生成全排列:
print([m + n for m in 'ABC' for n in 'XYZ'])
print([str(x)+str(y) for x in range(1,6) for y in range(11,16)])
#for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:
d = {'x': 'A', 'y': 'B', 'z': 'C' }
print([k + '=' + v for k, v in d.items()])
第一题:创建一个包含从1到10的平方的列表。
惯性思维——常规方法解决:
list_end = []
for x in range(10):
list_end.append(x**2)
print(list_end)
借由列表推导式——一行代码解决:
list_end1 = [x**2 for x in range(10)]
print(list_end1)
二者输出都为:
第二题:输出30以内能够整除3的整数。
传统方法实现:
nums = []
for x in range(30):
if x % 3 == 0:
nums.append(x)
print(nums)
使用列表推导式一行代码解决:
nums1 = [x for x in range(30) if x % 3 == 0]
print(nums1)
二者输出都为:
第二个:升级版使用——使用列表生成式的同时使用函数处理
需求:首先获取30以内能够整除3的整数,然后一次输出所获得整数的平方。
def squared(x):
return x**2
end = [squared(x) for x in range(30) if x % 3 == 0]
print(end)
第三个:高级版使用——删选列表中特定元素的高级操作
在python程序中,有时候筛选列表中的某些特定元素时,筛选标准无法简单的表示在列表推导式或生成器表达式中,例如当筛选过程涉及异常处理或者其他一些复杂的细节时。此时可以考虑将处理筛选功能的代码放到单独的功能函数中,然后使用内建的filter()函数进行处理。
需求:筛选指定列表中的所有整数元素!
values = ['1', '2', '-3', '-', '4', 'N/A', '5']
def is_int(val):
try:
x = int(val)
return True
except ValueError:
return False
# 注意:因为使用函数filter()创建了一个迭代器,所以想要得到一个列表形式的结果,必须在filter()前面加上list()函数。
isvals = list(filter(is_int, values))
print(isvals)
二、列表生成器
-
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
-
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
-
只要把一个列表生成式的[]改成(),就创建了一个generator:
g = (x * x for x in range(10))
-
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
for x in (x * x for x in range(1, 11)):
print(x, end=" ")
print()
for x in (x * x for x in range(1, 11) if x % 2 == 0):
print(x, end=" ")
三、函数列表生成器
- 如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, … - 斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
- 上面的函数可以输出斐波那契数列的前N个数:
>>> fib(6)
1
1
2
3
5
8
- 仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
- 也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
- 这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
>>> fib(6)
<generator object fib at 0x104feaaa0>
- 这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
- 举个简单的例子,定义一个generator,依次返回数字1,3,5:
# -*- coding: utf-8 -*-
"""
__author__ = 小小明-代码实体
"""
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
- 调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
-
可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就抛出StopIteration异常。
-
可以for循环迭代generator:
for i in odd():
print(i)
- 回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
- 同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:
for n in fib(6):
print(n)
- generator是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。
(5)删除列表中的重复元素并保持顺序不变
第一个版本:
需求:删除列表中重复出现的元素,并且保持剩下元素的显示顺序不变。如果序列中保存的元素是可散列的(hashable),那么此功能可以使用集合和生成器实现。
知识点补给站:
如果一个对象是可散列的,那么在它的生存期内必须是不可变的,这需要有一个__hash__()方法。在python程序中,
整数,浮点数,字符串和元组都是不可变的。
上代码:
def fun(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
if __name__ == '__main__':
a = [5, 3, 3, 8, 6, 5, 9]
print(a)
print(list(fun(a)))
第二个版本:
上述代码有个缺陷——只有当序列中的元素是可散列的时候才能实现功能。如果想在不可散列的对象序列中实现此功能,该如何实现呢?
上代码:
def fun(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
if __name__ == '__main__':
a = [
{'x' :2, 'y':3},
{'x' :1, 'y':4},
{'x' :2, 'y':3},
{'x' :2, 'y':3},
{'x' :10, 'y':15}
]
print(a)
print(list(fun(a, key=lambda a: (a['x'], a['y']))))
注意:上述代码中函数fun()中的参数key的功能是将序列中的元素转换为可散列的类型,这样做的目的是检测重复选项。