一、字符串拼接
str(1) + ',' + 'b' # 都转换成字符串拼接到一起
"{}-{}".format(1, 'a') # {}就是填的空,有2个,就使用2个值填充,并依次对应,字符串对象.format
# 在3.6后,可以使用插值
a = 100
b = 'abc'
print(f'{a}-{b}') # 一定要使用f前缀,在大括号中使用变量名
二、内建常用数据结构
- 序列sequence
- 字符串str、字节序列bytes、bytearray
- 列表list、元组tuple
- 键值对
- 集合set、字典dict
三、线性数据结构
线性表
- 线性表:线性表是一种抽象数学概念,是一组元素的序列,由有穷个元素组成。
- 顺序表:使用一大块连续内存顺序存储表中的元素,这样实现的表称为顺序表,或称为连续表。顺序表中,元素的关系使用顺序表的存储顺序自然地表示。
- 链接表: 在存储空间中将分散存储的元素链接起来,这种实现称为链接表,简称为链表。
列表如同地铁站排好的队伍,有序,可以插队、离队,可以索引。
链表如同操场上手拉手的小朋友,有序但排列随意。或者可以想象成一串带线的珠子,随意盘放在桌上。也可以离队、插队,也可以索引。
对比体会一下,这两种数据结构的增删改查。
总结:链表是线性结构,内存分布上看着不连续,但是内部有指向,前一个元素指向下一个元素,所有它是有明确的顺序的。
四、列表list
- 一个排列整齐的队伍,Python采用顺序表实现
- 列表内的个体称作元素,由若干元素组成列表
- 元素可以是任意对象(数字、字符串、对象、列表等)
- 列表内元素有顺序,可以使用索引
- 线性的数据结构
- 使用 [ ] 表示
- 列表是可变的
列表是非常重要的数据结构,对其内存结构和操作方法必须烂熟于心。
1、初始化
-
list() -> new empty list
-
list(iterable) -> new list initialized from iterable's items
-
[]
-
列表不能一开始就定义大小
ls1 = [] # 空列表
ls2 = list() # 空列表,list+()就是创建一个列表
ls3 = [2, 'ab', [3, 'abc'], (5, 30, 50)] # 列表是一个容器,元素可以是其它类型
ls4 = list(range(5)) # 非常常用的构造方式,将一个可迭代对象转换为一个列表,range()返回一个可迭代对象for
# 函数调用,就是在函数名后面加上括号,括号中写上对应的值
ls5 = list(range(5)) # 从range对象中遍历所有元素,组建一个新的列表
ls6 = list([1, 'a', 100]) # 从[1,'a',100]中遍历所有元素,组建一个新的列表
# list()内建函数list执行,返回一个列表对象给你
可遍历,说明你是容器,可索引,说明容器内的元素有序
列表有序,顺序表实现
2、索引
-
索引,也叫下标
-
正索引:从左至右,从0开始,为列表中每一个元素编号
- 如果列表有元素,索引范围[0, 长度-1]
-
负索引:从右至左,从-1开始
- 如果列表有元素,索引范围[-长度, -1]
-
正、负索引不可以超界,否则引发异常IndexError
-
为了理解方便,可以认为列表是从左至右排列的,左边是头部,右边是尾部,左边是下界,右边是 上界
-
列表通过索引访问,list[index] ,index就是索引,使用中括号访问
使用索引定位访问元素的时间复杂度为O(1),这是最快的方式,是列表最好的使用方式。
3、查询
- index(value,[start,[stop]])
- 通过值value,从指定区间查找列表内的元素是否匹配
- 匹配第一个就立即返回索引
- 匹配不到,抛出异常ValueError
- count(value)
- 返回列表中匹配value的次数
- 时间复杂度
- index和count方法都是O(n)
- 随着列表数据规模的增大,而效率下降
- 如何返回列表元素的个数?如何遍历?如何设计高效?
- len()
示例
ls2 = [10, 20, 30, 20, 20, 40]
print(ls2.index(10), ls2.index(20)) # 括号中填写列表中的值返回索引,查询2时返回从左开始搜索到的第一个的索引
0 1
print(ls2.index(20, 2)) # 括号中20为要查询的值,2为列表中从左开始搜索到的第二个,返回索引
3
print(ls2[3]) # 通过索引可以找到该值
20
print(ls2.count(20)) # 查询20在该列表中有几个,遍历所有元素,统计出指定的值出现的次数
3
index和count效率极差,因为他们都需要遍历所有元素,遍历元素都跟当前列表的数据的个数有关,元素越多规模越大,len不会遍历,直接查找属性
4、修改
索引定位元素,然后修改。注意索引不能超界
ls1 = [1, 2, 3, 4]
ls1[2] = 200
print(ls1)
[1, 2, 200, 4]
5、增加单个元素
append(object) -> None
- 列表尾部追加元素,返回None
- 返回None就意味着没有新的列表产生,就地修改
- 定位时间复杂度是O(1),不需要扩容,相当于
ls1[length = value]
insert(index, object) -> None
- 在指定的索引index处插入元素object
- 返回None就意味着没有新的列表产生,就地修改
- 定位时间复杂度是O(n)
索引可以超界
- 超越上界,尾部追加,相当于append(),推荐
- 超越下界,头部追加,相当于插入到首位
例如:
ls1 = [1, 2, 3, 4]
ls1.append(5) # 在内部尾部追加
print(ls1)
[1, 2, 3, 4, 5]
ls1 = [1, 2, 3, 4]
ls1.insert(-1, 10) # 在中间插入
print(ls1)
6、增加多个元素
extend(iteratable) -> None
- 将可迭代对象的元素追加进来,返回None
- 就地修改,本列表自身扩展
+ -> list
- 连接操作,将两个列表连接起来,产生新的列表,原列表不变
- 本质上调用的是魔术方法__add__()方法
* -> list
- 重复操作,将本列表元素重复n次,返回新的列表
print(ls1 = [1] * 5)
[1, 1, 1, 1, 1]
print(ls2 = [None] * 6)
[None, None, None, None, None, None]
print(ls3 = [1, 2] * 3)
[1, 2, 1, 2, 1, 2]
print(ls4 = [[1]] * 3)
[[1], [1], [1]]
这个重复操作看似好用,如果原理掌握不好,但非常危险
y = [[1]] * 3
print(y)
[[1], [1], [1]]
y[0] = 100
print(y)
[100, [1], [1]]
y[1][0] = 200
print(y)
[100, [200], [200]] # 是因为列表中更改元素实际上会把所有所有引用它的元素都改变
在Python中一切皆对象,而对象都是引用类型,可以理解为一个地址指针指向这个对象。
但是,字面常量字符串、数值等表现却不像引用类型,暂时可以称为简单类型。
而列表、元组、字典,包括以后学习的类和实例都可以认为是引用类型。
你可以认为简单类型直接存在列表中,而引入类型只是把引用地址存在了列表中。
例如:
# extend
ls1 = [1, 2, 3, 4]
ls1.extend(range(110, 150, 10)) # 就地尾部后扩展,range中因为前包后不包所以包含4个对象,分别是110,120,130,140,10为步长即110+10+10+10
print(ls1)
[1, 2, 3, 4, 110, 120, 130, 140]
# +
ls1 = [1, 2]
ls2 = [11, 12]
x = ls1 + ls2 # 必须同类型才可以相加
print(x)
[1, 2, 11, 12]
7、删除
remove(value) -> None
-
从左至右查找第一个匹配value的值,找到就移除该元素,并返回None,否则ValueError
-
就地修改
-
效率不高,遍历,O(n),移除到中间的元素,其后所有元素向前挪动
pop([index]) -> item
-
不指定索引index,就从列表尾部弹出一个元素
-
指定索引index,就从索引处弹出一个元素,索引超界抛出IndexError错误
-
使用索引时效率低,只是用索引找的快,移除元素后其后所有元素向前挪动,不指定索引时效率高,在尾部移除最后一个元素
clear() -> None
- 清除列表所有元素,剩下一个空列表
示例
ls1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(ls1.remove(1))
None
print(ls1.pop(3))
5
ls1.clear()
8、反转
reverse() -> None
- 将列表元素反转,返回None
- 就地修改
- 这个方法最好不用,可以倒着读取,都不要反转
ls1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
ls1.reverse()
print(ls1)
[9, 8, 7, 6, 5, 4, 3, 2, 1]
9、排序
sort(key=None, reverse=False) -> None
- 对列表元素进行排序,就地修改,默认升序
- reverse为True,反转,降序
- key一个函数,指定key如何排序,lst.sort(key=function)
- 如果排序是必须的,那么排序。排序效率高吗
ls2 = [3, 4, 5, 7, 2, 8, 6, 1, 10, 9]
ls2.sort(reverse=True) # 纯数字排序,升序
print(ls2)
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
ls2.sort(reverse=False) # 降序
print(ls2)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ls3 = [3, 4, 5, 7, 2, 8, 6, 1, 10, 9, '12']
ls3.sort(key=int)
print(ls3)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '12']
ls3.sort(key=str)
print(ls3)
[1, 10, '12', 2, 3, 4, 5, 6, 7, 8, 9]
10、in成员操作
>>> [3, 4] in [1, 2, 3, [3, 4]]
True
>>> 'a' in ['a', 'b', 'c']
True
for x in [1,2,3,4]:
pass
11、列表复制
a = list(range(4))
b = list(range(4))
print(a == b)
True
c = a
c[2] = 10
print(a)
[0, 1, 10, 3]
print(a == b) # 还相等吗?
False
print(a == c) # 相等吗?
True
问题:
最终a 和 b相等吗?a和b分别存着什么元素
a 和 c 相等吗?为什么? c = a 这一句有复制吗?
下面的程序a和b相等吗?
a = list(range(4))
b = a.copy()
print(a == b)
True
a[2] = 10
print(a == b)
False
a[2] = b[2]
print(a == b)
True
a[1][1] = 100
print(a == b) # 还相等吗? print(a)
True
print(b)
[1, [2, 100, 4], 5]
1、列表的内存模型和深浅拷贝
- shadow copy
- 影子拷贝,也叫浅拷贝。遇到引用类型数据,仅仅复制一个引用而已
- deep copy
- 深拷贝,往往会递归复制一定深度
一般情况下,大多数语言提供的默认复制行为都是浅拷贝。
import copy
a = [1, [2, 3], 4]
b = copy.deepcopy(a)
print(a == b)
True
a[1][1] = 100
print(a == b) # 还相等吗?
False
print(a)
[1, [2, 100], 4]
print(b)
[1, [2, 3], 4]
Python内建数据类型,内部都实现了 == ,它的意思是内容比较
12、常见线性结构比较
- 列表list
这是Python中常用的数据结构,用于存储一系列有序的元素。列表中的元素可以随时被修改或删除。列表可以通过索引来访问、插入或删除元素。
- 链表Linked List
链表是一种数据结构,其中每个元素都包含一个指向下一个元素的引用。链表的主要优点是可以高效地插入和删除元素,但访问元素的效率较低。在Python中,我们通常不会直接使用链表,而是使用列表或其他数据结构。
- 栈Stack
栈是一种后进先出(LIFO)的数据结构,其中最后一个添加到栈中的元素是第一个被移除的元素。Python并没有内置的栈数据结构,但我们可以使用列表来实现栈的功能。可以通过append方法添加元素到栈顶,使用pop方法删除栈顶元素。
- 队列Queue
队列是一种先进先出(FIFO)的数据结构,其中第一个添加到队列中的元素是第一个被移除的元素。Python的内置数据类型列表可以作为队列使用,通过append方法添加元素到队尾,使用pop(0)方法删除队头元素。另外,Python的标准库collections中提供了Queue类,它提供了更高级的队列操作。
总的来说,Python的列表可以作为链表和队列的一种替代,但需要权衡访问速度和插入/删除速度的需求。栈在Python中可以使用列表来实现,但在需要高效的栈操作时,可能需要寻找第三方库或自己实现栈的数据结构。
五、Python内存管理
- 变量无须事先声明,也不需要指定类型,这是动态语言的特性
- 变量只是一个标识符,指向一个对象,而这个对象被创建在内存“堆“中
- Python编程中一般无须关心变量的存亡,一般也不用关心内存的管理
- python使用引用计数记录所有对象的引用数。当对象引用数变为0,它就可以被垃圾回收GC
计数增加:
- 赋值给其它变量就增加引用计数,例如x=3;y=X;z=[x,1]
- 实参传参,如foo(y)
计数减少:
- 函数运行结束时,局部变量就会被自动销毁,对象引用计数减少
- 变量被赋值给其它对象。例如x=3;y=X;x=4
有关性能的时候,就需要考虑变量的引用问题,但是,到底该释放内存还是尽量不释放内存,看需求。
内存是宝贵的,因为它快。但再好的硬件资源,再多的机器,在高并发面前都嫌少。内存一定要合理利用。
但是,数据搬到内存中不易,不要把大量数据好不容易搬到内存中,立刻就不要了。这非常没有效率。
1、引用计数的问题
- 引用计数是简单实现垃圾标记的办法。
- 引用计数可能出现循环引用,Python提供了gc模块,解决了这个问题
2、查看引用计数
import sys
# 列表
x = [] # 新建的空列表[],地址交给了x,引用计数为1
print(sys.getrefcount(x)) # 函数调用时自动加一,用完减一
2
print(sys.getrefcount([])) # 实际[]引用为0,调用完之后,适当的时候,gc清理它
1
y = x
print(sys.getrefcount(y), sys.getrefcount(x))
3 3
x = 5 # 注意字面常量,通常为数值或字符串等,常量处理不同,一旦创建不允许改动,因此没有必要在内存中保存很多份
print(sys.getrefcount(y))
2
print(sys.getrefcount(x))
28
z = 5
print(sys.getrefcount(x))
29
六、随机数
random模块
- randint(a, b) 返回[a, b]之间的整数
- randrange ([start,] stop [,step]) 从指定范围内,按指定基数递增的集合中获取一个随机数,基数 缺省值为1。 random.randrange(1,7,2)
- choice(seq) 从非空序列的元素中随机挑选一个元素,比如random.choice(range(10)),从0到9中 随机挑选一个整数。random.choice([1,3,5,7])
- 3.6开始提供choices,一次从样本中随机选择几个,可重复选择,可以指定权重
- random.shuffle(list) ->None 就地打乱列表元素
- sample(population, k) 从样本空间或总体(序列或者集合类型)中随机取出k个不同的元素,返回 一个新的列表
- random.sample(['a', 'b', 'c', 'd'], 2)
- random.sample(['a', 'a'], 2) 会返回什么结果
- 每次从样本空间采样,在这一次中不可以重复抽取同一个元素
import random
for i in range(10):
print(random.randint(1, 5))
print('-' * 30)
for i in range(10):
print(random.randrange(1, 5))
print('-' * 30)
x = [1, 2, 3, 4, 5]
for i in range(10):
print(random.choice(x))
print('-' * 30)
# 观察下面的0和1的比例
for i in range(10):
print(random.choices([0, 1], k=6)) # k是拿几个,每次都可以重复拿
print('-' * 30)
for i in range(10):
print(random.choices([0, 1], [10, 1], k=6)) # 10比1权重
x = [1, 2, 3, 4, 5]
# 采样
for i in range(5):
print(random.sample(x, 5)) # 从样本中不重复取,k不允许大于样本数
七、元祖tuple
- 一个有序的元素组成的集合
- 使用小括号 ( ) 表示
- 元组是不可变对象
1、初始化
- tuple() -> empty tuple
- tuple(iterable) -> tuple initialized from iterable's items
t1 = () # 空元组
t2 = (1,) # 必须有这个逗号
t3 = (1,) * 5
t4 = (1, 2, 3)
t5 = 1, 'a'
t6 = (1, 2, 3, 1, 2, 3)
t7 = tuple() # 空元组
t8 = tuple(range(5))
t9 = tuple([1, 2, 3])
元组是个容器
2、索引
索引和列表规则一样,不可以超界
3、查询
方法和列表一样,时间复杂度也一样。index、count、len等
4、增删改
元组元素的个数在初始化的时候已经定义好了,所以不能为元组增加元素、也不能从中删除元素、也不能修改元素的内容。
但是要注意下面这个例子
t1 = ([1]) * 3 # 相当于t1 = [1] * 3
t1[1] = 100 # 这个例子定义的不是元组,而是列表
print([t1])
[[1, 100, 1]]
# 注意下面的例子
t2 = ([1],) * 3 # [1]加了,就成了元组
print(t2)
t2[1] = 100 # 元组不支持项目赋值
t2[0][0] = 100
print(t2)
八、字符串str
- 一个个字符组成的有序的序列,是字符的集合
- 使用单引号、双引号、三引号引住的字符序列
- 字符串是不可变对象,是字面常量
Python3起,字符串都是Unicode类
1、初始化
s1 = 'string'
s2 = "string2"
s3 = '''this's a "String" '''
s4 = 'hello \n magedu.com'
s5 = r"hello \n magedu.com"
s6 = 'c:\windows\nt'
s7 = R"c:\windows\nt"
s8 = 'c:\windows\\nt'
name = 'tom'; age = 20 # python代码写在一行,使用分号隔开,不推荐
s9 = f'{name}, {age}' # 3.6支持f前缀
sql = """select * from user where name='tom' """
r前缀:所有字符都是本来的意思,没有转义
f前缀:3.6开始,使用变量插值
2、索引
字符串是序列,支持下标访问。但不可变,不可以修改元素。
sql = "select * from user where name='tom'"
print(sql[4]) # 字符串'c'
sql[4] = 'o' # 不可以
3、连接
+加号
- 将2个字符串连接起来
- 返回一个新的字符串
join方法
- sep.join(iterable)
- 使用指定字符串作为分隔符,将可迭代对象中字- 符串使用这个分隔符拼接起来
- 可迭代对象必须是字符串
- 返回一个新的字符串
x = 'ab'
x = x + 'cd'
print(','.join(x))
print('\t'.join(x))
print('\n'.join(x))
print('-'.join(range(5))) # join 拼接必须是字符串类型
4、字符查找
find(sub[, start[, end]]) -> int
- 在指定的区间[start, end),从左至右,查找子串sub
- 找到返回正索引,没找到返回-1
rfind(sub[, start[, end]]) -> int
- 在指定的区间[start, end),从右至左,查找子串sub
- 找到返回正索引,没找到返回-1
s = 'magedu.edu'
print(s.find('edu'))
print(s.find('edu', 3))
print(s.find('edu', 4))
print(s.find('edu', 6, 9))
print(s.find('edu', 7, 20))
print(s.find('edu', 200))
s = 'magedu.edu'
print(s.rfind('edu'))
print(s.rfind('edu', 3))
print(s.rfind('edu', 4))
print(s.rfind('edu', 6, 9))
print(s.rfind('edu', 7, 20))
print(s.rfind('edu', 200))
这两个方法只是找字符串的方向不同,返回值一样。找到第一个满足要求的子串立即返回。特别注意返回值,找不到返回的是负数-1。
这两个方法效率高吗?要不要用?
这两个方法效率真不高,都是在字符串中遍历搜索,但是如果找子串工作必不可少,那么必须这么做, 但是能少做就少做。
- index(sub[, start[, end]]) -> int
- 在指定的区间[start, end),从左至右,查找子串sub
- 找到返回正索引,没找到抛出异常ValueError
- rindex(sub[, start[, end]]) -> int
- 在指定的区间[start, end),从左至右,查找子串sub
- 找到返回正索引,没找到抛出异常ValueError
index方法和find方法很像,不好的地方在于找不到抛异常。推荐使用find方法
s = 'magedu.edu'
print(s.index('edu'))
print(s.index('edu', 3))
print(s.index('edu', 4))
# print(s.index('edu', 6, 9)) # 抛异常
print(s.index('edu', 7, 20))
# print(s.index('edu', 200)) # 抛异常
- count(sub[, start[, end]]) -> int
- 在指定的区间[start, end),从左至右,统计子串sub出现的次数
s = 'magedu.edu'
print(s.count('edu'))
print(s.count('edu', 4))
- 时间复杂度
- find、index和count方法都是O(n)
- 随着字符串数据规模的增大,而效率下降
- len(string)
- 返回字符串的长度,即字符的个数
5、分割
split(sep=None, maxsplit=-1) -> list of -strings
- 从左至右
- sep 指定分割字符串,缺省的情况下空白字符串作为分隔符
- maxsplit 指定分割的次数,-1 表示遍历整个字符串
- 立即返回列表
示例
a = '1,2,3,a,b,c'
print(a.split()) # 从左到右切割,立即返回一个列表
['1,2,3,a,b,c']
print(a.split(','))
['1', '2', '3', 'a', 'b', 'c']
print(a.split('3')) # 找到分隔符时切断
['1,2,', ',a,b,c']
b = '\n\r\t\n a\nb\tc\t\n' # 空格、换行符('\n')、回车符('\r')和制表符('\t')都是空白字符,它们作为默认的分割符。
print(b.split()) # 连续的空白字符会被看作一个整体,作为分割符进行字符串的切割
['a', 'b', 'c']
print(b.split('\n')) # 指定了切割符
['', '\r\t', ' a', 'b\tc\t', '']
print(b.rsplit()) # 从右往左切割
['a', 'b', 'c']
print(b.rsplit('\n'))
['', '\r\t', ' a', 'b\tc\t', '']
print(b.split('\n', 2))
['', '\r\t', ' a\nb\tc\t\n']
print(b.rsplit('\n', 2))
['\n\r\t\n a', 'b\tc\t', '']
print(b.splitlines()) # 按照换行符切割
['', '', '\t', ' a', 'b\tc\t']
print(b.splitlines(True)) # 是否保留切割掉的换行符
['\n', '\r', '\t\n', ' a\n', 'b\tc\t\n']
rsplit(sep=None, maxsplit=-1) -> list of strings
- 从右向左开始切,但是输出的字符串字符不会反
- sep 指定分割字符串,缺省的情况下空白字符串作为分隔符
- maxsplit 指定分割的次数,-1 表示遍历整个字符串
- 立即返回列表
splitlines([keepends]) -> list of strings -
- 按照行来切分字符串
- keepends 指的是是否保留行分隔符
- 行分隔符包括\n、\r\n、\r等
partition(sep) -> (head, sep, tail)
- 从左至右,遇到分隔符就把字符串分割成两部分,返回头、分隔符、尾三部分的三元组
- 如果没有找到分隔符,就返回头、2个空元素的三元组
- sep 分割字符串,必须指定
rpartition(sep) -> (head, sep, tail)
- 从右至左,遇到分隔符就把字符串分割成两部分,返回头、分隔符、尾三部分的三元组
- 如果没有找到分隔符,就返回2个空元素和尾的三元组
s = ','.join('abcd')
print(s.partition(',')) # 立即返回三元组
('a', ',', 'b,c,d')
print(s.partition('.'))
('a,b,c,d', '', '')
print(s.rpartition(','))
('a,b,c', ',', 'd')
print(s.rpartition('.'))
('', '', 'a,b,c,d')
6、替换
replace(old, new[, count]) -> str
字符串中找到匹配替换为新子串,返回新字符串
count表示替换几次,不指定就是全部替换
s = ','.join('abcd')
print(s.replace(',', ' ')) # 字符串替换不是就地替换,返回新的字符串
a b c d
print(s.replace(',', ' ', 2))
a b c,d
s1 = 'www.magedu.edu'
print(s1.replace('w', 'a'))
aaa.magedu.edu
print(s1.replace('ww', 'a'))
aw.magedu.edu
print(s1.replace('www', 'a'))
a.magedu.edu
7、移除
-
strip([chars]) -> str
- 在字符串两端去除指定的字符集chars中的所有字符
- 如果chars没有指定,去除两端的空白字符
-
lstrip([chars]) -> str ,从左开始
-
rstrip([chars]) -> str,从右开始
s = '\t\r\na b c,d\ne\n\t'
print(s.strip())
a b c,d
e
print(s.strip('\t\n'))
a b c,d
e
print(s.strip('\t\ne\r'))
a b c,d
8、首位判断
- endswith(suffix[, start[, end]]) -> bool
- 在指定的区间[start, end),字符串是否是suffix结尾
- startswith(prefix[, start[, end]]) -> bool
- 在指定的区间[start, end),字符串是否是prefix开头
s = "www.magedu.edu"
print(s.startswith('ww'))
True
print(s.startswith('e', 7))
True
print(s.startswith('e', 10))
False
print(s.startswith('edu', 11))
True
print(s.endswith('edu'))
True
9、其他函数
- upper()大写
- lower()小写 swapcase() 交换大小写
- isalnum() -> bool 是否是字母和数字组成
- isalpha() 是否是字母
- isdecimal() 是否只包含十进制数字
- isdigit() 是否全部数字(0~9)
- isidentifier() 是不是字母和下划线开头,其他都是字母、数字、下划线
- islower() 是否都是小写
- isupper() 是否全部大写
- isspace() 是否只包含空白字符
其他格式打印函数中文几乎不用,大家自行查看帮助
10、格式化
简单的使用+或者join也可以拼接字符串,但是需要先转换数据到字符串后才能拼接。
1、C风格printf-style
- 占位符:使用%和格式字符,例如%s、%d
- 修饰符:在占位符中还可以插入修饰符,例如%03d
- format % values
- format是格式字符串,values是被格式的值
- 格式字符串和被格式的值之间使用%
- values只能是一个对象,可以是一个值,可以是一个元素个数和占位符数目相等的元组,也 可以是一个字典
"I am %03d" % (20,)
'I like %s.' % 'Python'
"%3.2f%% 0x%x %#X" % (89.7654, 10, 256) # 宽度为3,小数点后2位
"I am %-5d" % (20,)
"%(host)s.%(domain)s" % {'domain':'magedu.com', 'host':'www'} # 靠名字对应
2、format函数
Python2.5之后,字符串类型提供了format函数,功能更加强大,鼓励使用。
"{} {xxx}".format(*args, **kwargs) -> str
- args是可变的位置参数
- kwargs是可变关键字参数,写作a=100
- 使用花括号作为占位符
- {}表示按照顺序匹配位置参数,{n}表示取位置参数索引为n的值
- {xxx}表示在关键字参数中搜索名称一致的
- {{}} 表示打印花括号
# 位置对应
"{}:{}".format('127.0.0.1', 8080)
# 位置或关键字对应
"{server} {1}:{0}".format(8080, '127.0.0.1', server='Web Server Info: ')
# 访问元素
"{0[0]}.{0[1]}".format(('magedu', 'com'))
# 进制
"{0:d} {0:b} {0:o} {0:x} {0:#X}".format(31)
# 浮点数
print("{}".format(3 ** 0.5)) # 1.7320508075688772
print("{:f}".format(3 ** 0.5)) # 1.732051,精度默认6 print("{:10f}".format(3**0.5)) # 右对齐,宽度10
print("{:2}".format(102.231)) # 宽度为2数字
print("{:2}".format(1)) # 宽度为2数字
print("{:.2}".format(3 ** 0.5)) # 1.7 2个数字
print("{:.2f}".format(3 ** 0.5)) # 1.73 小数点后2位
print("{:3.2f}".format(3 ** 0.5)) # 1.73 宽度为3,小数点后2位 print("{:20.3f}".format(0.2745)) # 0.275
print("{:3.3%}".format(1 / 3)) # 33.333%
# 注意宽度可以被撑破
对齐
print("{}*{}={}".format(5, 6, 5 * 6))
5*6=30
print("{}*{}={:2}".format(5, 6, 5 * 6))
5*6=30
print("{1}*{0}={2:3}".format(5, 6, 5 * 6))
6*5= 30
print("{1}*{0}={2:0>3}".format(5, 6, 5 * 6))
6*5=030
print("{}*{}={:#<3}".format(4, 5, 20))
4*5=20#
print("{:#^7}".format('*' * 3))
##***##
九、编码与解码
- 编码:str => bytes,将字符串这个字符序列使用指定字符集encode编码为一个个字节组成的序列 bytes
- 解码:bytes或bytearray => str,将一个个字节按照某种指定的字符集解码为一个个字符串组成的 字符串
print("abc".encode()) # 缺省为utf-8编码
b'abc'
print("啊".encode('utf-8'))
b'\xe5\x95\x8a'
print("啊".encode('gbk'))
b'\xb0\xa1'
print(b'abc'.decode('utf8'))
abc
print(b'\xb0\xa1'.decode('gbk'))
a
十、ASCII
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套单字节编码系统
熟记:
- \x00 表中第一项,C语言中的字符串结束符
- \t \x09 tab字符
- \r\n \x0d\x0a
- \x30~\x39 字符0~9,\x31 字符1
- \x41 65 A
- \x61 97 a
注意:这里的1指定是字符1,不是数字1
UTF-8、GBK都兼容了ASCII
十一、字节序列
Python3 引入两个新的类型bytes、bytearray。
bytes不可变字节序列;bytearray是可变字节数组。
1、Bytes
1、Bytes初始化
- bytes() 空bytes
- bytes(int) 指定字节的bytes,被0填充
- bytes(iterable_of_ints) -> bytes [0,255]的int组成的可迭代对象
- bytes(string, encoding[, errors]) -> bytes 等价于string.encode()
- bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer 从一个字节序列或者buffer复制 出一个新的不可变的bytes对象
- 使用b前缀定义
- 只允许基本ASCII使用字符形式b'abc9'
- 使用16进制表示b"\x41\x61"
b1 = bytes() # 空字节,不可变类型,不可修改
print(b1)
b''
b2 = b''
print(b2)
b''
b3 = bytes(5)
print(b3)
b'\x00\x00\x00\x00\x00'
b4 = bytes('abc', 'utf-8') # 同b'abc'或者'abc'.encode(gbk)
print(b4)
b'abc'
2、索引
print(b'abcd'[2]) # 返回int,指定是本字节对应的十进制数
99
3、bytes操作方法
bytes和str类似,它们都是不可变类型,操作方法几乎一样。只不过要注意的是,str的方法操作的是str类型数据,而bytes的方法操作的是bytes类型数据。
b'abcdef'.replace(b'f', b'z')
b'abc'.find(b'c')
4、类方法构造
bytes就是字节序列,最好书写的方式就是16进制的字符串表达,例如'61626a6b',这个字符串中的空格将被忽略。
#还没有bytes对象,由一个字符串构造
bytes.fromhex('616263 6a 6b')
5、十六进制表达
一个bytes对象,就是一个个字节的序列,完全可以把每个字节用16进制表示。
'abcde'.encode().hex() # 返回字符串
2、bytearray
1、bytearray初始化
- bytearray() 空bytearray
- bytearray(int) 指定字节的bytearray,被0填充
- bytearray(iterable_of_ints) -> bytearray [0,255]的int组成的可迭代对象
- bytearray(string, encoding[, errors]) -> bytearray 近似string.encode(),不过返回可变对象
- bytearray(bytes_or_buffer) 从一个字节序列或者buffer复制出一个新的可变的bytearray对象
b前缀表示的是bytes,不是bytearray类型
2、索引
print(bytearray(b'abcd')[2]) # 返回int,指定是本字节对应的十进制数
3、bytearray操作方法
和bytes类似。
b'abcdef'.replace(b'f', b'z')
b'abc'.find(b'c')
4、类方法构造
bytearray就是字节数组,最好书写的方式就是16进制的字符串表达,例如'6162 6a 6b',这个字符串中的空格将被忽略。
# 还没有bytearray对象,由一个字符串构造
x = bytearray.fromhex('616263 6a 6b')
x.append(49)
5、十六进制表达
一个bytearray对象,就是一个个字节的序列,完全可以把每个字节用16进制表示。
x = bytearray.fromhex('616263 6a 6b')
x.append(49)
print(x.hex()) # 返回字符串
6、更多操作方法
由于bytearray类型是可变数组,所以,又类似列表。
- append(int) 尾部追加一个元素
- insert(index, int) 在指定索引位置插入元素
- extend(iterable_of_ints) 将一个可迭代的整数集合追加到当前bytearray
- pop(index=-1) 从指定索引上移除元素,默认从尾部移除
- remove(value) 找到第一个value移除,找不到抛ValueError异常
- 注意:上述方法若需要使用int类型,值在[0, 255]
- clear() 清空bytearray
- reverse() 翻转bytearray,就地修改
b = bytearray()
b.append(97)
b.append(99)
b.insert(1, 98)
b.extend([65, 66, 67])
b.remove(66)
b.pop()
b.reverse()
print(b) # 输出 bytearray(b'Acba')
b.clear()
3、字节序
内存中对于一个超过一个字节数据的分布方式。
上图,内存地址向右越大
- 大端模式,big-endian;小端模式,little-endian
- Intel X86 CPU使用小端模式
- 网络传输更多使用大端模式
- Windows、Linux使用小端模式
- Mac OS使用大端模式
- Java虚拟机是大端模式
4、int和bytes互转
int.from_bytes(bytes,byteorder)
- 按照指定字节序,将一个字节序列表示成整数
int.to_bytes(length,byteorder)
- 按照指定字节序,将一个整数表达成一个指定长度的字节序列
i = int.from_bytes(b'abcd', 'big') # 大端模式
print(i, hex(i))
1633837924 0x61626364
print(i.to_bytes(4, 'big'))
b'abcd'
b = bytearray()
b.extend(range(0x61, 100))
b.append(100)
print(b)
bytearray(b'abcd')
十二、线性结构
线性结构特征:
- 可迭代for...in
- 有长度,通过len(x)获取,容器
- 通过整数下标可以访问元素。正索引、负索引
- 可以切片
1、切片
sequence[start:stop]
sequence[start:stop:step]
- 通过给定的索引区间获得线性结构的一部分数据
- start、stop、step为整数,可以是正整数、负整数、零
- start为0时,可以省略
- stop为末尾时,可以省略
- step为1时,可以省略
- 切片时,索引超过上界(右边界),就取到末尾;超过下界(左边界),取到开头
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[:])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[:-1]) # 去尾
[0, 1, 2, 3, 4, 5, 6, 7, 8]
print(x[0:])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[3:])
[3, 4, 5, 6, 7, 8, 9]
print(x[3:-1]) #
[3, 4, 5, 6, 7, 8]
print(x[9:])
[9]
print(x[:9])
[0, 1, 2, 3, 4, 5, 6, 7, 8]
print(x[9:-1])
[]
print(x[:100])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[-100:])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[4:-2])
[4, 5, 6, 7]
print(x[-4:-2])
[6, 7]
print('0123456789'[-4:8])
67
print(b'0123456789'[-4:8])
b'67'
print(bytearray(b'0123456789')[-10:5])
bytearray(b'01234')
步长:
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[::])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[::2])
[0, 2, 4, 6, 8]
print(x[2:8:3])
[2, 5]
print(x[:9:3])
[0, 3, 6]
print(x[1::3])
[1, 4, 7]
print(x[-10:8:2])
[0, 2, 4, 6]
起止和方向
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[-10:])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(x[-5:6])
[5]
print(x[-5:-6])
[]
print(x[6:5])
[]
print(x[5:5])
[]
print(x[1:9:-2])
[]
print(x[::-2])
[9, 7, 5, 3, 1]
print(x[8::-2])
[8, 6, 4, 2, 0]
print(x[8:2:-2])
[8, 6, 4]
print(x[8:-10:2])
[]
print(x[8:-10:-2])
[8, 6, 4, 2]
print(x[-5:4:-1])
[5]
print(x[-5:5:-1])
[]
在序列上使用切片[start:stop],子区间索引范围[start, stop),相当于从start开始指向stop的方向上获 取数据
默认step为1,表示向右;步长为负数,表示向左
如果子区间方向和步长方向不一致,直接返回当前类型的"空对象"
如果子区间方向和步长方向一致,则从起点间隔步长取值
内建函数 | 函数签名 | 说明 |
---|---|---|
id | id(object) | CPython中返回对象的内存地址 |
可以用来判断是不是同一个对象
# 使用id看地址,要注意地址回收复用问题
print(id([1,2,3]))
1602228056960
print(id([4,5,6,7]))
1602228056960
# 上下两句可能内存地址一样,但是上面那个[1,2,3]没有意义,因为它用完之后,引用计数为0了,没人能再次访问到,释放了内存
# 如果2个存在在内存中的对象,地址一样一定是同一个对象
2、本质
x = [0, 1, 2]
y = x[:]
print(x, y)
[0, 1, 2] [0, 1, 2]
print(id(x), id(y))
1727446709120 1727446675648
x[0] = 100
print(x, y)
[100, 1, 2] [0, 1, 2]
x = [[1]]
y = x[:]
print(x, y)
[[1]] [[1]]
print(x == y)
True
print(id(x), id(y), x is y)
1727446983360 1727446709120 False
x[0][0] = 100
print(x, y)
[[100]] [[100]]
print(x == y)
True
print(x is y)
False
x[0] = 200
print(x == y)
False
print(x, y)
[200] [[100]]
上例可知,实际上切片后得到一个全新的对象。 [:] 或 [::] 相当于copy方法
3、切片赋值
- 切片操作写在了等号左边
- 被插入的可迭代对象写在等号右边
x = [0, 1, 2]
z = None
z = 1
z[:] = x # 不可以
x = [0, 1, 2, 3, 4]
z = list()
z[:] = x
z[1:2] = 10 # 不可以
z[1:2] = (10,)
z[3:] = (20,)
z[1:] = (40, 50 ,60, 70)
z[1:-1] = ()
x = [0, 1, 2, 3, 4]
y = []
y[:] = x # 用来复制列表x的内容到列表y中。这种方式是复制了x的值,而不是引用。也就是说,此时y是x的一个浅拷贝。此时,如果你修改y的内容,x的内容不会被改变,反之亦然
print(x == y)
True
print(id(x), id(y), x is y)
2558097879936 2558097846464 False
m = x # 创建一个新的引用m,指向x所指向的列表。也就是说,m和x现在引用同一个列表。所以,如果你通过m修改了列表的内容,那么x看到的列表内容也会被改变,因为他们引用的是同一个列表
m 和 x它们两个变量指向同一个对象。
y=[]、y[:]=x等价于 z=x[:],都是创建x的副本。
切片赋值用作初始化相当于copy,还可以使用。如果用在替换、插入元素,看似语法比较简洁,但是由 于列表是顺序表结构,将会引起数据的挪动,这非常影响性能,应当尽量避免使用。
十三、集合set
集合,简称集。由任意个元素构成的集体。高级语言都实现了这个非常重要的数据结构类型。
Python中,它是可变的、无序的、不重复的元素的集合
1、初始化
- set() -> new empty set object
- set(iterable) -> new set object
s1 = set()
s2 = set(range(5))
s3 = set([1, 2, 3]) # s3 = {1, 2, 3}
s4 = set('abcdabcd')
s5 = {} # 这是什么?这是字典
s6 = {1, 2, 3}
s7 = {1, (1,)}
s8 = {1, (1,), [1]} # ?
2、元素性质
- 去重:在集合中,所有元素必须相异
- 无序:因为无序,所以不可索引
- 可哈希:Python集合中的元素必须可以hash,即元素都可以使用内建函数hash
- 目前学过不可hash的类型有:list、set、bytearray
- 可迭代:set中虽然元素不一样,但元素都可以迭代出来
3、增加
- add(elem)
- 增加一个元素到set中
- 如果元素存在,什么都不做
- update(*others)
- 合并其他元素到set集合中来
- 参数others必须是可迭代对象
- 就地修改
s = set()
s.add(1)
s.update((1, 2, 3), [2, 3, 4])
print(s)
{1, 2, 3, 4}
4、删除
- remove(elem)
- 从set中移除一个元素
- 元素不存在,抛出KeyError异常。
- discard(elem)
- 从set中移除一个元素
- 元素不存在,什么都不做
- pop() -> item
- 移除并返回任意的元素。为什么是任意元素?
- 空集返回KeyError异常
- clear()
- 移除所有元素
s = set(range(10))
s.remove(0)
# s.remove(11) # KeyError意思是找不到唯一不重复的值
s.discard(11) # 有就清除,没有并不报错
s.pop()
s.clear()
5、修改
集合类型没有修改。因为元素唯一。如果元素能够加入到集合中,说明它和别的元素不一样。
所谓修改,其实就是把当前元素改成一个完全不同的元素,就是删除加入新元素。
6、索引
非线性结构,不可索引。
7、遍历
只要是容器,都可以遍历元素。但是效率都是O(n)
8、成员运算符in
print(10 in [1, 2, 3])
False
print(10 in {1, 2, 3})
False
上面2句代码,分别在列表和集合中搜索元素。如果列表和集合的元素都有100万个,谁的效率高?
- 如果内存中有线程列表,你需要做的操作,里面用到了遍历,0(n),n规模越大,效率越低下。微秒-》毫秒。
- 如果内存中有集合,不管数据规模多大,它的检索元素的时间,不随着规模变化,O(1),效率极高。
1、IPython魔术方法
IPython内置的特殊方法,使用%百分号开头的
- % 开头是line magic
- %% 开头是 cell magic,notebook的cell
%timeit statement
-n 一个循环loop执行语句多少次
-r 循环执行多少次loop,取最好的结果
%%timeit setup_code
* code.....
# 下面写一行,列表每次都要创建,这样写不好
%timeit (-1 in list(range(100)))
# 下面写在一个cell中,写在setup中,列表创建一次
%%timeit l=list(range(1000000))
-1 in l
2、set和线性结构比较
结果说明,集合性能很好。为什么?
- 线性数据结构,搜索元素的时间复杂度是O(n),即随着数据规模增加耗时增大
- set、dict使用hash表实现,内部使用hash值作为key,时间复杂度为O(1),查询时间和数据规模 无关,不会随着数据规模增大而搜索性能下降。
9、可哈希
- 数值型int、float、complex
- 布尔型True、False
- 字符串string、bytes
- tuple
- None
- 以上都是不可变类型,称为可哈希类型,hashable
set元素必须是可hash的。
10、集合概念
- 全集
- 所有元素的集合。例如实数集,所有实数组成的集合就是全集
- 子集subset和超集superset
- 一个集合A所有元素都在另一个集合B内,A是B的子集,B是A的超集
- 真子集和真超集
- A是B的子集,且A不等于B,A就是B的真子集,B是A的真超集
- 并集:多个集合合并的结果
- 交集:多个集合的公共部分
- 差集:集合中除去和其他集合公共部分
11、并集
将两个集合A和B的所有的元素合并到一起,组成的集合称作集合A与集合B的并集
union(*others)
返回和多个集合合并后的新的集合|
运算符重载,等同union
update(*others)
和多个集合合并,就地修改|=
等同update
a = {1, 2, 3}
b = {2, 3, 4, 5}
print(a.union(b))
{1, 2, 3, 4, 5}
print(a.update({1, 2, 3}, [range(10, 11)]))
None
print(a)
{1, 2, 3, range(10, 11)}
a |= {10, 11}
print(a)
{1, 2, 3, 10, 11, range(10, 11)}
12、交集
集合A和B,由所有属于A且属于B的元素组成的集合
intersection(*others)
返回和多个集合的交集&
等同intersection
intersection_update(*others)
获取和多个集合的交集,并就地修改- &= 等同
intersection_update
a = {1, 2, 3}
b = {2, 3, 4, 5}
c = {'a', 'b'}
print(a & b)
{2, 3}
print(a & c)
set() # 空集显示set()
c &= a
print(c)
set()
13、差集
集合A和B,由所有属于A且不属于B的元素组成的集合
difference(*others)
返回和多个集合的差集-
等同difference
difference_update(*others)
获取和多个集合的差集并就地修改-=
等同difference_update
a = {1, 2, 3}
b = {2, 3, 4, 5}
print(a - b)
{1}
print(b - a)
{4, 5}
14、对称差集
集合A和B,由所有不属于A和B的交集元素组成的集合,记作(A-B)∪(B-A)
symmetric_differece(other)
返回和另一个集合的对称差集^
等同symmetric_differece
symmetric_differece_update(other)
获取和另一个集合的对称差集并就地修改^=
等同symmetric_differece_update
a = {1, 2, 3}
b = {2, 3, 4, 5}
print(a ^ b)
{1, 4, 5}
15、其他集合运算
issubset(other)
、<=
判断当前集合是否是另一个集合的子集set1 < set2
判断set1是否是set2的真子集issuperset(other)
、>=
判断当前集合是否是other的超集set1 > set2
判断set1是否是set2的真超集isdisjoint(other)
当前集合和另一个集合没有交集,没有交集,返回True
练习:
- 一个总任务列表,存储所有任务。一个已完成的任务列表。找出为未完成的任务
业务中,任务ID一般不可以重复
所有任务ID放到一个set中,假设为ALL
所有已完成的任务ID放到一个set中,假设为COMPLETED,它是ALL的子集 ALL - COMPLETED => UNCOMPLETED
集合运算,用好了妙用无穷.
十四、字典Dict
Dict即Dictionary,也称为mapping。
Python中,字典由任意个元素构成的集合,每一个元素称为Item,也称为Entry。这个Item是由(key, value)组成的二元组。
字典是可变的、无序的、key不重复的key-value键值对集合。
1、初始化
dict(**kwargs)
使用name=value对初始化一个字典dict(iterable, **kwarg)
使用可迭代对象和name=value对构造字典,不过可迭代对象的元素 必须是一个二元结构dict(mapping, **kwarg)
使用一个字典构建另一个字典
字典的初始化方法都非常常用,都需要会用
d1 = {}
d2 = dict() # 空字典
d3 = dict(a=100, b=200)
d4 = dict(d3) # 构造另外一个字典
d5 = dict(d4, a=300, c=400)
d6 = dict([('a', 100), ['b', 200], (1, 'abc')], b=300, c=400)
# 类方法dict.fromkeys(iterable, value)
d = dict.fromkeys(range(5))
d = dict.fromkeys(range(5), 0)
2、元素访问
- d[key]
- 返回key对应的值value
- key不存在抛出KeyError异常
- get(key[, default])
- 返回key对应的值value
- key不存在返回缺省值,如果没有设置缺省值就返回None
- setdefault(key[, default])
- 返回key对应的值value
- key不存在,添加kv对,value设置为default,并返回default,如果default没有设置,缺省 为None
3、新增和修改
- d[key] = value
- 将key对应的值修改为value
- key不存在添加新的kv对
- update([other]) -> None
- 使用另一个字典的kv对更新本字典
- key不存在,就添加
- key存在,覆盖已经存在的key对应的值
- 就地修改
d = {}
d['a'] = 1
d.update(red=1)
# d.update(['red',2]) # 不能直接传递一个列表
d.update({'red': 3})
4、删除
- pop(key[, default])
- key存在,移除它,并返回它的value
- key不存在,返回给定的default
- default未设置,key不存在则抛出KeyError异常
- popitem()
- 移除并返回一个任意的键值对
- 字典为empty,抛出KeyError异常
- clear()
- 清空字典
# del语句
del d # 删除d标识符,d指向的对象引用数减一,如果引用计数为0,被gc回收清理
del d[1] # 删除d标识符中key为1的对象
d = dict([('a', 100), ['b', 200], (1, 'abc')], b=300, c=400)
print(d)
{'a': 100, 'b': 300, 1: 'abc', 'c': 400}
print(d.pop('x', 1000)) # key不存在,返回1000
1000
print(d.pop(1, 2000)) # key存在,移除并返回对应value对象
abc
print(d.popitem()) # 随机弹出item,但不能为空
('c', 400)
5、遍历
k=key,d=dict
1、遍历Key
d = dict([('a', 100), ['b', 200], (1, 'abc')], b=300, c=400)
for k in d:
print(k)
# 推荐
for k in d.keys():
print(k)
2、遍历Value
for v in d.values():
print(v)
for k in d.keys():
print(d[k])
print(d.get(k))
print(d.setdefault(k))
3、遍历Item
for item in d.items():
print(item)
print(item[0], item[1])
# 推荐
for k, v in d.items():
print(k, v)
for k, _ in d.items():
print(k)
for _, v in d.items():
print(v)
Python3中,keys、values、items方法返回一个类似一个生成器的可迭代对象
- Dictionary view对象,可以使用len()、iter()、in操作
- 字典的entry的动态的视图,字典变化,视图将反映出这些变化
- keys返回一个类set对象,也就是可以看做一个set集合。如果values都可以hash,那么items也可 以看做是类set对象
Python2中,上面的方法会返回一个新的列表,立即占据新的内存空间。所以Python2建议使用 iterkeys、itervalues、iteritems版本,返回一个迭代器,而不是返回一个copy
6、遍历与删除
# 错误的做法
d = dict(a=1, b=2, c=3)
for k, v in d.items(): # 正在遍历期间,不许改变字典的长度,lv不许增减
print(d.pop(k))
在使用keys、values、items方法遍历的时候,不可以改变字典的size
while len(d):
print(d.popitem())
while d:
print(d.popitem())
上面的while循环虽然可以移除字典元素,但是很少使用,不如直接clear。
# for 循环正确删除
d = dict(a=1, b=2, c=3)
keys = []
for k, v in d.items():
keys.append(k)
for k in keys:
d.pop(k)
7、key
字典的key和set的元素要求一致
- set的元素可以就是看做key,set可以看做dict的简化版
- hashable 可哈希才可以作为key,可以使用hash()测试
- 使用key访问,就如同列表使用index访问一样,时间复杂度都是O(1),这也是最好的访问元素的方式
d = {
1: 0,
2.0: 3,
"abc": None,
('hello', 'world', 'python'): "string",
b'abc': '135'
}
8、有序性
字典元素是按照key的hash值无序存储的。
但是,有时候我们却需要一个有序的元素顺序,Python 3.6之前,使用OrderedDict类可以做到,3.6开始dict自身支持。到底Python对一个无序数据结构记录了什么顺序?
# 3.5如下
Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> d = {'a':300, 'b':200, 'c':100, 'd':50}
>>> d
{'b': 200, 'c': 100, 'a': 300, 'd': 50}
>>> d
{'b': 200, 'c': 100, 'a': 300, 'd': 50}
>>> list(d.keys())
['b', 'c', 'a', 'd']
>>> exit()
Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> d = {'a':300, 'b':200, 'c':100, 'd':50}
>>> d
{'d': 50, 'c': 100, 'a': 300, 'b': 200}
Python 3.6之前,在不同的机器上,甚至同一个程序分别运行2次,都不能确定不同的key的先后顺序
# 3.6+表现如下
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>> d = {'a':300, 'b':200, 'c':100, 'd':50}
>>> d
{'a': 300, 'b': 200, 'c': 100, 'd': 50}
>>> exit()
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>> d = {'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d
{'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d.keys()
dict_keys(['c', 'a', 'b', 'd'])
Python 3.6+,记录了字典key的录入顺序,遍历的时候,就是按照这个顺序,和key的排序是两回事。
如果使用d = {'a':300, 'b':200, 'c':100, 'd':50} ,就会造成以为字典按照key排序的错觉。 目前,建议不要3.6提供的这种字典特性,还是以为字典返回的是无序的,可以在Python不同版本中考虑使用OrderedDict类来保证这种录入序
Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> d1 = {}
>>> d1.update(d=1)
>>> od.update(d=1)
>>> d1.update(c='abc')
>>> od.update(c='abc')
>>> d1
{'c': 'abc', 'd': 1}
>>> od
OrderedDict([('d', 1), ('c', 'abc')])
>>> d1.update(a=100,b=2)
>>> od.update(a=100,b=2)
>>> d1
{'a': 100, 'c': 'abc', 'd': 1, 'b': 2}
>>> od
OrderedDict([('d', 1), ('c', 'abc'), ('a', 100), ('b', 2)])
9、defaultdict
先看一段代码
d1 = {}
for k in 'abcd':
for v in range(5):
if k not in d1.keys():
d1[k] = []
d1[k].append(v)
print(d1)
当d1中k不存在的时候,先创建k和口空列表键值对,然后为列表增加元素。最后,得到{'a': [0, 1, 2, 3, 4], 'b': [0, 1, 2, 3, 4], 'c': [0, 1, 2, 3, 4], 'd': [0, 1, 2, 3, 4]}
。
Python提供了defaultdict类,defaultdict(default_factory)
构造一个特殊字典,初始化是可以传入一个工厂函数,当访问一个不存在的key是,就会调用这个工厂函数获得返回值,和key凑成键值对。
from collections import defaultdict
d2 = defaultdict(list)
print(d2['a']) # 创建kv对
print(d2) # 多了一个键值对
from collections import defaultdict
d2 = defaultdict(list)
for k in 'abcd':
for v in range(5):
d2[k].append(v) # k找不到,创建k:list()
print(d2)
十五、封装和解构
1、基本概念
t1 = 1, 2
print(type(t1)) # 什么类型
t2 = (1, 2)
print(type(t2))
Python等式右侧出现逗号分隔的多值的时候,就会将这几个值封装到元组中。这种操作称为封装 packing。
x, y = (1, 2)
print(x) # 1
print(y) # 2
Python中等式右侧是一个容器类型,左侧是逗号分隔的多个标识符,将右侧容器中数据的一个个和左侧 标识符一一对应。这种操作称为解构unpacking。
从Python3开始,对解构做了很大的改进,现在用起来已经非常的方便快捷。
封装和解构是非常方便的提取数据的方法,在Python、JavaScript等语言中应用极广.
# 交换数据
x = 4
y = 5
t = x
x = y
y = t
# 封装和解构,交换
x = 10
y = 11
x, y = y, x
2、简单结构
# 左右个数相同
a, b = 1, 2
a, b = (1, 2)
a, b = [1, 2]
a, b = [10, 20]
a, b = {10, 20} # 非线性结构
a, b = {'a': 10, 'b': 20} # 非线性结构也可以解构
[a, b] = (1, 2)
[a, b] = 10, 20
(a, b) = {30, 40}
那么,左右个数不一致可以吗? a, b = (10, 20, 30)
3、剩余变量解构
在Python3.0中增加了剩余变量解构(rest)。
a, *rest, b = [1, 2, 3, 4, 5]
print(a, b) # 1 5
print(type(rest), rest) # <class 'list'> [2, 3, 4]
标识符rest将尽可能收集剩余的数据组成一个列表.
a, *rest = [1, 2, 3, 4, 5]
print(a, rest)
1 [2, 3, 4, 5]
*rest, b = [1, 2, 3, 4, 5]
print(rest, b)
[1, 2, 3, 4] 5
*rest = [1, 2, 3, 4, 5]
print(rest) # 会报错
a, *r1, *r2, b = [1, 2, 3, 4, 5] # 会报错,只允许带一个*号的表达式
a, *_, b = [1, 2, 3, 4, 5]
print(_) # 在IPython中实验,_是最后一个输出值,这里将把它覆盖
[2, 3, 4]
_, *b, _ = [1, 2, 3]
print(_) # 第一个_是3
print(b) # 是[2]
print(_) # 第二个_是3
_是合法的标识符,这里它没有什么可读性,它在这里的作用就是表示不关心这个变量的值,我不想 要。有人把它称作 丢弃(Throwaway)变量。
十六、选择排序
1、简单选择排序
选择排序
- 每一趟两两比较大小,找出极值(极大值或极小值)并放置到有序区的位置
1、核心算法
- 结果可为升序或降序排列,默认升序排列。
- 扩大有序区,减小无序区。图中红色部分就是增大的有序区,反之就是减小的无序区。
- 以降序为例。
- 相邻元素依次两两比较,获得每一次比较后的最大值,并记住此值的索引。
- 每一趟都从无序区中选择出最大值,然后交换到当前无序区最左端。
2、算法实现
nums = [1, 9, 8, 5, 6, 7, 4, 3, 2] # 定义一个列表nums
length = len(nums) # 获取列表的长度
for i in range(length - 1): # 外层循环,遍历整个列表,除了最后一个元素
maxindex = i # 初始化maxindex为当前外层循环的索引
for j in range(i + 1, length): # 内层循环,从当前外层循环的下一个元素开始,遍历到列表的最后一个元素
if nums[j] > nums[maxindex]: # 如果当前元素大于maxindex指向的元素
maxindex = j # 更新maxindex为当前元素的索引
if maxindex != i : # 在内层循环结束后,检查maxindex是否还是i
nums[i], nums[maxindex] = nums[maxindex], nums[i] # 如果maxindex不是i,交换i和maxindex指向的元素的位置
print(nums) # 打印排序后的列表
2、二元选择排序
同时选择出每一趟的最大值和最小值,并分别固定到两端的有序区减少迭代的趟数。
nums = [1, 9, 8, 1, 1, 1, 5] # 待排序的列表
length = len(nums) # 列表的长度
count_iter = 0 # 迭代次数的计数器
count_swap = 0 # 交换次数的计数器
# 一次固定2个数,进行比较和交换
for i in range(length // 2): # 外层循环,遍历整个列表的一半
maxindex = i # 假设无序区第一个就是最大数,其索引记作最大
minindex = -i - 1 # 假设无序区最后一个就是最小数,其索引记作最小
minorigin = minindex # 无序区最小值的原始索引
# 每次左边加一个,右边也要减一个,表示无序区两端都减少
for j in range(i + 1, length - i): # 内层循环,从无序区的下一个元素开始,到无序区的末尾
count_iter += 1 # 每次内层循环,迭代次数加1
if nums[j] > nums[maxindex]: # 如果当前元素大于无序区的最大值
maxindex = j # 更新最大值的索引
if nums[-j - 1] < nums[minindex]: # 如果当前元素的负索引小于无序区的最小值
minindex = -j - 1 # 更新最小值的索引
# 如果最大值和最小值相等,说明无序区已经排好序,跳出循环
if nums[maxindex] == nums[minindex]:
break
# 如果最大值不在无序区的开头,说明需要进行交换,交换无序区的第一个元素和最大值所在的位置
if maxindex != i:
nums[i], nums[maxindex] = nums[maxindex], nums[i] # [9, 1, 8, 5]
count_swap += 1 # 交换次数加1
# 如果无序区的第一个元素就是最小值,那么最小值的索引要调整为新的最大值的索引加上长度
if i == length + minindex:
minindex = maxindex - length # [1, 3, 2]为例,如果i位置上就是最小值,走到这里,说明最大值和最小值交换过了,要调整最小值索引为maxindex
# [5, 1, 8, 9]交换后的结果,交换次数加1
# 如果最小值不在无序区的末尾,且最小值所在的位置的值和原始的最小值不相等,说明需要进行交换,交换无序区的末尾元素和最小值所在的位置
if minindex != minorigin and nums[minindex] != nums[minorigin]: # [5, 1, 8, 9]交换后的结果,交换次数加1
nums[minorigin], nums[minindex] = nums[minindex], nums[minorigin] # [5, 1, 8, 9]交换后的结果,交换次数加1
count_swap += 1 # [5, 1, 8, 9]交换后的结果,交换次数加1
3、总结
- 简单选择排序需要数据一趟趟比较,并在每一趟中发现极值
- 没有办法知道当前这一趟是否已经达到排序要求,但是可以知道极值是否在目标索引位置上
- 遍历次数1,…,n-1之和n(n-1)/2
- 时间复杂度O(n2)
- 减少了交换次数,提高了效率,性能略好于冒泡法
十七、解析式和生成器表达式
1、列表解析式
列表解析式List Comprehension,也叫列表推导式。
# 生成一个列表,元素0~9,将每一个元素加1后的平方值组成新的列表
# 创建一个空列表 x
x = []
# 遍历 range(10)
for i in range(10):
# 对每个 i,计算 (i + 1) ** 2(也就是 i 的平方加 1),然后把结果添加到 x 列表中
x.append((i + 1) ** 2)
print(x)
# 列表解析式
# 遍历 range(10),对每个 i,计算 (i + 1) ** 2,然后直接打印出这个计算结果列表
print([(i + 1) ** 2 for i in range(10)])
语法:
- [返回值 for 元素 in 可迭代对象 if 条件]
- 使用中括号[],内部是for循环,if条件语句可选
- 返回一个新的列表
列表解析式是一种语法糖
- 编译器会优化,不会因为简写而影响效率,反而因优化提高了效率
- 减少程序员工作量,减少出错
- 简化了代码,增强了可读性
例1、求10以内偶数
# 创建一个空的列表x,用于存储满足条件的数字
x = []
# 遍历0到9的整数范围
for i in range(10):
# 使用if语句检查i是否是偶数,即当i除以2的余数为0时
if i % 2 == 0:
# 如果是偶数,则将其添加到列表x中
x.append(i)
# 打印列表x,也就是10以内所有的偶数
print(x)
# 使用列表解析打印10以内所有的偶数,这种方式更为简洁
print([i for i in range(10) if i % 2 == 0])
# 使用range函数,以步长为2(即只选择偶数)遍历0到9的整数范围,然后转化为列表并打印
print(list(range(0, 10, 2)))
# 使用列表解析和步长参数,以步长为2遍历0到9的整数范围,然后转化为列表并打印
print([i for i in range(0, 10, 2)])
复杂语法如下
# 使用列表推导式 (List Comprehension) 来构建新的列表
# 对于列表推导式 [expr for item in iterable if cond1 if cond2],它的含义是:
# 在 iterable 中遍历每个元素 item,如果 cond1 和 cond2 都为真,则将 expr 的结果添加到新的列表中
# 列表推导式可以简写为:
ret = []
for item in iterable:
if cond1:
if cond2:
ret.append(expr)
# 对于列表推导式 [expr for i in iterable1 for j in iterable2],它的含义是:
# 先遍历 iterable1 中的每个元素 i,然后对于每个 i,遍历 iterable2 中的每个元素 j,将 expr 的结果添加到新的列表中
# 这也可以被简写为:
ret = []
for i in iterable1:
for j in iterable2:
ret.append(expr)
例2、20以内既能被2整除又能被3整除的数
nums = []
for i in range(20):
if i % 2 == 0: # 如果i能被2整除
if i % 3 == 0: # 如果i也能被3整除
nums.append(i) # 添加这个数到列表nums中
print(nums)
print([i for i in range(20) if i % 2 == 0 if i % 3 == 0])
# 或者
nums = []
for i in range(20):
if i % 2 == 0 and i % 3 == 0: # 如果i能被2整除并且也能被3整除
nums.append(i) # 添加这个数到列表nums中
print(nums)
print([i for i in range(20) if i % 2 == 0 and i % 3 == 0])
例3、找到20以内能被2整除或能被3整除的数
# 创建一个空列表,用来存放符合条件的数字
nums = []
# 遍历从0到19的所有数字(包括0但不包括20)
for i in range(20):
# 判断数字是否可以被2整除
if i % 2 == 0:
# 如果可以,则将其添加到nums列表中
nums.append(i)
# 使用continue来跳过当前循环的剩余部分,直接开始下一次循环
continue
# 如果上面的条件不满足(即数字不能被2整除),则判断数字是否可以被3整除
if i % 3 == 0:
# 如果可以,则将其添加到nums列表中
nums.append(i)
# 打印出nums列表,显示所有符合条件的数字
print(nums)
# 或者
nums = []
for i in range(20):
# 如果数字可以被2或3整除,则将其添加到nums列表中
if i % 2 == 0 or i % 3 == 0:
nums.append(i)
print(nums)
print([i for i in range(20) if i % 2 == 0 or i % 3 == 0])
# 该列表生成式会生成一个包含 5×3=15 个元素的列表。每个元素都是一个元组 (x, y),其中 x 是字符串 'abcde' 中的一个字符,y 是范围从 0 到 2 的整数序列中的一个整数
print([(x, y) for x in 'abcde' for y in range(3)])
# 这个列表生成式会生成一个包含 5×3=15 个元素的列表。每个元素都是一个包含两个元素的列表 [x, y],其中 x 是字符串 'abcde' 中的一个字符,y 是范围从 0 到 2 的整数序列中的一个整数
print([[x, y] for x in 'abcde' for y in range(3)])
# 这个列表生成式会生成一个包含 5×3=15 个元素的列表。每个元素都是一个包含两个元素的列表 [x, y],其中 x 是字符串 'abcde' 中的一个字符,y 是范围从 0 到 2 的整数序列中的一个整数
print([{x, y} for x in 'abcde' for y in range(3)])
print([(i, j) for i in range(7) if i > 4 for j in range(20, 25) if j > 23]) # [(5, 24), (6, 24)]
print([(i, j) for i in range(7) for j in range(20, 25) if i > 4 if j > 23]) # [(5, 24), (6, 24)]
print([(i, j) for i in range(7) for j in range(20, 25) if i > 4 and j > 23]) # [(5, 24), (6, 24)]
2、生成器表达式
语法
- (返回值 for 元素 in 可迭代对象 if 条件)
- 列表解析式的中括号换成小括号就行了
- 返回一个生成器对象
和列表解析式的区别
- 生成器表达式是按需计算(或称惰性求值、延迟计算),需要的时候才计算值
- 列表解析式是立即返回值
生成器对象
- 可迭代对象
- 迭代器
x = (i + 1 for i in range(10))
print(next(x))
for i in x:
print(i)
print('-' * 30)
for i in x:
print(i)
x = [i + 1 for i in range(10)]
for i in x:
print(i)
print('-' * 30)
for i in x:
print(i)
生成器表达式 | 列表解析式 |
---|---|
延迟计算/返回可迭代对象/只能迭代一次 | 立即计算/返回可迭代对象列表,不是迭代器/可反复迭代 |
3、生成器表达式和列表解析式对比
- 计算方式
- 生成器表达式延迟计算,列表解析式立即计算
- 内存占用
- 单从返回值本身来说,生成器表达式省内存,列表解析式返回新的列表
- 生成器没有数据,内存占用极少,但是使用的时候,虽然一个个返回数据,但是合起来占用 的内存也差不多
- 列表解析式构造新的列表需要立即占用掉内存
- 计算速度
- 单看计算时间看,生成器表达式耗时非常短,列表解析式耗时长
- 但生成器本身并没有返回任何值,只返回了一个生成器对象
- 列表解析式构造并返回了一个新的列表
4、集合解析式
语法
- {返回值 for 元素 in 可迭代对象 if 条件}
- 列表解析式的中括号换成大括号{}就变成了集合解析式
- 立即返回一个集合
print({(x, x + 1) for x in range(10)})
print({[x] for x in range(10)}) # 可以吗?不可以,因为大括号 {} 用于定义集合(set)或字典(dictionary),而不是列表
5、字典解析式
语法
- {key:value for 元素 in 可迭代对象 if 条件}
- 列表解析式的中括号换成大括号{},元素的构造使用key:value形式
- 立即返回一个字典
# 会创建一个字典,其中每个键是一个整数(从0到9),每个值是一个元组,元组的第一个元素是键值,第二个元素是键值加1。所以,这个字典有10个元素
{x: (x, x + 1) for x in range(10)}
# 创建一个字典,其中每个键是一个整数(从0到9),每个值是一个列表,列表的第一个元素是键值,第二个元素是键值加1。所以,这个字典有10个元素
{x: [x, x + 1] for x in range(10)}
# 创建一个字典,其中每个键是一个元组(其中只有一个元素,即整数0到9),每个值是一个列表,列表的第一个元素是键中的整数,第二个元素是键中的整数加1。所以,这个字典有10个元素
{(x,): [x, x + 1] for x in range(10)}
# 因为字典的键必须是不可变的数据类型,而列表([x])是可变的。因此,这种写法是错误的
{[x]: [x, x + 1] for x in range(10)}
# 会创建一个字典,其中键是整数0到2的字符串形式('0','1','2'),值是从整数0到3的列表。因此,这个字典有6个元素
{str(x): y for x in range(3) for y in range(4)}
6、总结
- Python2 引入列表解析式
- Python2.4 引入生成器表达式
- Python3 引入集合、字典解析式,并迁移到了2.7 一般来说,应该多应用解析式,简短、高效。如果一个解析式非常复杂,难以读懂,要考虑拆解成for 循环。
生成器和迭代器是不同的对象,但都是可迭代对象。
如果不需要立即获得所有可迭代对象的元素,在Python 3中,推荐使用惰性求值的迭代器。
内建函数 | 函数签名 | 说明 |
---|---|---|
sorted | sorted(iterable[, key][, reverse])sorted(iterable[, key][, reverse]) | 默认升序,对可迭代对象排序 |
立即返回列表
# 排序一定是容器内全体参与
print(sorted([1, 2, 3, 4, 5]))
[1, 2, 3, 4, 5]
print(sorted(range(10, 20), reverse=True))
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10]
print(sorted({'a': 100, 'b': 'abc'}))
['a', 'b']
print(sorted({'a': 100, 'b': 'abc'}.items()))
[('a', 100), ('b', 'abc')]
print(sorted({'a': 'ABC', 'b': 'abc'}.values(), key=str, reverse=True))
['abc', 'ABC']
评论区