This commit is contained in:
blueloveTH 2024-05-12 20:24:16 +08:00
parent f5de8b12b8
commit a37b6043d9
12 changed files with 128 additions and 116 deletions

View File

@ -105,7 +105,7 @@ int main(){
vm->exec("a = [1, 2, 3]"); vm->exec("a = [1, 2, 3]");
// Eval the sum of the list // Eval the sum of the list
PyObject* result = vm->eval("sum(a)"); PyVar result = vm->eval("sum(a)");
std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6 std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6
// Bindings // Bindings
@ -117,7 +117,7 @@ int main(){
}); });
// Call the function // Call the function
PyObject* f_add = vm->_main->attr("add"); PyVar f_add = vm->_main->attr("add");
result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7)); result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7));
std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10 std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10

View File

@ -6,6 +6,18 @@ order: 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. 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`.
```cpp
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 ## Old style bindings
We introduced the new style bindings `vm->bind` in [`v1.1.3`](https://github.com/pocketpy/pocketpy/releases/tag/v1.1.3) and deprecated the old style bindings `vm->bind_func<>` and `vm->bind_method<>`. We introduced the new style bindings `vm->bind` in [`v1.1.3`](https://github.com/pocketpy/pocketpy/releases/tag/v1.1.3) and deprecated the old style bindings `vm->bind_func<>` and `vm->bind_method<>`.
@ -34,14 +46,14 @@ struct Point{
int x, y; int x, y;
static void _register(VM* vm, PyObject* mod, PyObject* type){ static void _register(VM* vm, PyVar mod, PyVar type){
// do bindings here // do bindings here
} }
}; };
int main(){ int main(){
VM* vm = new VM(); VM* vm = new VM();
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
Point::register_class(vm, mod); Point::register_class(vm, mod);
return 0; return 0;
} }
@ -56,9 +68,9 @@ struct Point{
int main(){ int main(){
VM* vm = new VM(); VM* vm = new VM();
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
vm->register_user_class<Point>(mod, "Point", vm->register_user_class<Point>(mod, "Point",
[](VM* vm, PyObject* mod, PyObject* type){ [](VM* vm, PyVar mod, PyVar type){
// do bindings here // do bindings here
}); });
return 0; return 0;
@ -72,14 +84,14 @@ This is achieved by an overloaded version of `vm->register_user_class<>`. For ex
struct Point{ struct Point{
int x, y; int x, y;
static void _register(VM* vm, PyObject* mod, PyObject* type){ static void _register(VM* vm, PyVar mod, PyVar type){
// do bindings here (if you like the intrusive way) // do bindings here (if you like the intrusive way)
} }
}; };
int main(){ int main(){
VM* vm = new VM(); VM* vm = new VM();
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
vm->register_user_class<Point>(mod, "Point"); vm->register_user_class<Point>(mod, "Point");
return 0; return 0;
} }
@ -107,11 +119,11 @@ Previously, if you want to return multiple values, you need to pack them into a
For example: For example:
```cpp ```cpp
vm->bind__next__(type, [](VM* vm, PyObject* _0){ vm->bind__next__(type, [](VM* vm, PyVar _0){
if(is_end) return vm->StopIteration; if(is_end) return vm->StopIteration;
// ... // ...
PyObject* a = VAR(1); PyVar a = VAR(1);
PyObject* b = VAR(2); PyVar b = VAR(2);
return VAR(Tuple(a, b)); return VAR(Tuple(a, b));
}); });
``` ```
@ -126,11 +138,11 @@ It is not efficient because unnecessary tuple creation and destruction are invol
In `v1.5.0`, you need to use stack-based style to reimplement the above code: In `v1.5.0`, you need to use stack-based style to reimplement the above code:
```cpp ```cpp
vm->bind__next__(type, [](VM* vm, PyObject* _0) -> unsigned{ vm->bind__next__(type, [](VM* vm, PyVar _0) -> unsigned{
if(is_end) return 0; // return 0 indicates StopIteration if(is_end) return 0; // return 0 indicates StopIteration
// ... // ...
PyObject* a = VAR(1); PyVar a = VAR(1);
PyObject* b = VAR(2); PyVar b = VAR(2);
vm->s_data.push(a); // directly push to the stack vm->s_data.push(a); // directly push to the stack
vm->s_data.push(b); // directly push to the stack vm->s_data.push(b); // directly push to the stack
return 2; // return the number of values return 2; // return the number of values
@ -197,7 +209,7 @@ Enabling the profiler has a performance overhead. Only enable it when you need i
## Python Stringify functions ## Python Stringify functions
The following functions now return `Str` object instead of `PyObject*`: The following functions now return `Str` object instead of `PyVar`:
+ `vm->py_str` + `vm->py_str`
+ `vm->py_repr` + `vm->py_repr`

View File

@ -9,18 +9,18 @@ In order to use a C/C++ library in python, you need to write bindings for it.
pkpy uses an universal signature to wrap a function pointer as a python function or method that can be called in python code, i.e `NativeFuncC`. pkpy uses an universal signature to wrap a function pointer as a python function or method that can be called in python code, i.e `NativeFuncC`.
```cpp ```cpp
typedef PyObject* (*NativeFuncC)(VM*, ArgsView); typedef PyVar (*NativeFuncC)(VM*, ArgsView);
``` ```
+ The first argument is the pointer of `VM` instance. + The first argument is the pointer of `VM` instance.
+ The second argument is an array-like object indicates the arguments list. You can use `[]` operator to get the element and call `size()` to get the length of the array. + The second argument is an array-like object indicates the arguments list. You can use `[]` operator to get the element and call `size()` to get the length of the array.
+ The return value is a `PyObject*`, which should not be `nullptr`. If there is no return value, return `vm->None`. + The return value is a `PyVar`, which should not be `nullptr`. If there is no return value, return `vm->None`.
## Bind a function or method ## Bind a function or method
Use `vm->bind` to bind a function or method. Use `vm->bind` to bind a function or method.
+ `PyObject* bind(PyObject*, const char* sig, NativeFuncC)` + `PyVar bind(PyVar, const char* sig, NativeFuncC)`
+ `PyObject* bind(PyObject*, const char* sig, const char* docstring, NativeFuncC)` + `PyVar bind(PyVar, const char* sig, const char* docstring, NativeFuncC)`
```cpp ```cpp
@ -56,7 +56,7 @@ vm->bind(obj, "f() -> int", [x](VM* vm, ArgsView args){
I do not encourage you to capture something in a lambda being bound I do not encourage you to capture something in a lambda being bound
because: because:
1. Captured lambda runs slower and causes "code-bloat". 1. Captured lambda runs slower and causes "code-bloat".
2. Captured values are unsafe, especially for `PyObject*` as they could leak by accident. 2. Captured values are unsafe, especially for `PyVar` as they could leak by accident.
However, there are 3 ways to capture something when you really need to. However, there are 3 ways to capture something when you really need to.
The most safe and elegant way is to subclass `VM` and add a member variable. The most safe and elegant way is to subclass `VM` and add a member variable.
@ -115,9 +115,9 @@ You can create a `test` module and use `vm->register_user_class<>` to bind the c
```cpp ```cpp
int main(){ int main(){
VM* vm = new VM(); VM* vm = new VM();
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
vm->register_user_class<Point>(mod, "Point", vm->register_user_class<Point>(mod, "Point",
[](VM* vm, PyObject* mod, PyObject* type){ [](VM* vm, PyVar mod, PyVar type){
// wrap field x // wrap field x
vm->bind_field(type, "x", &Point::x); vm->bind_field(type, "x", &Point::x);
// wrap field y // wrap field y
@ -145,12 +145,12 @@ int main(){
### Handle gc for container types ### Handle gc for container types
If your custom type stores `PyObject*` in its fields, you need to handle gc for them. If your custom type stores `PyVar` in its fields, you need to handle gc for them.
```cpp ```cpp
struct Container{ struct Container{
PyObject* a; PyVar a;
std::vector<PyObject*> b; std::vector<PyVar> b;
// ... // ...
} }
``` ```
@ -159,8 +159,8 @@ Add a magic method `_gc_mark() const` to your custom type.
```cpp ```cpp
struct Container{ struct Container{
PyObject* a; PyVar a;
std::vector<PyObject*> b; std::vector<PyVar> b;
// ... // ...
void _gc_mark() const{ void _gc_mark() const{
@ -168,7 +168,7 @@ struct Container{
if(a) PK_OBJ_MARK(a); if(a) PK_OBJ_MARK(a);
// mark elements in b // mark elements in b
for(PyObject* obj : b){ for(PyVar obj : b){
if(obj) PK_OBJ_MARK(obj); if(obj) PK_OBJ_MARK(obj);
} }
} }
@ -188,7 +188,7 @@ They do not take universal function pointer as argument.
You need to provide the detailed `Type` object and the corresponding function pointer. You need to provide the detailed `Type` object and the corresponding function pointer.
```cpp ```cpp
PyObject* f_add(VM* vm, PyObject* lhs, PyObject* rhs){ PyVar f_add(VM* vm, PyVar lhs, PyVar rhs){
int a = py_cast<int>(vm, lhs); int a = py_cast<int>(vm, lhs);
int b = py_cast<int>(vm, rhs); int b = py_cast<int>(vm, rhs);
return py_var(vm, a + b); return py_var(vm, a + b);

View File

@ -34,7 +34,7 @@ vm->exec("print('Hello!')");
Evaluate a source string Evaluate a source string
```cpp ```cpp
PyObject* obj = vm->eval("123"); PyVar obj = vm->eval("123");
std::cout << py_cast<int>(vm, obj); // 123 std::cout << py_cast<int>(vm, obj); // 123
``` ```
@ -59,7 +59,7 @@ try{
Create primitive objects Create primitive objects
```cpp ```cpp
PyObject* obj; PyVar obj;
obj = py_var(vm, 1); // create a int obj = py_var(vm, 1); // create a int
obj = py_var(vm, 1.0); // create a float obj = py_var(vm, 1.0); // create a float
obj = py_var(vm, "123"); // create a string obj = py_var(vm, "123"); // create a string
@ -74,7 +74,7 @@ Tuple t(3);
t[0] = py_var(vm, 1); t[0] = py_var(vm, 1);
t[1] = py_var(vm, 1.0); t[1] = py_var(vm, 1.0);
t[2] = py_var(vm, "123"); t[2] = py_var(vm, "123");
PyObject* obj = py_var(vm, std::move(t)); PyVar obj = py_var(vm, std::move(t));
``` ```
Create a list object Create a list object
@ -85,7 +85,7 @@ List t;
t.push_back(py_var(vm, 1)); t.push_back(py_var(vm, 1));
t.push_back(py_var(vm, 1.0)); t.push_back(py_var(vm, 1.0));
t.push_back(py_var(vm, "123")); t.push_back(py_var(vm, "123"));
PyObject* obj = py_var(vm, std::move(t)); PyVar obj = py_var(vm, std::move(t));
``` ```
Create a dict object Create a dict object
@ -95,13 +95,13 @@ Create a dict object
Dict d(vm); Dict d(vm);
d.set(py_var(vm, "x"), py_var(vm, 1)); d.set(py_var(vm, "x"), py_var(vm, 1));
d.set(py_var(vm, "y"), py_var(vm, "123")); d.set(py_var(vm, "y"), py_var(vm, "123"));
PyObject* obj = py_var(vm, std::move(d)); PyVar obj = py_var(vm, std::move(d));
``` ```
Get native types from python objects Get native types from python objects
```cpp ```cpp
PyObject* obj; PyVar obj;
i64 a = py_cast<i64>(vm, obj); i64 a = py_cast<i64>(vm, obj);
f64 b = py_cast<f64>(vm, obj); f64 b = py_cast<f64>(vm, obj);
Str& c = py_cast<Str&>(vm, obj); // reference cast Str& c = py_cast<Str&>(vm, obj); // reference cast
@ -130,11 +130,11 @@ Tuple& c_ = PK_OBJ_GET(Tuple, obj);
Access built-in python types Access built-in python types
```cpp ```cpp
PyObject* int_t = vm->_t(VM::tp_int); PyVar int_t = vm->_t(VM::tp_int);
PyObject* float_t = vm->_t(VM::tp_float); PyVar float_t = vm->_t(VM::tp_float);
PyObject* object_t = vm->_t(VM::tp_object); PyVar object_t = vm->_t(VM::tp_object);
PyObject* tuple_t = vm->_t(VM::tp_tuple); PyVar tuple_t = vm->_t(VM::tp_tuple);
PyObject* list_t = vm->_t(VM::tp_list); PyVar list_t = vm->_t(VM::tp_list);
``` ```
Access user registered types Access user registered types
@ -146,22 +146,22 @@ Type voidp_t = vm->_tp_user<VoidP>();
Check if an object is a python type Check if an object is a python type
```cpp ```cpp
PyObject* obj; PyVar obj;
bool ok = is_type(obj, VM::tp_int); // check if obj is an int bool ok = is_type(obj, VM::tp_int); // check if obj is an int
``` ```
Get the type of a python object Get the type of a python object
```cpp ```cpp
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
PyObject* t = vm->_t(obj); // <class 'int'> PyVar t = vm->_t(obj); // <class 'int'>
Type type = vm->_tp(obj); // VM::tp_int Type type = vm->_tp(obj); // VM::tp_int
``` ```
Convert a type object into a type index Convert a type object into a type index
```cpp ```cpp
PyObject* int_t = vm->_t(VM::tp_int); PyVar int_t = vm->_t(VM::tp_int);
Type t = PK_OBJ_GET(Type, int_t); Type t = PK_OBJ_GET(Type, int_t);
// t == VM::tp_int // t == VM::tp_int
``` ```
@ -171,7 +171,7 @@ Type t = PK_OBJ_GET(Type, int_t);
Check an object supports attribute access Check an object supports attribute access
```cpp ```cpp
PyObject* obj; PyVar obj;
bool ok = !is_tagged(obj) && obj->is_attr_valid(); bool ok = !is_tagged(obj) && obj->is_attr_valid();
``` ```
@ -188,8 +188,8 @@ class MyClass:
Get and set attributes Get and set attributes
```cpp ```cpp
PyObject* obj = vm->exec("MyClass(1, 2)"); PyVar obj = vm->exec("MyClass(1, 2)");
PyObject* x = vm->getattr(obj, "x"); // obj.x PyVar x = vm->getattr(obj, "x"); // obj.x
vm->setattr(obj, "x", py_var(vm, 3)); // obj.x = 3 vm->setattr(obj, "x", py_var(vm, 3)); // obj.x = 3
``` ```
@ -203,16 +203,16 @@ def add(a, b):
Call a function Call a function
```cpp ```cpp
PyObject* f_add = vm->eval("add"); PyVar f_add = vm->eval("add");
PyObject* ret = vm->call(f_add, py_var(vm, 1), py_var(vm, 2)); PyVar ret = vm->call(f_add, py_var(vm, 1), py_var(vm, 2));
std::cout << py_cast<int>(vm, ret); // 3 std::cout << py_cast<int>(vm, ret); // 3
``` ```
Call a method Call a method
```cpp ```cpp
PyObject* obj = vm->exec("MyClass(1, 2)"); PyVar obj = vm->exec("MyClass(1, 2)");
PyObject* ret = vm->call_method(obj, "sum"); PyVar ret = vm->call_method(obj, "sum");
std::cout << CAST(i64, ret); // 3 std::cout << CAST(i64, ret); // 3
``` ```
@ -221,7 +221,7 @@ Cache the name of a function or method to avoid string-based lookup
```cpp ```cpp
// cache the name "add" to avoid string-based lookup // cache the name "add" to avoid string-based lookup
const static StrName m_sum("sum"); const static StrName m_sum("sum");
PyObject* ret = vm->call_method(obj, m_sum); PyVar ret = vm->call_method(obj, m_sum);
``` ```
## Special operations ## Special operations
@ -229,50 +229,50 @@ PyObject* ret = vm->call_method(obj, m_sum);
Compare two python objects Compare two python objects
```cpp ```cpp
PyObject* obj1 = py_var(vm, 1); PyVar obj1 = py_var(vm, 1);
PyObject* obj2 = py_var(vm, 2); PyVar obj2 = py_var(vm, 2);
bool ok = vm->py_eq(obj1, obj2); bool ok = vm->py_eq(obj1, obj2);
``` ```
Convert a python object to string Convert a python object to string
```cpp ```cpp
PyObject* obj = py_var(vm, 123); PyVar obj = py_var(vm, 123);
std::cout << vm->py_str(obj); // 123 std::cout << vm->py_str(obj); // 123
``` ```
Get the string representation of a python object Get the string representation of a python object
```cpp ```cpp
PyObject* obj = py_var(vm, "123"); PyVar obj = py_var(vm, "123");
std::cout << vm->py_repr(obj); // "'123'" std::cout << vm->py_repr(obj); // "'123'"
``` ```
Get the JSON representation of a python object Get the JSON representation of a python object
```cpp ```cpp
PyObject* obj = py_var(vm, 123); PyVar obj = py_var(vm, 123);
std::cout << vm->py_json(obj); // "123" std::cout << vm->py_json(obj); // "123"
``` ```
Get the hash value of a python object Get the hash value of a python object
```cpp ```cpp
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
i64 h = vm->py_hash(obj); // 1 i64 h = vm->py_hash(obj); // 1
``` ```
Get the iterator of a python object Get the iterator of a python object
```cpp ```cpp
PyObject* obj = vm->eval("range(3)"); PyVar obj = vm->eval("range(3)");
PyObject* iter = vm->py_iter(obj); PyVar iter = vm->py_iter(obj);
``` ```
Get the next item of an iterator Get the next item of an iterator
```cpp ```cpp
PyObject* obj = vm->py_next(iter); PyVar obj = vm->py_next(iter);
if(obj == vm->StopIteration){ if(obj == vm->StopIteration){
// end of iteration // end of iteration
}else{ }else{
@ -283,7 +283,7 @@ if(obj == vm->StopIteration){
Convert a python iterable to a list Convert a python iterable to a list
```cpp ```cpp
PyObject* obj = vm->eval("range(3)"); PyVar obj = vm->eval("range(3)");
List list = vm->py_list(obj); List list = vm->py_list(obj);
``` ```
@ -328,6 +328,6 @@ vm->_lazy_modules["test"] = "pi = 3.14";
Create a native module Create a native module
```cpp ```cpp
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
vm->setattr(mod, "pi", py_var(vm, 3.14)); vm->setattr(mod, "pi", py_var(vm, 3.14));
``` ```

View File

@ -62,7 +62,7 @@ You can add other functions to `os` module via normal binding if you need them.
For example, add `os.system`: For example, add `os.system`:
```cpp ```cpp
PyObject* mod = vm->_modules["os"]; PyVar mod = vm->_modules["os"];
vm->bind(mod, "system(cmd: str) -> int", [](VM* vm, ArgsView args){ vm->bind(mod, "system(cmd: str) -> int", [](VM* vm, ArgsView args){
const char* cmd = py_cast<CString>(vm, args[0]); const char* cmd = py_cast<CString>(vm, args[0]);

View File

@ -13,9 +13,9 @@ You can use `obj->attr()` to manipulate the instance dict of an object.
VM* vm = new VM(); VM* vm = new VM();
// get the `builtin` module // get the `builtin` module
PyObject* builtins = vm->builtins; PyVar builtins = vm->builtins;
// get `dict` type // get `dict` type
PyObject* dict = builtins->attr("dict"); PyVar dict = builtins->attr("dict");
// set `pi = 3.14` // set `pi = 3.14`
builtins->attr().set("pi", py_var(vm, 3.14)); builtins->attr().set("pi", py_var(vm, 3.14));
``` ```
@ -25,9 +25,9 @@ For example, the `int` object.
```cpp ```cpp
// create a `int` object // create a `int` object
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
// THIS IS WRONG!! WILL LEAD TO A SEGFAULT!! // THIS IS WRONG!! WILL LEAD TO A SEGFAULT!!
PyObject* add = obj->attr("__add__"); PyVar add = obj->attr("__add__");
``` ```
To determine whether an object has instance dict or not, you can use this snippet. To determine whether an object has instance dict or not, you can use this snippet.
@ -35,7 +35,7 @@ To determine whether an object has instance dict or not, you can use this snippe
```cpp ```cpp
// 1. call `is_tagged` to check the object supports `->` operator // 1. call `is_tagged` to check the object supports `->` operator
// 2. call `is_attr_valid` to check the existence of instance dict // 2. call `is_attr_valid` to check the existence of instance dict
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
bool ok = !is_tagged(obj) && obj->is_attr_valid(); // false bool ok = !is_tagged(obj) && obj->is_attr_valid(); // false
``` ```
@ -45,7 +45,7 @@ As you can see, direct access does not take care of derived attributes or method
In most cases, what you need is `getattr` and `setattr`. In most cases, what you need is `getattr` and `setattr`.
These two methods handle all possible cases. These two methods handle all possible cases.
#### `PyObject* getattr(PyObject* obj, StrName name, bool throw_err=true)` #### `PyVar getattr(PyVar obj, StrName name, bool throw_err=true)`
This method is equivalent to `getattr` in python. This method is equivalent to `getattr` in python.
If the attribute is not found, it will return `nullptr` If the attribute is not found, it will return `nullptr`
@ -53,20 +53,20 @@ or throw an `AttributeError` depending on the value of `throw_err`.
```cpp ```cpp
// create a `int` object // create a `int` object
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
// get its `__add__` method, which is a `bound_method` object // get its `__add__` method, which is a `bound_method` object
PyObject* add = vm->getattr(obj, "__add__"); PyVar add = vm->getattr(obj, "__add__");
// call it (equivalent to `1 + 2`) // call it (equivalent to `1 + 2`)
PyObject* ret = vm->call(add, py_var(vm, 2);); PyVar ret = vm->call(add, py_var(vm, 2););
// get the result // get the result
int result = py_cast<int>(vm, ret); int result = py_cast<int>(vm, ret);
std::cout << result << std::endl; // 3 std::cout << result << std::endl; // 3
``` ```
#### `void setattr(PyObject*, StrName, PyObject*)` #### `void setattr(PyVar, StrName, PyVar)`
This method is equivalent to `setattr` in python. This method is equivalent to `setattr` in python.
It raises `TypeError` if the object does not support attribute assignment. It raises `TypeError` if the object does not support attribute assignment.

View File

@ -10,8 +10,8 @@ You can use `call` to invoke any python callable object,
including functions, methods, classes, etc. including functions, methods, classes, etc.
For methods, `call_method` can be used. For methods, `call_method` can be used.
+ `PyObject* call(PyObject* obj, ...)` + `PyVar call(PyVar obj, ...)`
+ `PyObject* call_method(PyObject* obj, StrName name, ...)` + `PyVar call_method(PyVar obj, StrName name, ...)`
### Example ### Example
@ -27,22 +27,22 @@ print(obj["a"]) # print the value
First, create an empty dict object, First, create an empty dict object,
```cpp ```cpp
PyObject* tp = vm->builtins->attr("dict"); PyVar tp = vm->builtins->attr("dict");
PyObject* obj = vm->call(tp); // this is a `dict` PyVar obj = vm->call(tp); // this is a `dict`
``` ```
And set a key-value pair, And set a key-value pair,
```cpp ```cpp
PyObject* _0 = py_var(vm, "a"); PyVar _0 = py_var(vm, "a");
PyObject* _1 = py_var(vm, 5); PyVar _1 = py_var(vm, 5);
vm->call_method(obj, "__setitem__", _0, _1); vm->call_method(obj, "__setitem__", _0, _1);
``` ```
And get the value, And get the value,
```cpp ```cpp
PyObject* ret = vm->call_method(obj, "__getitem__", _0); PyVar ret = vm->call_method(obj, "__getitem__", _0);
std::cout << py_cast<int>(vm, i64); std::cout << py_cast<int>(vm, i64);
``` ```
@ -55,17 +55,17 @@ you should use `vm->vectorcall`. This is a low-level, stack-based API.
4. Call `vm->vectorcall` with the number of arguments. 4. Call `vm->vectorcall` with the number of arguments.
```cpp ```cpp
PyObject* f_sum = vm->builtins->attr("sum"); PyVar f_sum = vm->builtins->attr("sum");
List args(N); // a list of N arguments List args(N); // a list of N arguments
vm->s_data.push_back(f_sum); vm->s_data.push_back(f_sum);
vm->s_data.push_back(PY_NULL); // self vm->s_data.push_back(PY_NULL); // self
for(PyObject* arg : args) { for(PyVar arg : args) {
vm->s_data.push_back(arg); vm->s_data.push_back(arg);
} }
PyObject* ret = vm->vectorcall(args.size()); PyVar ret = vm->vectorcall(args.size());
``` ```

View File

@ -8,20 +8,20 @@ order: 93
Once you have a `VM` instance, you can execute python code by calling `exec` method. Once you have a `VM` instance, you can execute python code by calling `exec` method.
#### `PyObject* exec(Str source, Str filename, CompileMode mode, PyObject* _module=nullptr)` #### `PyVar exec(Str source, Str filename, CompileMode mode, PyVar _module=nullptr)`
+ `source`, the python source code to be executed + `source`, the python source code to be executed
+ `filename`, the filename of the source code. This is used for error reporting + `filename`, the filename of the source code. This is used for error reporting
+ `mode`, the compile mode. See below for details + `mode`, the compile mode. See below for details
+ `module`, the module where the code will be executed. If `nullptr`, the code will be executed in the `__main__` module + `module`, the module where the code will be executed. If `nullptr`, the code will be executed in the `__main__` module
`exec` handles possible exceptions and returns a `PyObject*`. `exec` handles possible exceptions and returns a `PyVar`.
If the execution is not successful, e.g. a syntax error or a runtime exception, If the execution is not successful, e.g. a syntax error or a runtime exception,
the return value will be `nullptr`. the return value will be `nullptr`.
There are also overloaded versions of `exec` and `eval`, which is useful for simple execution: There are also overloaded versions of `exec` and `eval`, which is useful for simple execution:
+ `PyObject* exec(Str source)` + `PyVar exec(Str source)`
+ `PyObject* eval(Str source)` + `PyVar eval(Str source)`
### Compile mode ### Compile mode
@ -39,7 +39,7 @@ In some cases, you may want to execute python code in a more fine-grained way.
These two methods are provided for this purpose: These two methods are provided for this purpose:
+ `CodeObject_ compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope)` + `CodeObject_ compile(Str source, Str filename, CompileMode mode, bool unknown_global_scope)`
+ `PyObject* _exec(CodeObject_ co, PyObject* _module)` + `PyVar _exec(CodeObject_ co, PyVar _module)`
1. `compile` compiles the source code into a `CodeObject_` instance. Leave `unknown_global_scope` to `false` if you don't know what it means. 1. `compile` compiles the source code into a `CodeObject_` instance. Leave `unknown_global_scope` to `false` if you don't know what it means.
2. `_exec` executes the `CodeObject_` instance. 2. `_exec` executes the `CodeObject_` instance.

View File

@ -93,7 +93,7 @@ int main(){
vm->exec("a = [1, 2, 3]"); vm->exec("a = [1, 2, 3]");
// Eval the sum of the list // Eval the sum of the list
PyObject* result = vm->eval("sum(a)"); PyVar result = vm->eval("sum(a)");
std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6 std::cout << "Sum of the list: "<< py_cast<int>(vm, result) << std::endl; // 6
// Bindings // Bindings
@ -105,7 +105,7 @@ int main(){
}); });
// Call the function // Call the function
PyObject* f_add = vm->_main->attr("add"); PyVar f_add = vm->_main->attr("add");
result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7)); result = vm->call(f_add, py_var(vm, 3), py_var(vm, 7));
std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10 std::cout << "Sum of 2 variables: "<< py_cast<int>(vm, result) << std::endl; // 10

View File

@ -4,20 +4,20 @@ label: 'Interop with PyObject'
order: 90 order: 90
--- ---
In pkpy, any python object is represented by a `PyObject*`. In pkpy, any python object is represented by a `PyVar`.
### Create `PyObject*` from C type ### Create `PyVar` from C type
A set of overloaded function `PyObject* py_var(VM* vm, ...)` were implemented to A set of overloaded function `PyVar py_var(VM* vm, ...)` were implemented to
create a `PyObject*` from a supported C type. create a `PyVar` from a supported C type.
Assume we have a `VM* vm` instance. Assume we have a `VM* vm` instance.
You can create a python `int` object from a C `i64` type: You can create a python `int` object from a C `i64` type:
```cpp ```cpp
i64 i = 2; i64 i = 2;
PyObject* obj = py_var(vm, i); PyVar obj = py_var(vm, i);
``` ```
Each python type has a corresponding C type, for example, `int` in python is `i64` in C. Each python type has a corresponding C type, for example, `int` in python is `i64` in C.
@ -26,7 +26,7 @@ For strings, we have defined
a set of overloaded version including `const char*`, `std::string`, `std::string_view`, `Str`, etc. a set of overloaded version including `const char*`, `std::string`, `std::string_view`, `Str`, etc.
```cpp ```cpp
PyObject* obj = py_var(vm, "abc"); // create a python str object PyVar obj = py_var(vm, "abc"); // create a python str object
``` ```
A more complex example is to create a python `list`. A more complex example is to create a python `list`.
@ -44,15 +44,15 @@ obj = py_var(vm, std::move(list)); // create a python list object
Please note that `std::move` is used here to avoid unnecessary copy. Please note that `std::move` is used here to avoid unnecessary copy.
Most types have both a rvalue and a lvalue version of `py_var` function. Most types have both a rvalue and a lvalue version of `py_var` function.
### Access internal C type of `PyObject*` ### Access internal C type of `PyVar`
A set of template function `T py_cast<T>(VM* vm, PyObject* obj)` were implemented. A set of template function `T py_cast<T>(VM* vm, PyVar obj)` were implemented.
```cpp ```cpp
i64 i = 2; i64 i = 2;
PyObject* obj = py_var(vm, i); PyVar obj = py_var(vm, i);
// cast a PyObject* to C i64 // cast a PyVar to C i64
i64 j = py_cast<i64>(vm, obj); i64 j = py_cast<i64>(vm, obj);
``` ```
@ -63,21 +63,21 @@ However, this type check has a cost. If you are sure about the type of `obj`,
you can use the underscore version `_py_cast` to skip the type check. you can use the underscore version `_py_cast` to skip the type check.
```cpp ```cpp
// cast a PyObject* to C i64 (unsafe but faster) // cast a PyVar to C i64 (unsafe but faster)
i64 j = _py_cast<i64>(vm, obj); i64 j = _py_cast<i64>(vm, obj);
``` ```
For complex objects like `list`, we can use reference cast to avoid unnecessary copy. For complex objects like `list`, we can use reference cast to avoid unnecessary copy.
```cpp ```cpp
PyObject* obj = py_var(vm, List()); PyVar obj = py_var(vm, List());
// reference cast (no copy) // reference cast (no copy)
List& list = py_cast<List&>(vm, obj); List& list = py_cast<List&>(vm, obj);
``` ```
### Check type of `PyObject*` ### Check type of `PyVar`
Each `PyObject*` has a `Type type` field to indicate its type. Each `PyVar` has a `Type type` field to indicate its type.
`Type` is just an integer which is the global index in `vm->_all_types`. `Type` is just an integer which is the global index in `vm->_all_types`.
`VM` class has a set of predefined `Type` constants for quick access. `VM` class has a set of predefined `Type` constants for quick access.
@ -88,16 +88,16 @@ Types are divided into **tagged type** and **non-tagged type**.
+ small `int` objects are tagged. + small `int` objects are tagged.
+ Other types are non-tagged type. + Other types are non-tagged type.
To determine whether a `PyObject*` is of a specific type, To determine whether a `PyVar` is of a specific type,
you can use the following functions: you can use the following functions:
+ `bool is_type(PyObject* obj, Type type)` + `bool is_type(PyVar obj, Type type)`
+ `bool is_int(PyObject* obj)` + `bool is_int(PyVar obj)`
+ `bool is_float(PyObject* obj)` + `bool is_float(PyVar obj)`
+ `bool is_tagged(PyObject* obj)` + `bool is_tagged(PyVar obj)`
```cpp ```cpp
PyObject* obj = py_var(vm, 1); PyVar obj = py_var(vm, 1);
bool ok = is_type(obj, vm->tp_int); // true bool ok = is_type(obj, vm->tp_int); // true
ok = is_int(obj); // true ok = is_int(obj); // true
@ -110,8 +110,8 @@ ok = is_float(obj); // false
Simply put, `is_type` is the most general function and can check any types. Simply put, `is_type` is the most general function and can check any types.
Other variants are designed for specific types and are faster. Other variants are designed for specific types and are faster.
You can also use `check_` prefix functions assert the type of a `PyObject*`, You can also use `check_` prefix functions assert the type of a `PyVar`,
which will throw `TypeError` on failure. which will throw `TypeError` on failure.
+ `void check_type(PyObject* obj, Type type)` + `void check_type(PyVar obj, Type type)`

View File

@ -18,7 +18,7 @@ For example, you create a temporary object on the stack and then call `vm->py_ne
```cpp ```cpp
void some_func(VM* vm){ void some_func(VM* vm){
PyObject* obj = VAR(List(5)); PyVar obj = VAR(List(5));
// unsafe // unsafe
PyObject iter = vm->py_iter(obj); PyObject iter = vm->py_iter(obj);
PyObject next = vm->py_next(iter); PyObject next = vm->py_next(iter);
@ -32,7 +32,7 @@ The scope lock prevents this from happening.
```cpp ```cpp
void some_func(VM* vm){ void some_func(VM* vm){
PyObject* obj = VAR(List(5)); PyVar obj = VAR(List(5));
// safe // safe
auto _lock = vm->heap.gc_scope_lock(); auto _lock = vm->heap.gc_scope_lock();
PyObject iter = vm->py_iter(obj); PyObject iter = vm->py_iter(obj);

View File

@ -30,7 +30,7 @@ Native modules are always compiled and executed when the VM is created.
To creata a native module, use `vm->new_module(Str name)`. To creata a native module, use `vm->new_module(Str name)`.
```cpp ```cpp
PyObject* mod = vm->new_module("test"); PyVar mod = vm->new_module("test");
mod->attr().set("pi", py_var(vm, 3.14)); mod->attr().set("pi", py_var(vm, 3.14));
vm->bind(mod, "add(a: int, b: int)", vm->bind(mod, "add(a: int, b: int)",