4.4 定时器与参数
前面三节,你已经会写节点、连数据流、跑起来了。这一节我们补上两块让节点更实用、更灵活的拼图:
- 定时器:怎么让节点"按节奏"自动干活,以及不同写法的区别;
- 参数:怎么不改代码、只改配置,就让同一个节点表现不同——这是专业开发的关键习惯。
学完这节,第四章就圆满了,小莫的 ✍️ 会表达 能力就正式点亮。
学习目标
学完本节,你将能够:
- 熟练使用三种定时器写法:
millis、secs、hz,并知道何时用哪种; - 理解定时器的
tick事件本质上也是一种INPUT; - 用
env给节点传参数,在代码里用os.getenv读取; - 明白"参数化"为什么能让你的节点更通用。
前置要求
- 已完成 4.3,能跑通 sender → echo → printer 数据流。
一、定时器:让节点按节奏干活
定时器是"数据流内置的闹钟"
回想 4.2,我们没有在代码里写 time.sleep,而是靠 dataflow.yml 里的一行让 sender 每半秒动一次:
这个 dora/timer/... 就是 DORA 内置的定时器。它不是某个节点,而是运行时提供的一个"闹钟":每隔一段时间,就往你指定的输入上发一个事件。
用课堂比喻:定时器就像教室墙上的电铃,每隔固定时间"叮"一声,提醒某位同学"该你了"。
为什么不用 time.sleep 自己数时间?因为交给 DORA 的闹钟更省心——它和整个数据流的启动、停止是协调好的。我只要专心"被叫到就干活",不用自己盯着表。
三种写法:millis、secs、hz
定时器有三种写法,表达的是同一件事的不同说法:
三种可以互相换算。比如"每秒 2 次"可以写成 hz/2,也可以写成 millis/500——完全等价。
- 想直白地说"多久一次"→ 用
millis或secs(如日志每 5 秒打一次 →secs/5)。 - 想说"每秒多少次"这种频率→ 用
hz(如摄像头 30 帧 →hz/30,控制回路 100 次 →hz/100)。
选哪种只看哪种读起来更顺,性能上没差别。
tick 事件也是 INPUT
有个容易困惑的点要点破:定时器发来的事件,在代码里就是一个普通的 INPUT 事件。
看 sender 的代码:
定时器"叮"一声,节点收到的就是一个 event["type"] == "INPUT"、且 event["id"] == "tick" 的事件。所以如果一个节点有多个输入(有的来自定时器、有的来自别的节点),要用 event["id"] 区分:
定时器事件也带一个 event["value"],但它只是个占位,通常不含有用数据——定时器的意义在于"到点了"这个时机本身,而不是它带来的内容。别去 to_pylist() 它的 value。
动手试试改频率
把 4.3 的 dataflow.yml 里 sender 的定时器从 millis/500 改成 secs/2:
重新 dora run dataflow.yml,你会看到 printer 从"每半秒一行"变成"每两秒一行"。代码一个字没动,只改了配置——这就是数据流的灵活之处。
二、参数:不改代码,改行为
问题:写死的值不够灵活
看 4.2 的 sender,它发的是"开/关"。假如现在想让它发别的词,比如"红/绿"(当红绿灯用),你得改代码。如果又想要"上/下"呢?再改一次?
每换一个用途就改一次代码,很笨。专业做法是:把这些会变的值,做成"参数",从外部传进来。 这样同一份代码,配置不同就有不同行为。
用 env 传参数,用 os.getenv 读
DORA 里给节点传参数,最常用、最简单的方式是 环境变量(env)。分两步:
第一步:在 dataflow.yml 里用 env 写参数
第二步:在 sender.py 里用 os.getenv 读出来
现在:
- YAML 里配
ON_TEXT: 红 / OFF_TEXT: 绿→ printer 打印"红/绿"; - 改成
上 / 下→ 打印"上/下"; - 完全不配
env→ 用默认值"开/关"(多亏了os.getenv的第二个参数)。
代码一次都没改,行为却能随配置变化。 这就是参数化的威力。
就像同一只手,戴上不同的手套能做不同的事。参数就是给节点"戴手套"——零件还是那个零件,换套配置就有新本领!
参数是文字,数字要自己转
有一个极其常见的坑要提醒你:环境变量读出来永远是字符串(文字),哪怕你在 YAML 里写的是数字。
如果忘了转,"5" + 1 会直接报错(字符串不能和数字相加)。需要数字时,记得用 int()(整数)或 float()(小数)转一下。
- 整数(个数、索引):
int(os.getenv("BATCH_SIZE", "30")) - 小数(速度、比例):
float(os.getenv("SPEED", "1.5"))
这在真实节点里非常常见,比如摄像头节点读 CAMERA_INDEX、批处理节点读 BATCH_SIZE,都要转类型。
综合示例:一个可配置的计数器
把定时器和参数结合起来,我们做一个"可配置计数器"节点,体会两者一起用的感觉:
配套 YAML:
跑起来,printer 会每秒打印:[100]、[110]、[120]……改一下 START/STEP/定时器频率,行为立刻不同,代码纹丝不动。
动手练习
写一个节点,每隔可配置的秒数,就打印一句可配置的话(比如每 3 秒打印 "小莫还活着~")。要求秒数和那句话都来自 env 参数。
参考答案
heartbeat.py:
YAML(秒数通过定时器配置,消息通过 env):
想改成每 5 秒、说别的话?只动 YAML 就行。
常见报错 FAQ
TypeError: can only concatenate str (not "int") to str
典型的"忘了转类型"。os.getenv 读出来是字符串,做数学运算前要 int() 或 float()。
检查 dora/timer/... 的拼写:是 millis/secs/hz 三者之一,斜杠分隔,最后是数字。写错(比如 dora/timer/ms/500)定时器不会生效。
- 确认改的是正确节点的
env; - 确认代码里
os.getenv的键名和 YAML 里完全一致(区分大小写,ON_TEXT≠on_text); - 改完 YAML 要重新
dora run。
小结
- 定时器是数据流内置的"闹钟":
millis(毫秒)/secs(秒)/hz(每秒次数)三种写法等价,选顺口的用。 - 定时器的
tick在代码里就是一个INPUT事件,多输入时用event["id"]区分。 - 参数化用
env(YAML 里配)+os.getenv(代码里读):让同一份代码靠配置改变行为。 - 环境变量读出来永远是字符串,需要数字时记得
int()/float()。
本章回顾
走到这里,第四章圆满了。回头看看你都学会了什么:
- 4.1:搞懂了节点的"三段式"一生——出生、循环、退场;
- 4.2:亲手写出第一个 Python 节点(sender、echo);
- 4.3:用
dataflow.yml把节点串起来,跑通第一条自己写的数据流; - 4.4:用定时器控制节奏、用参数让节点更通用。
我现在会"表达"啦!不再只是运行别人写好的零件——你能亲手给我造零件了。下一章,我要学会处理更丰富的数据(图片、数组……),让我的表达更有内容!
下一章 第五章 · 数据的语言:Arrow,我们会把这一章反复出现的 pa.array 和 .to_pylist() 讲透,让小莫学会处理各种各样的数据。