Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

__new__ -> __init__ #218

Merged
merged 3 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,16 @@
],
"program": "zig-out/bin/debug.bin",
},
{
"type": "lldb",
"request": "launch",
"name": "LLDB Python",
"program": "${command:python.interpreterPath}",
"args": [
"-m",
"pytest",
],
"cwd": "${workspaceFolder}"
},
]
}
22 changes: 17 additions & 5 deletions docs/guide/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,24 @@ corresponding Python object around it.
For example, the class above can be correctly constructed using the `py.init` function:

```zig
const some_class = try py.init(SomeClass, .{ .count = 1 });
const some_instance = try py.init(SomeClass, .{ .count = 1 });
```

Alternatively, a class can be allocated without instantiation. This can be useful when some
fields of the class refer by pointer to other fields.

```zig
var some_instance = try py.alloc(SomeClass);
some_instance.* = .{
.foo = 124,
.bar = &some_instance.foo,
};
```

### From Python

To enable instantiation from Python, you must define a `__new__` function
that takes a CallArgs struct and returns a new instance of `Self`.
Declaring a `__init__` function signifies to Pydust to make your class instantiable
from Python. This function may take zero arguments as a pure marker to allow instantiation.

=== "Zig"

Expand All @@ -50,7 +61,7 @@ Inheritance allows you to define a subclass of another Zig Pydust class.

Subclasses are defined by including the parent class struct as a field of the subclass struct.
They can then be instantiated from Zig using `py.init`, or from Python
if a `__new__` function is defined.
if a `__init__` function is defined.

=== "Zig"

Expand Down Expand Up @@ -162,7 +173,8 @@ const binaryfunc = fn(*Self, object) !object;

| Method | Signature |
| :--------- | :--------------------------------------- |
| `__new__` | `#!zig fn(CallArgs) !Self` |
| `__init__` | `#!zig fn() void` |
| `__init__` | `#!zig fn(*Self) !void` |
| `__init__` | `#!zig fn(*Self, CallArgs) !void` |
| `__del__` | `#!zig fn(*Self) void` |
| `__repr__` | `#!zig fn(*Self) !py.PyString` |
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Please refer to the annotations in this example module for an explanation of the
2. Unlike regular Python modules, native Python modules are able to carry private internal state.

3. Any fields that cannot be defaulted at comptime (i.e. if they require calling into Python)
must be initialized in the module's `__new__` function.
must be initialized in the module's `__init__` function.

4. Module functions taking a `*Self` or `*const Self` argument are passed a pointer
to their internal state.
Expand Down
2 changes: 1 addition & 1 deletion example/buffers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ class ConstantBuffer:
A class implementing a buffer protocol
"""

def __init__(elem, length, /):
def __init__(self, elem, length, /):
pass
7 changes: 2 additions & 5 deletions example/buffers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@ pub const ConstantBuffer = py.class(struct {
shape: []const isize, // isize to be compatible with Python API
format: [:0]const u8 = "l", // i64

pub fn __new__(args: struct { elem: i64, length: u32 }) !Self {
pub fn __init__(self: *Self, args: struct { elem: i64, length: u32 }) !void {
const values = try py.allocator.alloc(i64, args.length);
@memset(values, args.elem);

const shape = try py.allocator.alloc(isize, 1);
shape[0] = @intCast(args.length);

return Self{
.values = values,
.shape = shape,
};
self.* = .{ .values = values, .shape = shape };
}

pub fn __del__(self: *Self) void {
Expand Down
14 changes: 7 additions & 7 deletions example/classes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Animal:
def species(self, /): ...

class Callable:
def __init__():
def __init__(self, /):
pass
def __call__(self, /, *args, **kwargs):
"""
Expand All @@ -13,18 +13,18 @@ class Callable:
...

class ConstructableClass:
def __init__(count, /):
def __init__(self, count, /):
pass

class Counter:
def __init__():
def __init__(self, /):
pass
def increment(self, /): ...

count: ...

class Hash:
def __init__(x, /):
def __init__(self, x, /):
pass
def __hash__(self, /):
"""
Expand All @@ -41,19 +41,19 @@ class SomeClass:
"""

class User:
def __init__(name, /):
def __init__(self, name, /):
pass
@property
def email(self): ...
@property
def greeting(self): ...

