""" 08_python_subport — drive a *Python* node from Python via write()/read() taps. Graph: (fed by net.write()) --int--> [py_triple] --int--> (tapped by net.read()) 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 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() # 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.build() net.start() # Push values in from Python and read the Python node's results back out. inputs = [1, 2, 7, 10, 100] results = [] 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("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