# Upgrade to v1.5.0

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.

# Alias for PyObject*

We are going to make a very big change in v2.0 which may break everything! In order to make this upgrade easier, we are introducing an alias for PyObject* in v1.5.0.

using PyVar = PyObject*;

Please replace all PyObject* with PyVar in your code in order to be compatible with v2.0 in the future. We will try our best to keep the compatibility with v1.x series.

# Old 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, PyVar mod, PyVar type){
        // do bindings here
    }
};

int main(){
    VM* vm = new VM();
    PyVar 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();
    PyVar mod = vm->new_module("test");
    vm->register_user_class<Point>(mod, "Point",
        [](VM* vm, PyVar mod, PyVar 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, PyVar mod, PyVar type){
        // do bindings here (if you like the intrusive way)
    }
};

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

# Signature of _import_handler

The signature of _import_handler was changed from:

unsigned char* (*)(const char* name_p, int name_size, int* out_size);

to:

unsigned char* (*)(const char* name, int* out_size);

This is because str object was ensured to be null-terminated after v1.4.1.

# Signature of 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, PyVar _0){
    if(is_end) return vm->StopIteration;
    // ...
    PyVar a = VAR(1);
    PyVar 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, PyVar _0) -> unsigned{
    if(is_end) return 0;    // return 0 indicates StopIteration
    // ...
    PyVar a = VAR(1);
    PyVar 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
    ...

# Builtin function next()

Previously, next() returns StopIteration when the iterator is exhausted. In v1.5.0, it raises StopIteration exception instead, which is consistent with CPython. If you like the old way, you can use the following snippet to import the old next() function:

from __builtins import next

Related C++ APIs do not change. They still return vm->StopIteration to indicate the end of iteration.

# User config support

We used to read user_config.h file to override the default configurations. In v1.5.0, this is no longer supported. Please use config macros before #include "pocketpy.h" directly.

#define PK_ENABLE_OS            1
#define PK_ENABLE_THREAD        1
#define PK_ENABLE_PROFILER      1
// for all config macros, please refer to `include/pocketpy/config.h`
#include "pocketpy.h"

# Debugger and profiler

We added a macro PK_ENABLE_PROFILER (default is 0) to control the availability of the builtin debugger and profiler. If you want to use them, for example, you want to call breakpoint() or import line_profiler, you need to set PK_ENABLE_PROFILER to 1 before #include "pocketpy.h".

#define PK_ENABLE_PROFILER      1
#include "pocketpy.h"

# Type checking functions

  • vm->is_non_tagged_type was removed. Use vm->is_type instead.
  • vm->check_non_tagged_type was removed. Use vm->check_type instead.

# Python Stringify functions

The following functions now return Str object instead of PyVar:

  • vm->py_str
  • vm->py_repr
  • vm->py_json

Also, vm->bind__str__ and vm->bind__repr__ were changed to return Str object.