将协程用作迭代器

简介: 将协程用作迭代器

我们可以将循环迭代器视为生产者-消费者模式的一种特例:一个迭代器会产生有循环体消费的内容。因此,用协程来实现迭代器看上去就很合适。的确,协程为实现这类任务提供了一种强大的工具。同时,协程最关键的特性是能够颠倒调用者与被调用者之间的关系。有了这种特性,我们在编写迭代器时就无需担心如何保存连续调用之间的状态了。


为了说明这类用途,让我们来编写一个遍历指定数组所有排列的迭代器。要直接编写这种迭代器并不容易,但如果要编写一个递归函数来产生所有排列则不是很难。思路很简单,只要依次将每个数组元素放到最后一个位置,然后递归生成其余元素的所有排列即可。

function permgen(a, n)
  n = n or #a         -- n的默认大小是a
  if n <= 1 then      -- 只有一种组合
    printResult(a)
  else
    for i = 1, n do
      a[n], a[i] = a[i], a[n]   -- 把第i个元素当做最后一个
      permgen(a, n - 1)         -- 生成其余元素的所有排列
      a[n], a[i] = a[i], a[n]   -- 恢复第i个元素
    end
  end
end


还需要定义一个合适的函数 printResult 来输出结果,并使用恰当的参数调用 permgen

function printResult(a)
  for i = 1, #a do
    io.write("\n")
  end
end
permgen({1, 2, 3, 4})
--> 2 3 4 1
--> 3 2 4 1
--> 3 4 2 1
……
--> 2 1 3 4
--> 1 2 3 4


当有了生成器后,将其转换为迭代器就很容易了。首先,我们把 printResult 改为 yield :

function permgen (a, n)
  n = n or #a
  if n <= 1 then
    coroutine.yield(a)
  else
    -- 同前


然后,我们定义一个将生成器放入协程运行并创建迭代函数的工厂。迭代器只是简单地唤醒协程,让其产生下一个排列:

function permutation (a)
  local co = coroutine.create(function () permgen(a) end)
  return function()
    local code, res = coroutine.resume(co)
    return res
  end
end


有了上面的这些,在 for 循环中遍历一个数组的所有排列就非常简单了:

for p in permutations {"a", "b", "c"} do
  printResult(p)
end
--> b c a
--> c b a
--> c a b
--> a c b
--> b a c
--> a b c


函数 permutations 使用了 Lua 语言中一种常见的模式,就是将唤醒对应协程的调用包装在一个函数中。由于这种模式比较常见,所以 Lua 语言专门提供了一个特殊的函数 coroutine.wrap 来完成这个功能。与函数 create 类似,函数 wrap 也用来创建一个新的协程。但不同的是,函数 wrap 返回的不是协程本身而是一个函数,当这个函数被调用时会唤醒协程。与原始的函数 resume 不同,该函数的第一个返回值不是错误代码,当遇到错误时该函数会抛出异常。我们可以使用函数 wrap 改写 permutations

function permutations (a)
  return coroutine.wrap(function() permgen(a) end)
end


通常,函数 coroutine.wrap 比函数 coroutine.create 更易于使用。它为我们提供了对于操作协程而言所需的功能,即一个唤醒协程的函数。不过,该函数缺乏灵活性,我们无法检查通过函数 wrap 所创建的协程的状态,也无法检查运行时的异常。

目录
相关文章
|
存储 缓存 Python
1_python高阶_协程—迭代器
python高阶_协程—迭代器
152 0
|
24天前
|
Go 调度 Python
Golang协程和Python协程用法上的那些“不一样”
本文对比了 Python 和 Go 语言中协程的区别,重点分析了调度机制和执行方式的不同。Go 的协程(goroutine)由运行时自动调度,启动后立即执行;而 Python 协程需通过 await 显式调度,依赖事件循环。文中通过代码示例展示了两种协程的实际运行效果。
|
2月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
157 1
使用python实现一个用户态协程
|
12月前
|
调度 Python
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
190 0
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
209 10
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
141 6
|
大数据 数据处理 API
性能飞跃:Python协程与异步函数在数据处理中的高效应用
【7月更文挑战第15天】在大数据时代,Python的协程和异步函数解决了同步编程的性能瓶颈问题。同步编程在处理I/O密集型任务时效率低下,而Python的`asyncio`库支持的异步编程利用协程实现并发,通过`async def`和`await`避免了不必要的等待,提升了CPU利用率。例如,从多个API获取数据,异步方式使用`aiohttp`并发请求,显著提高了效率。掌握异步编程对于高效处理大规模数据至关重要。
173 4
|
设计模式 机器学习/深度学习 测试技术
设计模式转型:从传统同步到Python协程异步编程的实践与思考
【7月更文挑战第15天】探索从同步到Python协程异步编程的转变,异步处理I/O密集型任务提升效率。async/await关键词定义异步函数,asyncio库管理事件循环。面对挑战,如思维转变、错误处理和调试,可通过逐步迁移、学习资源、编写测试和使用辅助库来适应。通过实践和学习,开发者能有效优化性能和响应速度。
129 3
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
【7月更文挑战第15天】Python异步编程借助协程和async/await提升并发性能,减少资源消耗。协程(async def)轻量级、用户态,便于控制。事件循环,如`asyncio.get_event_loop()`,调度任务执行。异步函数内的await关键词用于协程间切换。回调和Future对象简化异步结果处理。理解这些概念能写出高效、易维护的异步代码。
181 2