From 2b0873b61b3164433aaabe3649a3341480d3a640 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 4 Jul 2026 14:44:24 +0200 Subject: [PATCH] examples: make 08_python_subport run a real Python node The example was named "python subport" but its graph was entirely C++ (ProduceNode -> DoubleItNode); Python only tapped the output, leaving a dangling "#todo: return value to network". Rewrite so the only node in the graph is a pure-Python py_triple, driven from both ends via the subport taps: net.write() injects inputs and net.read() pulls results back, closing the round trip. Also del the network at the end so its callable cycle is reclaimed deterministically. --- examples/08_python_subport/example.py | 53 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/examples/08_python_subport/example.py b/examples/08_python_subport/example.py index 1351b5e..4d4600c 100644 --- a/examples/08_python_subport/example.py +++ b/examples/08_python_subport/example.py @@ -1,38 +1,55 @@ """ -08_python_subport — tap a C++ node's output from Python using net.read(). +08_python_subport — drive a *Python* node from Python via write()/read() taps. Graph: - [ProduceNode] --int--> [DoubleItNode] --int--> (tapped by net.read()) + (fed by net.write()) --int--> [py_triple] --int--> (tapped by net.read()) -The sink is Python: instead of connecting a PrintItNode, we call net.read() -to pull values out of DoubleItNode's output directly into Python. -We also demonstrate net.write() by injecting a value into DoubleItNode's input. +Unlike 07, there is no C++ source or sink here: the only node in the network is +a pure-Python function, py_triple. Python plays *both* the producer and the +consumer by using the subport taps: + + * net.write("py", 0, v) injects v into py_triple's input (Python -> network) + * net.read("py", 0) pulls py_triple's output back out (network -> Python) + +This closes the loop the old version left as a "#todo": a value flows from +Python, through a Python node running inside the network, and back to Python. """ import sys -import time -import threading -sys.path.insert(0, "build/python") +sys.path.insert(0, "build/python") # for `python examples/.../example.py` from repo root import kpn_python as kpn + +def py_triple(x: int) -> int: + return x * 3 + + net = kpn.Network() -net.add("src", kpn.make_produce()) -net.add("dbl", kpn.make_double_it()) +# The whole network is a single Python node with a tapped input and output. +net.add_node("py", py_triple, inputs=["int"], outputs=["int"]) -net.connect("src", 0, "dbl", 0) net.build() net.start() -# Collect a few values from DoubleItNode's output via Python tap +# Push values in from Python and read the Python node's results back out. +inputs = [1, 2, 7, 10, 100] results = [] -for _ in range(5): - val = net.read("dbl", 0) - results.append(val) +for v in inputs: + net.write("py", 0, v) # Python -> py_triple input + results.append(net.read("py", 0)) # py_triple output -> Python net.stop() -print("values read from C++ DoubleItNode output:", results) -assert all(v == 84 for v in results), f"expected all 84, got {results}" -print("all correct (42 * 2 = 84)") +print("inputs written from Python: ", inputs) +print("outputs read from py_triple:", results) + +expected = [v * 3 for v in inputs] +assert results == expected, f"expected {expected}, got {results}" +print("all correct (x * 3 computed by a Python node inside the network)") + +# Drop the network deterministically. The network holds the Python callable, +# which (via globals) forms a reference cycle; deleting the global breaks it so +# the network is reclaimed promptly instead of lingering to interpreter exit. +del net