pocketpy/docs/1_5_0.md
2024-05-04 23:04:22 +08:00

3.7 KiB

icon title order
log Upgrade to v1.5.0 25

We are applying a major API refactoring in this release. The main goal is to make the API more consistent and easier to use. We are also adding new features and improvements. This release is not backward compatible with the previous versions. Please read the following guide to upgrade your project.

New style bindings

We introduced the new style bindings vm->bind in v1.1.3 and deprecated the old style bindings vm->bind_func<> and vm->bind_method<>.

In this release, we added an ARGC-based binding vm->bind_func (without template parameter) to replace the old style bindings. If you are still using vm->bind_func<> and vm->bind_method<>, please update your code to use vm->bind_func instead.

// old style (removed)
vm->bind_func<N>(obj, "add", f_add);
vm->bind_method<M>(obj, "get", f_get);

// new ARGC-based binding
vm->bind_func(obj, "add", N, f_add);
vm->bind_func(obj, "get", M+1, f_get);  // +1 for `self`

Class bindings

Previously, if we want to write bindings for a C++ class, we need to add PY_CLASS macro and implement a magic method _register. This way was known as an intrusive way. For example:

struct Point{
    PY_CLASS(Point, test, Point)

    int x, y;

    static void _register(VM* vm, PyObject* mod, PyObject* type){
        // do bindings here
    }
};

int main(){
    VM* vm = new VM();
    PyObject* mod = vm->new_module("test");
    Point::register_class(vm, mod);
    return 0;
}

In v1.5.0, the PY_CLASS macro was deprecated. We introduced a non-intrusive way to write class bindings via vm->register_user_class<>. The example above can be rewritten as follows:

struct Point{
    int x, y;
};

int main(){
    VM* vm = new VM();
    PyObject* mod = vm->new_module("test");
    vm->register_user_class<Point>(mod, "Point",
        [](VM* vm, PyObject* mod, PyObject* type){
            // do bindings here
        });
    return 0;
}

The magic method _register is kept for users who still wants to use the intrusive way. This is achieved by an overloaded version of vm->register_user_class<>. For example:

struct Point{
    int x, y;

    static void _register(VM* vm, PyObject* mod, PyObject* type){
        // do bindings here (if you like the intrusive way)
    }
};

int main(){
    VM* vm = new VM();
    PyObject* mod = vm->new_module("test");
    vm->register_user_class<Point>(mod, "Point");
    return 0;
}

Optimization of vm->bind__next__

vm->bind__next__ is a special method that is used to implement the iterator protocol. Previously, if you want to return multiple values, you need to pack them into a tuple. For example:

vm->bind__next__(type, [](VM* vm, PyObject* _0){
    // ...
    PyObject* a = VAR(1);
    PyObject* b = VAR(2);
    return VAR(Tuple(a, b));
});
for a, b in my_iter:
    # There is tuple creation and destruction for each iteration
    ...

It is not efficient because unnecessary tuple creation and destruction are involved. In v1.5.0, you need to use stack-based style to reimplement the above code:

vm->bind__next__(type, [](VM* vm, PyObject* _0) -> unsigned{
    // ...
    PyObject* a = VAR(1);
    PyObject* b = VAR(2);
    vm->s_data.push(a);     // directly push to the stack
    vm->s_data.push(b);     // directly push to the stack
    return 2;  // return the number of values
});

In this way, the interpreter only creates a tuple when it is necessary. It can improve ~25% performance when iterating over a large array.

for a, b in my_iter:
    # No tuple creation and destruction
    ...

for t in my_iter:
    # Create a tuple lazily
    ...