Types¶
PyOZ automatically converts between Zig and Python types. This guide covers all supported types.
Basic Types¶
| Zig Type | Python Type | Notes |
|---|---|---|
i8 - i64, u8 - u64 |
int |
Standard integers |
i128, u128 |
int |
Big integers (via string conversion) |
f32, f64 |
float |
Floating point |
bool |
bool |
Boolean |
[]const u8 |
str |
Strings (input and output) |
void |
None |
No return value |
Optional Types¶
Use ?T for values that may be None:
- As parameters:
?[]const u8becomes an optional keyword argument - As return type: return
nullto returnNone
When returning null, if a Python exception is set, it becomes an error indicator; otherwise returns None.
Special Types¶
PyOZ provides wrapper types for Python's specialized types:
| Type | Python Equivalent | Usage |
|---|---|---|
pyoz.Complex |
complex |
64-bit complex numbers |
pyoz.Complex32 |
complex |
32-bit complex (NumPy) |
pyoz.Date |
datetime.date |
Date values |
pyoz.Time |
datetime.time |
Time values |
pyoz.DateTime |
datetime.datetime |
Combined date/time |
pyoz.TimeDelta |
datetime.timedelta |
Time differences |
pyoz.Bytes |
bytes |
Byte sequences |
pyoz.Path |
str or pathlib.Path |
File paths (accepts both) |
pyoz.Decimal |
decimal.Decimal |
Arbitrary precision decimals |
pyoz.Owned(T) |
(same as T) |
Allocator-backed return values |
Create them with .init() methods (e.g., pyoz.Date.init(2024, 12, 25)).
Collections¶
Input (Zero-Copy Views)¶
These provide read access to Python collections without copying:
| Type | Python Source | Key Methods |
|---|---|---|
pyoz.ListView(T) |
list |
.len(), .get(i), .iterator() |
pyoz.DictView(K, V) |
dict |
.len(), .get(key), .contains(key), .iterator() |
pyoz.SetView(T) |
set/frozenset |
.len(), .contains(val), .iterator() |
pyoz.IteratorView(T) |
Any iterable | .next() - works with generators, ranges, etc. |
Output¶
| Type | Creates | Example |
|---|---|---|
[]const T |
list |
return &[_]i64{1, 2, 3}; |
pyoz.Dict(K, V) |
dict |
.{ .entries = &.{...} } |
pyoz.Set(T) |
set |
.{ .items = &.{...} } |
pyoz.FrozenSet(T) |
frozenset |
.{ .items = &.{...} } |
pyoz.Iterator(T) |
list |
.{ .items = &.{...} } (eager) |
pyoz.LazyIterator(T, State) |
iterator | Lazy on-demand iteration |
struct { T, U } |
tuple |
return .{ a, b }; |
Iterator vs LazyIterator¶
PyOZ provides two ways to return iterable data:
Iterator(T) - Eager iteration, converts to Python list immediately:
fn get_fibonacci() pyoz.Iterator(i64) {
const fibs = [_]i64{ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 };
return .{ .items = &fibs };
}
// Python: get_fibonacci() returns [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
LazyIterator(T, State) - Lazy iteration, generates values on-demand:
const RangeState = struct {
current: i64,
end: i64,
step: i64,
pub fn next(self: *@This()) ?i64 {
if (self.current >= self.end) return null;
const val = self.current;
self.current += self.step;
return val;
}
};
fn lazy_range(start: i64, end: i64, step: i64) pyoz.LazyIterator(i64, RangeState) {
return .{ .state = .{ .current = start, .end = end, .step = step } };
}
// Python: lazy_range(0, 1000000, 1) returns an iterator (memory efficient!)
When to use which:
| Use Case | Type | Reason |
|---|---|---|
| Small, known data | Iterator(T) |
Simple, returns a list |
| Large/infinite sequences | LazyIterator(T, State) |
Memory efficient |
| Need random access | Iterator(T) |
Lists support indexing |
| Stream processing | LazyIterator(T, State) |
Values computed on-demand |
View Types (Input Only)¶
View types provide zero-copy read access to Python collections. They are consumer-only - you can receive them as function parameters but cannot return them. This asymmetry exists because:
- Views hold references to Python objects that must remain valid
- The underlying Python object owns the memory
- Returning a view would require the caller to manage Python object lifetimes
For returning collections, use the producer types (Set, Dict, Iterator, etc.) which create new Python objects.
Fixed-Size Arrays¶
[N]T accepts a Python list of exactly N elements. Wrong size raises an error.
NumPy Arrays (Buffer Protocol)¶
Zero-copy access to NumPy arrays:
| Type | Access | Use Case |
|---|---|---|
pyoz.BufferView(T) |
Read-only | Analysis, computation |
pyoz.BufferViewMut(T) |
Read-write | In-place modification |
Supported element types: f32, f64, i8-i64, u8-u64, pyoz.Complex, pyoz.Complex32
Methods: .len(), .rows(), .cols(), .data (slice), .fill(value) (mutable only)
Access data directly via .data slice for maximum performance.
Allocator-Backed Returns (Owned)¶
When you need to return dynamically-sized data (e.g., formatted strings, variable-length arrays), use pyoz.Owned(T) instead of fixed-size stack buffers. Owned(T) pairs a heap-allocated value with its allocator — PyOZ converts the value to a Python object, then frees the backing memory automatically.
fn generate_report(count: i64) !pyoz.Owned([]const u8) {
const allocator = std.heap.page_allocator;
const result = try std.fmt.allocPrint(allocator, "Processed {d} items", .{count});
return pyoz.owned(allocator, result); // []u8 auto-coerced to []const u8
}
The pyoz.owned(allocator, value) constructor automatically coerces mutable slices ([]u8) to const ([]const u8), so std.fmt.allocPrint and allocator.alloc results work directly without @as casts.
Supported wrappers: !Owned(T) (error union) and ?Owned(T) (optional) both work — PyOZ unwraps them before converting.
Why not stack buffers? A common pattern is var buf: [4096]u8 = undefined; — this works for small strings because toPy copies the data immediately, but it silently truncates output beyond 4096 bytes and is fragile. Owned(T) has no size limit and makes the allocation/deallocation explicit.
Supported inner types: Any slice type that PyOZ can convert — []const u8 (→ str), []const i64 (→ list[int]), etc.
Class Instances¶
Pass your PyOZ classes between functions:
*const T- Read-only access to instance*T- Mutable access to instance
The class must be registered in the same module.
Raw Python Objects¶
For advanced cases, use *pyoz.PyObject to work directly with Python objects. You're responsible for reference counting and type checking.
As parameters: Accept any Python object and convert manually:
pub fn process(self: *MyClass, obj: *pyoz.PyObject) i64 {
// Use pyoz.Conversions.fromPy() or the C API directly
return pyoz.Conversions.fromPy(i64, obj) catch 0;
}
As return type: Return a raw Python object built via the C API:
pub fn children(self: *const MyClass) ?*pyoz.PyObject {
const list = pyoz.py.PyList_New(2) orelse return null;
// ... populate list with PyList_SetItem ...
return list;
}
When returning ?*pyoz.PyObject, the converter passes it through as-is — no additional conversion is applied. Return null to signal an error (set the exception first with pyoz.raiseValueError() etc.).
Converting registered classes to PyObject: When building raw Python containers that hold instances of your registered classes, use the module-level converter instead of pyoz.Conversions. The generic pyoz.Conversions has no class knowledge and will return null for class types.
pub const Module = pyoz.module(.{
.name = "mymodule",
.classes = &.{ pyoz.class("Node", Node) },
});
const Node = struct {
value: i64,
/// Build a Python list of Node objects using the module converter
pub fn children(self: *const Node) ?*pyoz.PyObject {
const list = pyoz.py.PyList_New(0) orelse return null;
for (self.getChildren()) |child| {
// Module.toPy knows about Node — pyoz.Conversions.toPy does NOT
const obj = Module.toPy(Node, child) orelse {
pyoz.py.Py_DecRef(list);
return null;
};
_ = pyoz.py.PyList_Append(list, obj);
pyoz.py.Py_DecRef(obj);
}
return list;
}
};
Callable (Python Callbacks)¶
Use pyoz.Callable(ReturnType) to accept Python functions, lambdas, or any callable object as a parameter. PyOZ handles argument conversion, the call, result conversion, and all reference counting automatically.
Python:
The return type is always optional (?ReturnType) — returns null (Python None) if the callback raises an exception. The exception propagates to Python.
For callbacks that return nothing, use Callable(void). The .call() method returns bool (true = success, false = exception):
Methods:
| Method | Description |
|---|---|
.call(.{ args... }) |
Call with arguments (Zig tuple) |
.callNoArgs() |
Call with no arguments |
.obj |
Access the underlying *PyObject |
Type Conversion Summary¶
| Direction | Zig | Python |
|---|---|---|
| Both | Integers, floats, bool, strings | int, float, bool, str |
| Both | ?T |
T or None |
| Both | Special types (Complex, DateTime, etc.) | Corresponding Python types |
| Input only | View types (ListView, BufferView, etc.) | list, dict, set, ndarray |
| Both | *pyoz.PyObject |
Any Python object (advanced) |
| Input only | *const T, *T |
Class instances |
| Input only | pyoz.Callable(T) |
Functions, lambdas, any callable |
| Input only | [N]T |
list (exact size) |
| Output only | Slices, Dict, Set | list, dict, set |
| Output only | Anonymous struct | tuple |
| Output only | pyoz.Owned(T) |
Same as T (frees backing memory) |