18 · Modules
Python with extern
Typed Python functions, native packages and async calls through CPython.
On this page
Import the Python provider and declare a function using Zolo types. Its body is ordinary Python, including indentation, comments and local imports. Call the function like any other Zolo function. CPython runs inside the process.
Use an imported alias, group small functions and capture a Zolo value in a
Python expression. A local import math runs when the function is called.
Triple-quoted strings and braces in comments preserve their native meaning.
// In-process CPython: ordinary Zolo headers, native Python bodies.
use plugin python::{python as py}
extern py fn add(a: int, b: int) -> int {
# Braces in comments stay inside Python: } {
return a + b
}
extern py {
fn normalize(text: str) -> str {
return text.strip().lower()
}
fn factorial(n: int) -> int {
result = 1
for value in range(2, n + 1):
result *= value
return result
}
}
let original = " Zolo "
let clean: str = extern py {
return original.strip().upper()
}
let greeting: str = extern py { return f"Hi {original.strip()}" }
extern py fn native_import(value: float) -> float {
import math
return math.sqrt(value)
}
extern py fn literal() -> str {
value = '''Braces { } and "quotes"'''
return value
}
print(add(20, 22))
print(normalize(original))
print(factorial(5))
print(clean)
print(greeting)
print(native_import(81.0))
print(literal())
// expected:
// 42
// zolo
// 120
// ZOLO
// Hi Zolo
// 9
// Braces { } and "quotes"
Copy typed records, preserve exact 64-bit integers and nullable array slots, transfer bytes, pass a Zolo closure and call a returned Python closure. A live Python object retains attributes and state across calls.
// Exact values and live Python callables/objects share the ordinary syntax.
use plugin python::{python}
struct Payload { value: int, optional: int?, items: [int] }
extern python {
fn echo(value: Payload) -> Payload {
assert "optional" in value and value.optional is None
return value
}
fn nullable(values: [int?]) -> [int?] { return values }
fn empty() -> [int] { return [] }
fn binary() -> bytes { return b"\x00\xff\x2a" }
fn call(callback: fn(int) -> int) -> int { return callback(41) }
fn factory(delta: int) -> fn(int) -> int { return lambda value: value + delta }
fn counter() -> Any {
class Counter:
def __init__(self):
self.value = 0
def add(self, delta):
self.value += delta
return self.value
return Counter()
}
}
let copy = echo(Payload {
value: 9223372036854775807,
optional: nil,
items: [7, 8, 9],
})
print(copy.value)
print(copy.optional == nil)
print(copy.items.len())
let values = nullable([7, nil, nil])
print(values.len())
print(values[1] == nil)
print(empty().len())
print(binary().len())
print(call(|value: int| value + 1))
let increment = factory(1)
print(increment(41))
let live = counter()
print(live.add(40))
live.value = 41
print(live.add(1))
print(live.value)
// expected:
// 9223372036854775807
// true
// 3
// 3
// true
// 0
// 3
// 42
// 42
// 40
// 42
// 42
Native source imports expose annotated functions and classes. Relative paths follow the importing Zolo file. An inline native module can also expose free functions through its Zolo namespace.
Import functions and a class from tools.py, use a class alias, call an async
member and access an annotated field or property.
use plugin python::{python}
extern python from "./python_fixtures/tools.py" use {twice, later, Counter as Count}
extern python mod Tools {
def greet(name: str) -> str:
return f"Hello {name}"
}
print(twice(21))
print(await later(41))
let counter = Count(40)
print(counter.add(1))
print(await counter.add_later(1))
print(counter.value)
print(counter.total)
print(Tools.greet("Zolo"))
// expected:
// 42
// 42
// 41
// 42
// 42
// 42
// Hello Zolo
Await Python coroutines and callbacks returning Zolo Futures. Combine calls
with Promise.all, catch failures and request cancellation of a Python task.
use plugin python::{python}
use std::async::Promise
extern python async fn transform(value: int, callback: fn(int) -> Future<int>) -> int {
import asyncio
await asyncio.sleep(0.001)
return await callback(value)
}
extern python async fn fail() -> int {
import asyncio
await asyncio.sleep(0.001)
raise ValueError("Python failed")
}
extern python async fn wait_for_cancel() -> int {
import asyncio
await asyncio.sleep(60)
return 0
}
// Completing the entry function keeps its spawned tasks alive.
extern python async fn schedule(callback: fn(int) -> int) -> int {
import asyncio
async def later():
await asyncio.sleep(0.01)
callback(42)
asyncio.create_task(later())
return 42
}
async fn increment(value: int) -> int {
sleep 2ms
return value + 1
}
async fn reject(value: int) -> int {
sleep 1ms
panic("callback failed")
return value
}
async fn cancel_soon(future: Future<int>) -> bool {
sleep 5ms
future.cancel()
return future.is_done()
}
let values = await Promise.all([
transform(41, increment),
transform(42, increment),
])
print(values[0])
print(values[1])
print(try { await transform(0, reject) } catch error { "callback caught" })
print(try { await fail() } catch error { "Python caught" })
let pending = wait_for_cancel()
print(await cancel_soon(pending))
let background: [int] = []
print(await schedule(|value: int| {
background.push(value)
return value
}))
sleep 40ms
print(background[0])
// expected:
// 42
// 43
// callback caught
// Python caught
// true
// 42
// 42
Use Python and C# in one process. A callback from either language can reenter Zolo and call the other provider.
use plugin dotnet::{csharp}
use plugin python::{python}
extern csharp fn managed_add(a: int, b: int) -> int { return a + b; }
extern python fn python_add(a: int, b: int) -> int { return a + b }
extern python fn call_python(callback: fn(int) -> int) -> int {
return callback(41)
}
extern csharp fn call_managed(callback: fn(int) -> int) -> int {
return callback(41);
}
print(managed_add(20, 22))
print(python_add(20, 22))
print(call_python(|value: int| managed_add(value, 1)))
print(call_managed(|value: int| python_add(value, 1)))
print(python_add(managed_add(20, 1), 21))
// expected:
// 42
// 42
// 42
// 42
// 42
Select a complete conventional CPython 3.9–3.14 installation with the GIL using
ZOLO_PYTHON_HOME before first use,
and install dependencies into that environment. NumPy requires its normal
installation; 27-extern-python-numpy.zolo exercises its native extension.
Packaging this plugin retains source artifacts, while CPython and external
packages remain application dependencies. PyTorch has not been validated here.
Editor diagnostics, completion, hover and signature help analyze current source without executing authored code. VM, native and LLVM share the runtime contract; a wasm target without CPython rejects the binding. This experimental provider has been verified on Windows with CPython 3.12, including NumPy and execution after authoring sources are removed. Linux/macOS and other interpreter versions have not yet been verified at runtime.
See also