Skip to content

Python Classes

Classes are defined using py.class method.

pub const Animal = py.class("Animal", struct {
    pub const __doc__ = "Animal docstring";

    const Self = @This();

    kind: u64,

    pub fn __new__(args: struct { kind: u64 }) !Self {
        return .{ .kind = args.kind };
    }

    pub fn get_kind(self: *Self) !u64 {
        return self.kind;
    }

    pub fn get_kind_name(self: *Self) !py.PyString {
        return switch (self.kind) {
            1 => py.PyString.create("Dog"),
            2 => py.PyString.create("Cat"),
            3 => py.PyString.create("Parrot"),
            else => py.RuntimeError.raise("Unknown animal kind"),
        };
    }
});

The returned type of py.class is the same struct that has been used to define the class.

You can refer to your own state by taking pointer to self self: *@This(). Classes' internal state is only accessible from the extension code. Currently pydust doesn't support declaring Python class attributes.

Instantiation and constructors

Pydust provides convenience function py.init that creates an instance of pydust defined class. This will still create a PyObject internally and return the internal state tied to that object. You can then call methods on that object as usual which will avoid dispatching the method through Python.

If your class defines a pub fn __new__(args: struct{}) !Self function, then it is possible to instantiate it from Python. Otherwise, it is only possible to instantiate the class from Zig using py.init.

pub const Owner = py.class("Owner", struct {
    pub const __doc__ = "Takes care of an animal";

    // Allows this class to be instantiated from Python
    pub fn __new__() !@This() {
        return .{};
    }

    pub fn name_puppy(args: struct { name: py.PyString }) !*Dog {
        return try py.init(Dog, .{ .name = args.name });
    }
});

Subclasses

Creating subclasses is similar to classes, with the exception of needing to provide references to your base classes.

pub const Dog = py.subclass("Dog", &.{Animal}, struct {
    pub const __doc__ = "Adorable animal docstring";
    const Self = @This();

    // A subclass of a Pydust class is required to hold its parent's state.
    animal: Animal,
    name: py.PyString,

    pub fn __new__(args: struct { name: py.PyString }) !Self {
        args.name.incref();
        return .{
            .animal = .{ .kind = 1 },
            .name = args.name,
        };
    }

    pub fn __del__(self: *Self) void {
        self.name.decref();
    }

    pub fn __len__(self: *const Self) !usize {
        _ = self;
        return 4;
    }

    pub fn __str__(self: *const Self) !py.PyString {
        var pystr = try py.PyString.create("Dog named ");
        return pystr.append(self.name);
    }

    pub fn __repr__(self: *const Self) !py.PyString {
        var pyrepr = try py.PyString.create("Dog(");
        pyrepr = try pyrepr.append(self.name);
        return pyrepr.appendSlice(")");
    }

    pub fn __add__(self: *const Self, other: py.PyString) !*Self {
        const name = try self.name.append(other);
        return py.init(Self, .{ .name = name });
    }

    pub fn get_name(self: *const Self) !py.PyString {
        return self.name;
    }

    pub fn make_noise(args: struct { is_loud: bool = false }) !py.PyString {
        if (args.is_loud) {
            return py.PyString.create("Bark!");
        } else {
            return py.PyString.create("bark...");
        }
    }

    pub fn get_kind_name(self: *Self) !py.PyString {
        var super = try py.super(Dog, self);
        var superKind = try super.get("get_kind_name");
        var kindStr = try py.PyString.checked(try superKind.call0());
        kindStr = try kindStr.appendSlice(" named ");
        kindStr = try kindStr.append(self.name);
        return kindStr;
    }
});

Subclasses can then use builtins like super to invoke methods on their parent types. Bear in mind that Python superclasses aren't actually fields on the subtype. Thus it is only possible to refer to supertype methods from that supertype.

Binary Operators

Pydust supports classes implementing binary operators (e.g. __add__ or bitwise operators).

1
2
3
4
    pub fn __add__(self: *const Self, other: py.PyString) !*Self {
        const name = try self.name.append(other);
        return py.init(Self, .{ .name = name });
    }

The self parameter must be a pointer to the class type. The other parameter can be of any Pydust supported type.

Supported binary operators are: __add__, __sub__, __mul__, __mod__, __divmod__, __pow__, __lshift__, __rshift__, __and__, __xor__, __or__, __truediv__, __floordiv__, __matmul__, __getitem__.