#
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"
Enabling the profiler has a performance overhead. Only enable it when you need it.
#
Type checking functions
vm->is_non_tagged_type
was removed. Usevm->is_type
instead.vm->check_non_tagged_type
was removed. Usevm->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.