4.1 节点生命周期
从这一章开始,小莫要解锁一项大能力:✍️ 会表达——你将亲手写出它身上的第一个"零件"(节点)。
在动手敲代码之前,这一节先把一件最重要的事讲透:一个节点从"出生"到"退场",一生到底经历了什么。 只要你脑子里有了这幅"生命周期"的画面,后面写代码就是水到渠成。
在此之前,我身上的零件都是别人(比如小飞机工程)预先写好的,我只是"运行"它们。这一章,你要当我的"零件设计师"啦——先搞懂一个零件是怎么活着的,再动手造它。
学习目标
学完本节,你将能够:
- 说清一个 DORA 节点的三段人生:连线 → 循环收发 → 退场;
- 理解事件循环为什么长成
for event in node:这个样子; - 认识节点会收到的几种事件类型(
INPUT/STOP/InputClosed等); - 明白
send_output是怎么"往黑板上写字"的。
前置要求
- 读完第一章,脑子里有"黑板与课堂"的画面;
- 会一点点 Python(
for、if、字典取值),不熟的话先看附录 · Python 极简速成; - 装好了朵拉魔盒(第二章),并跑通过第三章的小飞机。
先回到黑板教室
第一章我们说过,一个节点(Node) 就是黑板教室里的一名同学,各管一摊事:有人负责读、有人负责算、有人负责写。
那么,一名"同学"在教室里的一天是怎么过的?其实非常简单,就三步:
- 走进教室、报到——让老师(DORA 运行时)知道"我来了,我是谁";
- 上课——一遍遍地:抬头看黑板(收到新数据)→ 动脑处理 → 把结果写上黑板;
- 下课铃响、离场——听到"放学"(STOP)信号,收拾好东西,安静离开。
一个 DORA 节点的"一生",和这个一模一样。我们给这三步起了正式的名字:
下面我们逐段拆开看。
第一段:出生——node = Node()
一个 Python 节点的代码,几乎总是这样开头:
这两行看着不起眼,背后却发生了一件关键的事:Node() 把你的这段代码,接进了整个数据流。
打个比方:Node() 就像新同学走进教室、向老师报到。报到之后,老师才知道:
- 你叫什么名字(节点的
id,来自dataflow.yml); - 你该抬头看黑板的哪一块区域(你的
inputs); - 你能往黑板的哪一块区域写字(你的
outputs)。
你可能会奇怪:代码里 Node() 括号里空空的,它怎么知道我叫什么、该收哪些数据?
秘密在于:这些信息不写在代码里,而写在 dataflow.yml 那张"值日表"里。运行时启动你时,会通过环境变量偷偷把这些告诉 Node()。所以同一份节点代码,换一张值日表,就能有不同的名字和连线——这非常灵活。
进阶延伸:
Node() 到底做了什么(可跳过)
Node() 会读取运行时注入的环境变量,找到与本节点通信的控制通道和事件通道,完成"握手"。此后:
- 运行时通过事件通道把
INPUT、STOP等事件推给你; - 你调用
send_output时,数据通过控制通道(大数据则走共享内存)发出去。
你还可以给它传参数,比如动态节点连接到指定端口:Node("my-node", daemon_port=6789)。零基础阶段用不到,知道有这回事即可。
第二段:主循环——for event in node:
报到之后,节点就进入了它一生中最主要的部分:一个不停转的循环,我们叫它事件循环(Event Loop)。
为什么是"事件",而不是"数据"?
这里有个很重要的观念转变。你可能以为节点是"等数据来了就处理",但 DORA 的说法更准确:节点等的是"事件(Event)"。
什么叫事件?就是"教室里发生的一件值得你知道的事"。新数据到了,是一件事;老师喊放学,也是一件事;某个同学提前走了,也是一件事。这些都通过同一个循环通知给你。
所以 for event in node: 的含义是:
只要教室里发生了什么和我有关的事,就把它交给我,让我处理一下。
当黑板上暂时没有新东西时,for event in node: 会安静地等着(专业叫"阻塞"),不会疯狂空转浪费 CPU。一旦有新事件,它立刻醒来把事件交给你。这正是我们想要的——该等就等,该干就干。
每个 event 长什么样?
每次循环拿到的 event,是一个字典(dict),你用中括号取里面的值。最常用的三个键:
于是一个节点处理事件的标准姿势就出来了——先看类型,再看是哪个输入,最后取数据:
节点会遇到哪几种事件类型?
event["type"] 主要有这几种。零基础阶段你真正要管的只有前两种,其余先认个脸:
是不是有点像我上课的样子?大多数时候我在处理"新内容"(INPUT),偶尔听到"放学铃"(STOP)就收拾书包回家。其它情况很少见,先不用操心~
第三段:退场——收到 STOP,优雅离开
节点不能永远转下去。当你在终端里停掉数据流(或数据流自然结束)时,运行时会给每个节点发一个 STOP 事件——这就是"放学铃"。
节点听到铃声,应该跳出循环、干净地退出:
break 之后,for 循环结束,函数返回,进程退出——这个节点的"一生"就圆满收场了。
如果你不理会 STOP、赖着不走,运行时最后会强制把你的进程杀掉。就像放学了还不走,最后被保安清场——不体面,还可能丢掉没保存的东西。养成 elif event["type"] == "STOP": break 的好习惯,让每个节点都能优雅谢幕。
进阶延伸:还有一种"活到自然结束"(可跳过)
除了等 STOP,节点也可以自己决定什么时候结束——比如"我只发 100 条数据就收工":
当一个节点退出后,它的下游节点会收到 InputClosed;当整张数据流没有"活口"时,运行时会给大家发 STOP,数据流整体结束。
把三段拼成一个完整骨架
现在,把出生、循环、退场三段拼起来,你就得到了几乎所有 Python 节点都长这样的通用骨架。请仔细看这段——它是本章后面所有代码的模板:
连线 → 循环 → 退场,是每个节点雷打不动的一生。以后你写任何节点,脑子里先默念这三段,代码结构就不会乱。
send_output:往黑板上写字
上面骨架里出现了 node.send_output(...),它就是节点"把结果写到黑板上"的动作——也就是产生一个输出(Output)。它的样子是:
- 第一个参数:输出的名字。它必须是你在
dataflow.yml的outputs里声明过的名字(下一节会看到)。 - 第二个参数:要写出去的数据。DORA 里数据统一用 Arrow 格式(还记得第一章"黑板的统一书写规范"吗),所以通常先用
pyarrow把它包一下:
现在不必纠结 pa.array 的细节——第五章会专门讲 Arrow 数据。这一节你只要记住:send_output = 写黑板,名字要和值日表对上。
"抬头看黑板"就是收到 INPUT,"在黑板上写字"就是调用 send_output。我的每个零件,一生都在重复这两个小动作,我就慢慢变聪明啦!
动手练习(读代码)
下面这个节点,来自 DORA 官方示例,叫 echo(回声)。请你先别看解析,试着用"三段式"读懂它在干什么:
问题:这个节点收到数据后,做了什么?
参考答案
它是一个"复读机":
- 出生:
node = Node()连上数据流; - 循环:每收到一个
INPUT,就立刻用send_output把收到的同一份数据(event["value"])原样写到自己的data输出上——相当于"你说什么,我原样再喊一遍"; - 退场:收到
STOP就break退出。
(那个多出来的 event["metadata"] 是随数据附带的"元信息",这里一并原样转发。零基础阶段可以先忽略它。)
常见报错 FAQ
ModuleNotFoundError: No module named 'dora'
说明当前环境里没有 DORA 的 Python 库。请确认你已正确安装了 DORA(参考第二章)。
最常见的原因:忘了写 for event in node: 循环,或循环体是空的。没有循环,节点报完到就"无事可做"直接结束了。对照上面的"三段式"骨架检查一下。
多半是没处理 STOP。确认你写了 elif event["type"] == "STOP": break。
小结
- 一个节点的一生就是三段式:出生(
Node())→ 主循环(for event in node)→ 退场(STOP后break)。 - 节点等的是事件,不只是数据;每个
event是个字典,看event["type"]、event["id"]、event["value"]。 - 最常处理两种事件:
INPUT(来数据了)和STOP(该退场了)。 send_output("名字", 数据)就是"往黑板上写字",名字要和dataflow.yml对上。
下一节,我们就用这套"三段式"骨架,亲手写出你的第一个 Python 节点。