Python 的数据结构包括了 序列、映射、集(它既不是序列也不是映射)、堆、队列等容器
- 序列:列表
list()
/[]
、元祖tuple()
/()
、字符串str()
/{}
- 映射:字典dict
{}
- 集:
set()
- 堆
heapq
- 队列
deque()
对于序列和映射而言,都是可迭代对象。list()是可变对象,而tuple()和str()是不可变对象。
序列
序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。
__len__(self)
:这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果len返回零(且没有实现覆盖这种行为的nonzero),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。 __getitem__(self, key)
:这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数),其中n为序列的长度。对映射来说,键可以是任何类型。__setitem__(self, key, value)
:这个方法应以与键相关联的方式存储值,以便以后能够使用getitem来获取。当然,仅当对象可变时才需要实现这个方法。 __delitem__(self, key)
:这个方法在对对象的组成部分使用del语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。 对于这些方法,还有一些额外的要求。
对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
也就是说,只要根据规则,实现了上述的特定的魔法方法,那么就是一个自定义序列。不过通常非特定需要的情况,使用或继承内置序列要靠谱也轻量很多。
迭代器
实现了
__iter__()
的对象是可迭代”iterable”的,而实现了__next__()
的对象是迭代器 “iterator” 也可以用内置函数iter()
接收可迭代对象(比如 list, tuple, string, dict),获得一个迭代器参考资料 https://docs.python.org/zh-cn/3/library/functions.html?#iter https://docs.python.org/zh-cn/3/library/stdtypes.html#typeiter https://www.bilibili.com/video/BV1ca411t7A9?spm_id_from=333.999.0.0
迭代器的优势:
- 内存占用小,性能好。当循环遍历一个可迭代对象如列表时,每一次的循环都会对列表进行拷贝,因此耗时长,内存有一定牺牲;而迭代器每次都只返回下一个对象(如果有)
- 安全。不会出现越界,当下一个对象没有后,会引发异常自动停止迭代
生成器之 yield
在 Python 中,包含
yield
语句的函数都被称为“生成器”。 生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。生成器被调用时,不会执行函数体内的代码,而是返回一个迭代器参考资料 真正理解生成器的机制,是有些绕的,因为它并不那么符合直觉。 在尝试生成器的代码时,官方文档内容很值得参阅 https://docs.python.org/zh-cn/3/glossary.html#term-generator https://docs.python.org/zh-cn/3/reference/expressions.html#generator.next https://docs.python.org/zh-cn/3/reference/expressions.html#generator.send
https://www.bilibili.com/video/BV1KS4y1D7Qb?spm_id_from=333.999.0.0
生成器的优势:
- 内存占用小,性能好。生成器每次都只返回一个迭代器对象(如果有)
- 实现简单协同程序的能力 (示例 https://www.python.org/dev/peps/pep-0342/)
```python
定义一个生成器函数
def gen(num): while num > 0:print("开始执行")
yield num
print("继续执行")
num -= 1
print(gen(5)) # 返回一个生成器对象
g = gen(5)
for i in g: print(i)
```python
def gen_2(val: list):
for _ in val:
yield _
lst = [1, 2, 3]
g_2 = gen_2(lst)
while True:
try:
print("当次的动作: %s 开始执行" % next(g_2))
except StopIteration:
print('结束循环')
break
异步 IO 之协程
Python版本在3.7及以上
https://docs.python.org/zh-cn/3/library/asyncio-task.html https://docs.python.org/zh-cn/3/library/asyncio.html
协程
可运行的机制包括
asyncio.run()
函数用来运行最高层级的入口点“main()”函数此函数会运行传入的协程,负责管理 asyncio 事件循环,终结异步生成器,并关闭线程池 当有其他 asyncio 事件循环在同一线程中运行时,此函数不能被调用 此函数总是会创建一个新的事件循环并在结束时关闭之。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次
await
等待一个协程对象await可等待对象主要是:协程、task 和 Future
asyncio.create_task()
函数用来并发运行作为 asyncio 任务的多个协程将 coro 协程 封装为一个 Task 并调度其执行。返回 Task 对象 该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError
# 定义协程函数-它返回的是一个协程对象
async def say():
print(f"started at {time.strftime('%X')}")
await asyncio.sleep(3)
print(f"end at {time.strftime('%X')}")
# 假设上述示例的 say() 为入口函数
asyncio.run(say())
async def tell():
print(f"started at {time.strftime('%X')}")
await asyncio.sleep(3)
print(f"end at {time.strftime('%X')}")
async def main():
await tell()
await tell()
if __name__ == '__main__':
asyncio.run(main())
async def speak():
await asyncio.create_task(say())
await asyncio.create_task(say())
if __name__ == '__main__':
asyncio.run(speak())
注意:asyncio.sleep()
是“阻塞”,它总是会挂起当前任务,以允许其他任务执行
异步 IO 之事件循环
学习资源推荐 https://space.bilibili.com/245645656?spm_id_from=333.788.b_765f7570696e666f.1 https://www.bilibili.com/video/BV1i7411877K?spm_id_from=333.337.top_right_bar_window_history.content.click https://www.bilibili.com/video/BV1ST4y1m7No?spm_id_from=333.999.0.0 https://www.bilibili.com/video/BV1oa411b7c9?spm_id_from=333.999.0.0