目 录CONTENT

文章目录

Python字符串和常用数据结构

简中仙
2023-11-24 / 0 评论 / 0 点赞 / 94 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于2024-02-08,若内容或图片失效,请留言反馈。 本文如有错误或者侵权的地方,欢迎您批评指正!

一、字符串拼接

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,美国信息交换标准代码)是基于拉丁字母的一套单字节编码系统

熟记:

  1. \x00 表中第一项,C语言中的字符串结束符
  2. \t \x09 tab字符
  3. \r\n \x0d\x0a
  4. \x30~\x39 字符0~9,\x31 字符1
  5. \x41 65 A
  6. \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,表示向右;步长为负数,表示向左

如果子区间方向和步长方向不一致,直接返回当前类型的"空对象"

如果子区间方向和步长方向一致,则从起点间隔步长取值

内建函数函数签名说明
idid(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、简单选择排序

选择排序

  • 每一趟两两比较大小,找出极值(极大值或极小值)并放置到有序区的位置

img

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中,推荐使用惰性求值的迭代器。

内建函数函数签名说明
sortedsorted(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']
0

评论区