Skip to content

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.

23-extern-python.zolo
Playground
// 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.

24-extern-python-data.zolo
Playground
// 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.

25-extern-python-imports.zolo
Playground
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.

26-extern-python-async.zolo
Playground
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.

28-extern-provider-coexistence.zolo
Playground
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.

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enpt-br