列表和元祖
基本概念:
列表和元祖的功能其实都差不多,可以用一个变量来表示多个数据,其实这两个东西就相当于其他编程语言中的 数组 。
列表和元祖,大部分功能其实都差不多!但是有一个非常大的不同就是:
- 列表是可以变的! 这里说的可变就是我们在创建好这个变量之后,可以发生改变;
- 元祖是不可变的!不可变就是创建好之后,不能发生改变,如果想要改变这个变量,就得把这个变量删除之后再重新定义一个新的。
创建列表:
创建列表主要有两种方式:
直接使用字面值来创建:用 [] 来创建
a = []
用使用 list 来创建:用 list() 来创建
b = list()
可以在创建列表的时候,在 [ ] 中指定列表的初始值,元素之间使用,来分割。
a = [1,2,3,4]
print(a)
运行结果:
[1, 2, 3, 4]
注意:在C++/java 里面一个数组在定义的时候都表明了这个是一个上面类型的数组,这也就表示了一个数组里面只能放相同类型的变量。但是在python中里的列表则无限制,想放python中什么类型的数据都可以。
例:
a = [1,'hello',True,[4,5,6]]
print(a)
打印结果:
[1, 'hello', True, [4, 5, 6]]
我们发现,真的是什么类型类型都放进去,我们在列表里面甚至可以放一个列表!
访问下标
列表和元祖也想数组一样,是有下标的,我们可以通过下标来访问列表和元祖里面所有元素,访问方式也是和数组一样,用 [ ] 访问。
例:
a = [1,'hello',True,[4,5,6]]
print(a[0])
print(a[1])
print(a[2])
print(a[3])
#输出结果
#1
#hello
#True
#[4, 5, 6]
len () 可以传字符串,列表,元祖,字典,自定义的类·········等等,作用是用来获取到列表的长度(元素个数),和字符串类似。
a = [1,2,3,4]
print(len(a))
print(a[len(a) - 1])
print(a[-1])
#输出结果:
#4
#4
#4
列表的切片操作
通过下标操作是一次取出了里面的一个元素;而我们通过切片,则是一次取出一组连续的元素,相当于是得到一个子列表:
使用 [ : ] 的方式进行切片操作:
alist = [1,2,3,4]
print(alist[1:3])
#输出:
#[2, 3]
这个 [1 :3 ] 冒号左右的两个数字表示了一段区间,1 表示开始区间的下标 ; 3 表示结束区间的下标; 1:3 取到下标为 1 一直到下标为 3 的元素 (包含1,但不包含3) 是 [1,3) 这样一个左闭右开的区间;
其次,这个边界是可以省略的:
省略后边界:
alist = [1,2,3,4]
print(alist[1:])
#输出:
#[2, 3, 4]
这个表示的是省略后边界,意思是从开始位置,一直取到整个列表的结束。
省略前边界:
alist = [1,2,3,4]
print(alist[:2])
#输出:
[1, 2]
这个例题的打印就是把这个区间从开始到下标为2的元素结束(不包含下标为2的元素)
省略前后边界:
alist = [1,2,3,4]
print(alist[:])
#输出:
#[1, 2, 3, 4]
都省略掉其实就是这个列表自身,所以打印是自身。
注意:切片操作是一个比较高级的操作,进行切片的时候,只是取出了原有列表里的一部分,并不涉及到“数据的拷贝”。假设有一个很大的列表,进行切片,切片的范围也很大,即使如此,切片操作仍然非常高效。
切片操作指定步长
我们可以借助range来让切片操作有步长;
a = {1,2,3,4,5,6,7,8,9,0}
print(a[::1])
print(a[::2])
print(a[::3])
print(a[1:-1:2])
print(a[::-1])
print(a[::-2])
#输出:
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
#[1, 3, 5, 7, 9]
#[1, 4, 7, 0]
#[2, 4, 6, 8]
#[0, 9, 8, 7, 6, 5, 4, 3, 2, 1]
#[0, 8, 6, 4, 2]
print(a[::1]) 这里多加了一个冒号,我们在第三个数据位置输入了一个1,这个位置的意思就是步长,这个代码的意思就是,以步长为以的方式取出a这个列表的全部数据。
其次,这个步长还可以为负数,当这个步长为负数的时候,表示这个时候从后往前来去元素。
当切片中的范围炒作有效下标之后,不会出现异常!而是尽可能的吧符合要求的元素给获取到,例子:
a = [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0]
print(a[0:100])
打印结果:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
他的意思是:我把下标为0 -- 100 的元素都尽量取出来,如果没有我再不取。
遍历列表
用for循环遍历列表:
a = [1,2,3,4,5]
for elem in a :
print(elem)
for elem in a : 这句代码中我们用 elem 代表的是我们列表里的每一个元素 。其中 in 后面的 a 就是一个可迭代对象,这句代码也就是获取这个 a 当中的每一个元素。这个代码是把a列表里的值赋值给elem了,所有我们可以用这个代码来打印这个a的列表里面的元素,但是不能用来修改这个列表里面的值。
a = [1,2,3,4,5]
for i in range(0,len(a)) :
print(a[i])
这个是利用下标的方式来打印这个列表,其中 i 是循环的变量,range(0,len(a)) 这个是创建一个和a列表元素个数相同的一个区间。这个方法相对于上面一种,这个方法可以修改a列表里面的值:
a = [1,2,3,4,5]
for i in range(0,len(a)) :
a[i] = i+10
print(a[i])
输出结果:
10
11
12
13
14
我们发现这个时候我们的a列表里面的元素的值已经被改变了。
而我们用第一种的方式来修改a里面的元素的值的话:
a = [1,2,3,4,5]
for elem in a :
elem = elem + 10
print(a)
输出:
[1, 2, 3, 4, 5]
我们发现这个a列表的里面元素的值并没有发生改变。
那么第一种的elem这个中for循环是如何取出a里元素然后去执行的呢?
第一个循环就是这样,在每一次循环都把a列表的值一次赋值给elem,然后我们就打印elem的值,其实就是打印这个a列表里面的其中一个元素。所以我们之前的 elem = elem + 10 这个操作只是把elem的值给改了,并没有修改a列表当中的值。
列表的插入操作
尾插
我们说过列表在创造之后是可以变的,我们使用append的方法在列表末尾插入一个元素(尾插):
alist = [1,2,3,4]
alist.append('hello')
print(alist)
#输出:
#[1, 2, 3, 4, 'hello']
此处的函数 append 我们发现他是搭配 a 来使用的一个函数,但是我们之前的函数是直接调用的,像什么 type , print , input , len ,自定义函数 ·············· 都是独立函数,这些函数都是可以独立调用的,不需要依赖其他对象;那么我们这个 append 函数可不一样 ,这种要搭配对象来使用的函数,也叫做'方法‘,其中的对象也就是变量,由此我们知道,在python的变量不仅仅可以用来包含一些数据,它还可以包含一些方法。
像这个 a.append 就表示的是:我这个append所表示的对象是 a 这个列表,就体现出我现在是要对a这个列表进行尾插操作。
用insert 方法在任意位置插入元素
上述实现了尾插的操作,我们还可以使用 insert 函数来在列表的任意位置来插入元素:
a = [1,2,3,4]
a.insert(1,'hello')
print(a)
#输出结果:
#[1, 'hello', 2, 3, 4]
从输出结果我们可以看到我们在 a 这个列表里面插入了一个 ’hello‘ 这个字符串;
a.insert(要插入位置的下标,要插入元素的值)
接下来是非法访问的问题:
a = [1,2,3,4]
a.insert(1,'hello')
a.insert(100,'world')
print(a)
#输出结果:
#[1, 'hello', 2, 3, 4, 'world']
我们发现 a.insert(100,'world') 这行代码并不是意味着我在 a 列表下标为100 的位置插入一个’world‘字符串,也不是发生列表的下标越界警告,也不是直接把这个列表扩大为100的大小,而是他发现我们insert的访问超出了列表的范围,就在列表的最后一个元素的后面插入这个’world‘字符串。
查找元素
使用 in 查找
使用 in 操作符,判定元素是否在列表中存在,这个的返回值是 bool 类型:
alist = [1,2,3,4]
print(2 in alist)
print(10 in alist)
#True
#False
我们在print函数中的表达式就是 使用 in 操作符来写出的表达式,这个表达式的返回值是 bool类型,如第一个print中因为 alist 中有 2 这个元素,所以打印的是 True ;而第二个print中 因为 alist没有 10 这个元素,所以返回的是False。
由上可知,要知道这个列表里面是否有 某一个元素 ,那么我们肯定是要遍历一遍这个列表的,只不过这个遍历操作不需要我们来写,都是这个 in 这个操作符帮我实现的。
有 in 表示是否存在,也有 not in 表示是否不存在:
alist = [1,2,3,4]
print(2 in alist)
print(10 in alist)
print(2 not in alist)
print(10 not in alist)
#True
#False
#False
#True
我们还是用上面这个例子进行修改,我们发现打印结果和上面是对立的,也就是说,not in 是 in 的 逻辑取反。
用index 方法
使用index 的方法来判定,当前元素在列表中的位置;index 方法的参数就只有一个----你需要在列表中查找的值,找到之后他会返回这个元素所在的位置下标:
a = [1,2,3,4]
print(a.index(2))
#1
如上返回了 1 ,这个 1 就是值为 2 的元素的下标。
如果index 在列表中没有找到这个元素,在Python中它就会直接抛出异常:
a = [1,2,3,4]
print(a.index(2))
print(a.index(10))
他会提示我 : 这个10 不在这个列表当中。
删除元素
用 pop 方法删除最末端元素
a = [1,2,3,4]
a.pop()
print(a)
#[1, 2, 3]
此时a中末尾的元素已经被删除掉了。
使用 pop 还能删除任一位置的元素
pop的参数可以传一个位置下标过去:
a = [1,2,3,4]
a.pop(1)
print(a)
#[1, 3, 4]
此时 a 列表中下标为 1 的元素已经被删除掉了。
使用 remove 方法
使用 remove 方法 还可以按照值来进行删除:
a = ['aa','bb','cc','dd']
a.remove('cc')
print(a)
#['aa', 'bb', 'dd']
我们这里就把 a 列表中的 ’cc'字符串给删掉了。和之前查找值一样,python编译器要找到这个值所在位置就要遍历一遍这个列表,而这遍历操作也是不需要我们执行的。
连接列表(拼接列表)
使用 + 把两个列表拼接到一起
a = [1,2,3,4]
b = [5,6,7]
print(a + b) #[1, 2, 3, 4, 5, 6, 7]
c = a + b
print(c) #[1, 2, 3, 4, 5, 6, 7]
我可以看到使用 a + b 把 a 和 b 两个列表拼接起来了;
需要注意的是:此处的 + 结果会产生一个新的列表,而不会影响到旧列表的内容。就像上面的 c = a + b 一样;
使用 extend 方法来进行拼接
这个拼接方式是把后一个列表拼接到前一个列表里面,是要修改前一个列表的值的。
a = [1,2,3,4]
b = [5,6,7]
a.extend(b) #把b列表的元素拼接到 a里面去
print(a) #[1, 2, 3, 4, 5, 6, 7]
print(b) #[5, 6, 7]
需要注意的是,这里的extend是一个方法,是直接作用于a的,直接修改a里面的值,不需要创建一个变量来进行拼接操作,也就是说这个extend不创建新的值,他没有返回值。
如果我们用一个变量来接收 extend 那么这个变量的值应该是空的:
a = [1,2,3,4]
b = [5,6,7]
c = a.extend(b) #用一个变量的值来接收extend的值
print(c) #输出 : None
我们发现他会返回一个 None 表示没有值,
关于 None:
这时一个特殊的变量的值,表示"啥都没有“,这个值非常类似于C里面的NULL,和java里面的null这种值。
我们使用extend方法 ,这个方法实际上是没有返回值的,我们上面拿一个变量来接收一个没有返回值的方法的返回值的话,这个变量里面就会是 None 这个值,
使用 += 来进行拼接
a = [1,2,3,4]
b = [5,6,7]
a += b
print(a) #[1, 2, 3, 4, 5, 6, 7]
print(b) #[5, 6, 7]
我们发现 使用 a += b 这个操作非常类似于 我们之前使用的 extend 方法 实现的结果是一样的,但是这两个 拼接的方法 程序所执行的方式是截然不同的。底层的实现细节是差异很大的。
a += b <=> a = a = b ;相当于是:我们先用 a + b 创建了一个更大的列表,然后把这个更大的列表赋值到 a 里面,也就是说,a里面的旧的值都不要了,要的是这个更大的新的列表的值。
a.extend(b)则是直接把b的内容拼接到a的后面,a原来旧的值还是在的,并没有进行修改,只是在此基础上把 b 的值拼接到a 的后面。
Python 中部可以在列表或数组上使用的内建方法 :
append() | 在列表的末尾添加一个元素 |
clear() | 删除列表中的所有元素 |
copy() | 返回列表的副本 |
count() | 返回具有指定值的元素数量。 |
extend() | 将列表元素(或任何可迭代的元素)添加到当前列表的末尾 |
index() | 返回具有指定值的第一个元素的索引 |
insert() | 在指定位置添加元素 |
pop() | 删除指定位置的元素 |
remove() | 删除具有指定值的项目 |
reverse() | 颠倒列表的顺序 |
sort() | 对列表进行排序 |
元祖的操作
元祖的功能和列表相比基本是一致的,
构造元祖:
a = ()
print(type(a)) #<class 'tuple'>
b = tuple()
print(type(b)) #<class 'tuple'>
#创建元祖的时候,指定初始值。
a = (1,2,3,4)
同样的元祖中的元素也可以是任意类型的:
a = (1,2,'hello',True,[1,2,3],(1,2,'world'))
print(a) #(1, 2, 'hello', True, [1, 2, 3], (1, 2, 'world'))
元祖的也是通过下标来访问元祖中的元素,下标也是从0开始,带len - 1结束,也是和列表一样有正负两种逆序的下标,同样也是有下标越界的报错。
a = (1,2,3,4)
print(a[1]) #2
print(a[-1]) #4
print(a[100]) #提示下标越界
元祖的切片操作,遍历操作,查找元素
元祖的切片操作和列表是一样的。
a = (1,2,3,4)
print(a[1:3]) #(2, 3)
同样可以使用for循环来遍历元素
a = (1,2,3,4)
for elem in a :
print(elem)
#1
#2
#3
#4
也是可以使用 in 来判定元素是否存在,使用index 来查找元祖的下标。
a = (1,2,3,4)
print(1 in a) #True
print(a.index(3)) #2
也可以用 + 来拼接两个元祖
a = (1,2,3,4)
b = (4,5,6)
print(a + b) #(1, 2, 3, 4, 4, 5, 6)
元祖和列表的不同
我们发现上述的操作和列表使用过的,同时上述的操作也只是对元祖就像 “读” 这个操作,并没有对元祖进行修改的操作,我们也说过元祖相对于列表是不能进行修改的。而我们上述的 + 拼接只是单纯的创建一个新的元祖 a + b 并没有对 a 和 b 进行修改,比如我们之前使用extend方法在这里就不适用了。
a = (1,2,3,4)
a[2] = 100
print(a)
当我们执行这个代码的时候,就会报错:
意思就是:元祖这个对象他并不支持按照元素的方式进行赋值。
那我们之前对列表的上面 append() pop() 等等这些操作也是不能实现的。
这里我们需要注意的是,我们之前说过python中有一个多元赋值的操作,这个操作的本质上就是按照元祖的方式来进行工作的:
def getPoint():
x = 10
y = 20
return x , y
x , y= getPoint()
print(type(getPoint())) #<class 'tuple'>
换句话说,我们return x,y 其实返回的就是一个存储了x 和 y 的值的元祖,那么我们接收这个返回值的时候就要拿一个元祖来接收,也就是在构建一个新的元祖。
那么元祖有什么用呢?
我们之前说过,再用python进行协调开发的时候,我写完一个函数,提供给另一个人来使用,这个函数是要传参的,那么另一个人在传参的时候,在我写入的函数里面,有可能会把另一个人传入的参数的类型给修改了,那么这个时候,我们使用元祖来作为参数传入到函数里面,就可以避免这样的问题。
元祖是不可变对象,不可变对象是可以哈希的,比如元祖可以作为字典的key键。
字典
字典是一种存储 键值对 的结构,键值对就是计算机中的一个非常重要的概念,其中分为两个概念,一个键(key)一个是值(value)。
根据key能够快速的找到value,这是一种映射关系。例如在学校里面,每一个同学都有一个学号,根据学号我们能找到对应的同学(学号 =》 同学)。
在Python字典中,可以同时包含很多个键值对,我们要求这些键不能重复!
创建字典
a = {}
print(type(a)) #<class 'dict'>
b = dict()
print(type(a)) #<class 'dict'>
我们在创建字典的同时还有可以同时设置初始值
a = {'id':1,'name':'zhangsan'}
上面中 我们使用 “ , ” 来区分不同的键值对, 在每一个键值对里面我们是用 “ :”来区分不同的键和值。如上面这个a字典中,它包含了 2 对键值对,
1)‘id’ : 1 key就是‘id’ , value就是 1
2)‘name’ : ‘zhangsan’ key就是‘name’,value就是 ‘zhangsan’
注意:字典和前面的列表元祖一样,一个字典中的key类型和value类型不一样都一样,他们可以是不同的类型,但是字典对于key是啥类型是有约束的,value的类型没有约束。
我们上面这种写法是有弊端的,当这个字典中的元素过多的时候,不直观,我们一般是用多行的方式来写这个字典中的元素:
a = {
'id':1,
'name':'zhangsan'
}
print(a) #{'id': 1, 'name': 'zhangsan'}
打印出的结果还是这中比较紧凑的格式。
需要注意的是:最后一个键值对的后面的逗号可以写也可以不写。
字典的里的各种操作,都是针对key来进行的!!
新增,删除,获取value,修改value······都是要借助key 来操作的。
查找key
使用 in 运算符来判定key是否存在
a = {
'id':1,
'name':'zhangsan'
}
print('id' in a) #True
print('class' in a) #False
in 只是判断key 是否存在,和 value无关!!!
a = {
'id':1,
'name':'zhangsan'
}
print('zhangsan' in a) #False
我们发现即使这个value在 a 这个字典里是存在的,但是这个是否我们的 in 表达式还是返回的是False。
所以 in 运算符只能判断key是否存在,不能用来判断这个字典中value是否存在。
同样可以使用 not in 来判断这个key是否存在。输出结果也是 in 表达式的逻辑取反值。
使用 [ ] 来根据key获得到 value
上述的 in 操作符只是判断 key 存不存在, 而 [ ] 可以直接将这个key中的 value 的值给取出来:
a = {
'id':1,
'name':'zhangsan'
}
print(a['id']) #1
print(a['name']) #zhangsan
我们在a[ ] 里的 [ ] 中输出需要查找的 key 就可以找到这个 key 对应的 value的值。
当我们在 a[ ] 中输出的key不存在的时候,
a = {
'id':1,
'name':'zhangsan'
}
print(a['class'])
表示当前这个key在字典中不存在。
对于字典来说,使用 in 或者 [ ] 来获取value都是非常高效的;但是对于列表来说,使用 in 比较低效的,而使用 [ ] 是比较高效的。因为在列表中使用 in 的话,他需要把这个列表遍历一遍找到这个值,而字典的背后使用了特殊的数据结构:哈希表;所以速度快上不少。
新增/修改元素
使用 [ ] 可以根据 key 来新增/ 修改 value
如果我们在 [ ] 中输入的key存在,我们在后面就是在修改value值;
如果我们在[ ] 中输入的key不存在,我们在后面就位新增键值对;
a = {
'id':1,
'name':'zhangsan'
}
#这个操作是往字典里插入新的键值对
a['score'] = 90
#这个操作是修改 ‘id’ 这个key的value值
a['id'] = 2
print(a['id']) #2
print(a['soore']) #{'id': 2, 'name': 'zhangsan', 'score': 90}
删除元素
使用 pop 方法 ,根据 key 来删除键值对
a = {
'id':1,
'name':'zhangsan'
}
a.pop('name')
print(a) #{'id': 1}
遍历字典的元素
首先,字典的设计目的不是为了实现遍历,而是为了增删查改,字典是哈希表,进行增删查改的效率都是非常高的,而字典遍历的效率就要差一些。因为哈希表的存在,无论字典中有多少的元素,字典的增删查改操作都是固定时间,不会因为元素多了,操作就慢了。
直接使用for循环
a = {
'id':1,
'name':'zhangsan',
'score':90,
}
for key in a:
print(key,a[key])
#id 1
#name zhangsan
#score 90
这个循环的意思就是在 a 这个字典里面取出所以的key,然后每一次循环取出key之后就打印这个key和value。
注意:我们上面的打印结果看似和我们在定义字典的时候创建的元素顺序是一样,但是在C++/java中,哈希表里面的键值对存储的顺序,是无序的;在python中还不一样,在python里面做了特殊处理,能够保证遍历出来的顺序,就是插入的顺序一致。
也就是说,python中的字典,又不是一个单纯的哈希表;他这里面又是按照哈希表这样的组织键值对,又用了一个类似于队列的结构来控制元素的先进先出。
取出所以key 和 value
~~ 用keys 方法获取到字典中的所有的key
a = {
'id':1,
'name':'zhangsan',
'score':90,
}
print(a.keys())
#dict_keys(['id', 'name', 'score'])
此处返回的结果看起来像列表,但其实不是,他是一个自定义的类型:dict_keys ,它是用来专门表示字典中的所有的key,; 使用的时候也可以把他当做一个列表来使用,用法和列表非常的相似,大部分元祖的基本操作对于这个类型也是同样使用。
~~ 用values 方法取字典中的所有value
#对于上面的a字典
print(a.values())
#dict_values([1, 'zhangsan', 90])
我们发现打印的时候values方法返回的是一个 dict_values 这样的类型,这个类型同样是自定义类型,和dict_keys 类似。
~~ 用items方法可以获取到字典中所有的键值对
#对于上面的a字典
print(a.items())
#dict_items([('id', 1), ('name', 'zhangsan'), ('score', 90)])
我们发现返回的 dict_items 这样一个类型,首先是一个列表一样的结构,然后每一个元素又是一个元祖,元祖里面包含了键和值。
那么针对这个 item方法,我们可以用这个方法来取到这个字典里面全部的键和值,然后利用for循环的针对这个可迭代对象来进行遍历的:
for key,value in a.items():
print(key,value)
#id 1
#name zhangsan
#score 90
这个代码的意思是,我们用一个 key,value 这样一个多元赋值的方法,因为每一次循环取到 a.items () 中的一个元祖,然后把这个元祖中的值多元赋值给 key 和 value,最后我们用print()打印这两个变量。
字典中的合法key类型
我们之前也说过,字典中value的类型是没有规定的,但是key的类型是有规定的,不是所以的类型都可以作为字典的key,字典本质上是一个哈希表,哈希表的key要去的是“可哈希的”,也就是可以计算出一个哈希值,只要能够计算出哈希值的,就可以作为字典的key。
关于计算哈希值:
使用 hash 函数计算某个对象的哈希值
在python中,专门提供了 hash 函数,这个函数就可以用来计算某一个变量的哈希值:
print(hash(0)) #0
print(hash(3.14)) #322818021289917443
print(hash('hello')) #2331932829128770650
print(hash(True)) #1
print(hash((1,2,3))) #529344067295497451
像这些类型都是可以计算哈希值的,别看计算出的大小,只要能计算出值,都属于是可哈希的。
但是有的类型是不能计算哈希值的:
print(hash([1,2,3]))
我们计算这个列表的哈希值,发现编译器直接报错了:
这个报错的意思就是,这个'list'类型是 unhashable 也就是不可哈希的。
一般情况下,不可变对象,一般是可哈希的;而可变的对象一般是不可哈希的。