class ZigOnlyMethod:
def __init__(x, /):
def __init__(self, x, /):
pass
def reexposed(self, /): ...

class Dog(Animal):
def __init__(breed, /):
def __init__(self, breed, /):
pass
def breed(self, /): ...
29 changes: 14 additions & 15 deletions example/classes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ pub const SomeClass = py.class(struct {
pub const ConstructableClass = py.class(struct {
count: u32 = 0,

pub fn __new__(args: struct { count: u32 }) @This() {
return .{ .count = args.count };
pub fn __init__(self: *@This(), args: struct { count: u32 }) void {
self.count = args.count;
}
});
// --8<-- [end:constructor]
Expand All @@ -48,9 +48,9 @@ pub const Dog = py.class(struct {
animal: Animal,
breed: py.PyString,

pub fn __new__(args: struct { breed: py.PyString }) !Self {
pub fn __init__(self: *Self, args: struct { breed: py.PyString }) !void {
args.breed.incref();
return .{
self.* = .{
.animal = .{ .species = try py.PyString.create("dog") },
.breed = args.breed,
};
Expand All @@ -67,9 +67,9 @@ pub const Dog = py.class(struct {
pub const User = py.class(struct {
const Self = @This();

pub fn __new__(args: struct { name: py.PyString }) Self {
pub fn __init__(self: *Self, args: struct { name: py.PyString }) void {
args.name.incref();
return .{ .name = args.name, .email = .{} };
self.* = .{ .name = args.name, .email = .{} };
}

name: py.PyString,
Expand Down Expand Up @@ -105,9 +105,8 @@ pub const Counter = py.class(struct {

count: py.attribute(usize) = .{ .value = 0 },

pub fn __new__(args: struct {}) Self {
_ = args;
return .{};
pub fn __init__(self: *Self) void {
_ = self;
}

pub fn increment(self: *Self) void {
Expand All @@ -129,8 +128,8 @@ pub const ZigOnlyMethod = py.class(struct {
const Self = @This();
number: i32,

pub fn __new__(args: struct { x: i32 }) Self {
return .{ .number = args.x };
pub fn __init__(self: *Self, args: struct { x: i32 }) void {
self.number = args.x;
}

pub usingnamespace py.zig(struct {
Expand All @@ -149,8 +148,8 @@ pub const Hash = py.class(struct {
const Self = @This();
number: u32,

pub fn __new__(args: struct { x: u32 }) Self {
return .{ .number = args.x };
pub fn __init__(self: *Self, args: struct { x: u32 }) void {
self.number = args.x;
}

pub fn __hash__(self: *const Self) usize {
Expand All @@ -163,8 +162,8 @@ pub const Hash = py.class(struct {
pub const Callable = py.class(struct {
const Self = @This();

pub fn __new__() Self {
return .{};
pub fn __init__(self: *Self) void {
_ = self;
}

pub fn __call__(self: *const Self, args: struct { i: u32 }) u32 {
Expand Down
2 changes: 1 addition & 1 deletion example/iterators.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Range:
An example of iterable class
"""

def __init__(lower, upper, step, /):
def __init__(self, lower, upper, step, /):
pass
def __iter__(self, /):
"""
Expand Down
4 changes: 2 additions & 2 deletions example/iterators.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub const Range = py.class(struct {
upper: i64,
step: i64,

pub fn __new__(args: struct { lower: i64, upper: i64, step: i64 }) Self {
return .{ .lower = args.lower, .upper = args.upper, .step = args.step };
pub fn __init__(self: *Self, args: struct { lower: i64, upper: i64, step: i64 }) void {
self.* = .{ .lower = args.lower, .upper = args.upper, .step = args.step };
}

pub fn __iter__(self: *const Self) !*RangeIterator {
Expand Down
4 changes: 2 additions & 2 deletions example/modules.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const Self = @This(); // (1)!
count: u32 = 0, // (2)!
name: py.PyString,

pub fn __new__() !Self { // (3)!
return .{ .name = try py.PyString.create("Ziggy") };
pub fn __init__(self: *Self) !void { // (3)!
self.* = .{ .name = try py.PyString.create("Ziggy") };
}

pub fn __del__(self: Self) void {
Expand Down
10 changes: 5 additions & 5 deletions example/operators.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

class Comparator:
def __init__(num, /):
def __init__(self, num, /):
pass
def __lt__(self, value, /):
"""
Expand Down Expand Up @@ -35,7 +35,7 @@ class Comparator:
...

class Equals:
def __init__(num, /):
def __init__(self, num, /):
pass
def __lt__(self, value, /):
"""
Expand Down Expand Up @@ -69,7 +69,7 @@ class Equals:
...

class LessThan:
def __init__(name, /):
def __init__(self, name, /):
pass
def __lt__(self, value, /):
"""
Expand Down Expand Up @@ -103,7 +103,7 @@ class LessThan:
...

class Operator:
def __init__(num, /):
def __init__(self, num, /):
pass
def __truediv__(self, value, /):
"""
Expand All @@ -118,7 +118,7 @@ class Operator:
def num(self, /): ...

class Ops:
def __init__(num, /):
def __init__(self, num, /):
pass
def __add__(self, value, /):
"""
Expand Down
20 changes: 10 additions & 10 deletions example/operators.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pub const Ops = py.class(struct {

num: u64,

pub fn __new__(args: struct { num: u64 }) Self {
return .{ .num = args.num };
pub fn __init__(self: *Self, args: struct { num: u64 }) !void {
self.num = args.num;
}

pub fn num(self: *const Self) u64 {
Expand Down Expand Up @@ -171,8 +171,8 @@ pub const Operator = py.class(struct {

num: u64,

pub fn __new__(args: struct { num: u64 }) Self {
return .{ .num = args.num };
pub fn __init__(self: *Self, args: struct { num: u64 }) void {
self.num = args.num;
}

pub fn num(self: *const Self) u64 {
Expand Down Expand Up @@ -204,8 +204,8 @@ pub const Comparator = py.class(struct {

num: u64,

pub fn __new__(args: struct { num: u64 }) Self {
return .{ .num = args.num };
pub fn __init__(self: *Self, args: struct { num: u64 }) void {
self.num = args.num;
}

pub fn __richcompare__(self: *const Self, other: *const Self, op: py.CompareOp) bool {
Expand All @@ -227,8 +227,8 @@ pub const Equals = py.class(struct {

num: u64,

pub fn __new__(args: struct { num: u64 }) Self {
return .{ .num = args.num };
pub fn __init__(self: *Self, args: struct { num: u64 }) void {
self.num = args.num;
}

pub fn __eq__(self: *const Self, other: *const Self) bool {
Expand All @@ -243,9 +243,9 @@ pub const LessThan = py.class(struct {

name: py.PyString,

pub fn __new__(args: struct { name: py.PyString }) Self {
pub fn __init__(self: *Self, args: struct { name: py.PyString }) void {
args.name.incref();
return .{ .name = args.name };
self.name = args.name;
}

pub fn __lt__(self: *const Self, other: *const Self) !bool {
Expand Down
13 changes: 9 additions & 4 deletions pydust/src/builtins.zig
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,25 @@ pub fn import(module_name: [:0]const u8) !py.PyObject {
return (try py.PyModule.import(module_name)).obj;
}

/// Instantiate a class defined in Pydust.
pub inline fn init(comptime Cls: type, args: Cls) PyError!*Cls {
/// Allocate a Pydust class, but does not initialize the memory.
pub fn alloc(comptime Cls: type) PyError!*Cls {
const pytype = try self(Cls);
defer pytype.decref();

// Alloc the class
// NOTE(ngates): we currently don't allow users to override tp_alloc, therefore we can shortcut
// using ffi.PyType_GetSlot(tp_alloc) since we know it will always return ffi.PyType_GenericAlloc
const pyobj: *pytypes.PyTypeStruct(Cls) = @alignCast(@ptrCast(ffi.PyType_GenericAlloc(@ptrCast(pytype.obj.py), 0) orelse return PyError.PyRaised));
pyobj.state = args;

return &pyobj.state;
}

/// Allocate and instantiate a class defined in Pydust.
pub inline fn init(comptime Cls: type, state: Cls) PyError!*Cls {
const cls: *Cls = try alloc(Cls);
cls.* = state;
return cls;
}

/// Check if object is an instance of cls.
pub fn isinstance(object: anytype, cls: anytype) !bool {
const pyobj = py.object(object);
Expand Down
Loading