学习笔记

Posted on Thu 21 November 2024 in 学习

Python 学习

生成器

  1. 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器函数,而在一个 async def 函数的内部使用它则会让这个协程函数变成一个异步生成器函数。调用生成器函数,返回一个生成器对象。

  2. 当一个生成器函数被调用时,它将返回一个名为生成器的迭代器。 然后这个生成器将控制生成器函数的执行。执行过程会在这个生成器的某个方法被调用时开始。这时,函数会执行到第一个 yield 表达式,在那里它将再次被挂起,向生成器的调用方返回 yield_list 的值,或者如果yield_list 被省略则返回 None。所谓的挂起,就是说所有局部状态都会被保留,包括局部变量的当前绑定、指令指针、内部求值栈及任何异常处理等等。当通过调用生成器的某个方法恢复执行时,这个函数的运行就与 yield 表达式只是一个外部调用的情况完全一样。在恢复执行后 yield 表达式的值取决于恢复执行所调用的方法。如果是用 next() (一般是通过 for 或者 next() 内置函数) 则结果为 None。在其他情况下,如果是用 send(),则结果将为传给该方法的值。

  3. 生成器函数与协程非常相似;它们 yield 多次,它们具有多个入口点,并且它们的执行可以被挂起。 唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。 当使用 yield from 时,所提供的表达式必须是一个可迭代对象。 迭代该可迭代对象所产生的值会被直接传递给当前生成器方法的调用者。 任何通过 send() 传入的值以及任何通过 throw() 传入的异常如果有适当的方法则会被传给下层迭代器。 如果不是这种情况,那么 send() 将引发 AttributeError 或 TypeError,而 throw() 将立即引发所转入的异常。 当下层迭代器完成时,被引发的 StopIteration 实例的 value 属性会成为 yield 表达式的值。 它可以在引发 StopIteration 时被显式地设置,也可以在子迭代器是一个生成器时自动地设置(通过从子生成器返回一个值)。 添加 yield from 以委托控制流给一个子迭代器。 当yield表达式是赋值语句右侧的唯一表达式时,括号可以省略。

  4. generator.next()
    开始一个生成器函数的执行或是从上次执行 yield 表达式的位置恢复执行。 当一个生成器函数通过 next() 方法恢复执行时,当前的 yield 表达式总是取值为 None。 随后会继续执行到下一个 yield 表达式,这时生成器将再次挂起,而 yield_list 的值会被返回给 next() 的调用方。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。 此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。

  5. generator.send(value)
    恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。 send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。 当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

生成器表达式

  1. 生成器表达式是用圆括号括起来的紧凑形式生成器标注。
    generator_expression ::= "(" expression comp_for ")"

  2. 生成器表达式会产生一个新的生成器对象。 其句法与推导式相同,区别在于它是用圆括号而不是用方括号或花括号括起来的。 在生成器表达式中使用的变量会在为生成器对象调用 next() 方法的时候以惰性方式被求值(即与普通生成器相同的方式)。 但是,最左侧 for 子句内的可迭代对象是会被立即求值的,因此它所造成的错误会在生成器表达式被定义时被检测到,而不是在获取第一个值时才出错。 后续的 for 子句以及最左侧 for 子句内的任何筛选条件无法在外层作用域内被求值,因为它们可能会依赖于从最左侧可迭代对象获取的值。圆括号在只附带一个参数的调用中可以被省略。

  3. 为了避免干扰到生成器表达式本身的预期操作,禁止在隐式定义的生成器中使用 yieldyield from 表达式。

  4. 如果生成器表达式包含 async for 子句或 await 表达式,则称为 异步生成器表达式。 异步生成器表达式会返回一个新的异步生成器对象,此对象属于异步迭代器。

协程

  1. 原生协程通过 async/await 语法来声明。

  2. 实际运行一个协程,asyncio 提供了以下几种机制:

    • asyncio.run()函数用来运行最高层级的入口点 "main()" 函数
    • 对协程执行await
    • asyncio.create_task()函数用来并发运行作为 asyncio 任务 的多个协程。
    • asyncio.TaskGroup类提供了 create_task() 的更现代化的替代。
  3. 可等待对象:如果一个对象可以在 await 语句中使用,那么它就是可等待对象协程,任务Future都是可等待对象。

    • Python协程属于可等待对象,因此可以在其他协程中被等待。
    • 任务被用来“并行的”调度协程。当一个协程通过 asyncio.create_task() 等函数被封装为一个任务,该协程会被自动调度执行。
  4. asyncio.create_task(coro, *, name=None, context=None)将coro协程封装为一个Task并调度其执行。返回Task对象。

  5. awaitable asyncio.gather(*aws, return_exceptions=False)并发运行aws序列中的可等待对象。

    • 如果 aws 中的某个可等待对象为协程,它将自动被作为一个任务调度。如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。
    • 如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。
    • 如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。
    • 如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消。
    • 如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理。在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。
  6. asyncio.as_completed(aws, *, timeout=None)并发地运行 aws 可迭代对象中的 可等待对象。 返回的对象可以被迭代以获取可等待对象结束时的结果。由 as_completed() 返回的对象可作为 asynchronous iterator 或普通的 iterator 被迭代。

    • 当使用异步迭代时,原来提供的可等待对象如果为 Task 或 Future 对象则会被产出。 这样可以更容易地将之前加入计划的任务与其结果进行对应。在异步迭代期间,将为不属于 Task 或 Future 对象的可等待对象产出隐式创建的任务。
    • 当被用作普通的迭代器时,每次迭代将产出一个返回结果的新协程或是引发下一个完成的等待对象对应的异常。如果在所有可等待对象完成之前达到超时限制则会引发 TimeoutError。 这将在异步迭代期间由 async for 循环引发或是在普通迭代期间由所产出的协程引发。
  7. coroutine asyncio.to_thread(func, /, *args, **kwargs) 在不同的线程中异步地运行函数 func。向此函数提供的任何 *args 和 **kwargs 会被直接传给 func。 并且,当前 contextvars.Context 会被传播,允许在不同的线程中访问来自事件循环的上下文变量。返回一个可被等待以获取 func 的最终结果的协程。这个协程函数主要是用于执行在其他情况下会阻塞事件循环的 IO 密集型函数/方法。

  8. coroutine asyncio.sleep(delay, result=None)阻塞delay指定的秒数。如果指定了result,则当协程完成时将其返回给调用者。sleep()总是会挂起当前任务,以允许其他任务运行。将delay设为0将提供一个经优化的路径以允许其他任务运行。这可供长期间运行的函数使用以避免在函数调用的全过程中阻塞事件循环。