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

迭代器的优势:

  1. 内存占用小,性能好。当循环遍历一个可迭代对象如列表时,每一次的循环都会对列表进行拷贝,因此耗时长,内存有一定牺牲;而迭代器每次都只返回下一个对象(如果有)
  2. 安全。不会出现越界,当下一个对象没有后,会引发异常自动停止迭代

生成器之 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

生成器的优势:

  1. 内存占用小,性能好。生成器每次都只返回一个迭代器对象(如果有)
  2. 实现简单协同程序的能力 (示例 https://www.python.org/dev/peps/pep-0342/) ```python

    定义一个生成器函数

    def gen(num): while num > 0:
    1. print("开始执行")
    2. yield num
    3. print("继续执行")
    4. num -= 1

print(gen(5)) # 返回一个生成器对象

g = gen(5)

for i in g: print(i)

  1. ```python
  2. def gen_2(val: list):
  3. for _ in val:
  4. yield _
  5. lst = [1, 2, 3]
  6. g_2 = gen_2(lst)
  7. while True:
  8. try:
  9. print("当次的动作: %s 开始执行" % next(g_2))
  10. except StopIteration:
  11. print('结束循环')
  12. 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

协程可运行的机制包括

  1. asyncio.run() 函数用来运行最高层级的入口点“main()”函数

    此函数会运行传入的协程,负责管理 asyncio 事件循环,终结异步生成器,并关闭线程池 当有其他 asyncio 事件循环在同一线程中运行时,此函数不能被调用 此函数总是会创建一个新的事件循环并在结束时关闭之。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次

  1. await等待一个协程对象

    await可等待对象主要是:协程、task 和 Future

  2. asyncio.create_task()函数用来并发运行作为 asyncio 任务的多个协程

    coro 协程 封装为一个 Task 并调度其执行。返回 Task 对象 该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError

  1. # 定义协程函数-它返回的是一个协程对象
  2. async def say():
  3. print(f"started at {time.strftime('%X')}")
  4. await asyncio.sleep(3)
  5. print(f"end at {time.strftime('%X')}")
  1. # 假设上述示例的 say() 为入口函数
  2. asyncio.run(say())
  1. async def tell():
  2. print(f"started at {time.strftime('%X')}")
  3. await asyncio.sleep(3)
  4. print(f"end at {time.strftime('%X')}")
  5. async def main():
  6. await tell()
  7. await tell()
  8. if __name__ == '__main__':
  9. asyncio.run(main())
  1. async def speak():
  2. await asyncio.create_task(say())
  3. await asyncio.create_task(say())
  4. if __name__ == '__main__':
  5. 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