Skip to content

Commit 114e1b9

Browse files
YesDrXWei Xiang
and
Wei Xiang
authored
add init, del, repr, doc for pyType (#308)
* add init, del, repr, doc for pyType * add a export type test * minor fix on the test case * use 2 spaces rather than 4 spaces * use 2 spaces rather than 4 spaces * remove __del__ --------- Co-authored-by: Wei Xiang <wxiang@wxtrades.wxtrades.com>
1 parent 0645af5 commit 114e1b9

File tree

10 files changed

+359
-32
lines changed

10 files changed

+359
-32
lines changed

.github/workflows/test.yml

+13-4
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ jobs:
1010
matrix:
1111
os: [ubuntu-latest, windows-latest, macos-latest]
1212
nim-channel: [stable, devel]
13-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
13+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
14+
exclude:
15+
- os: ubuntu-latest
16+
python-version: "3.7"
17+
- os: macos-latest
18+
python-version: "3.7"
1419

1520
name: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.nim-channel }}
1621
runs-on: ${{ matrix.os }}
1722
steps:
18-
- uses: actions/checkout@v2
23+
- uses: actions/checkout@v4
1924

20-
- uses: actions/setup-python@v2
25+
- name: Debug Environment
26+
run: |
27+
echo "OS: $(uname -a)"
28+
29+
- uses: actions/setup-python@v5
2130
with:
2231
python-version: ${{ matrix.python-version }}
23-
32+
2433
- name: Setup nim
2534
uses: jiro4989/setup-nim-action@v1
2635
with:

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ tests/tbuiltinpyfromnim
55
*.pyc
66
*.pyd
77
*.so
8+
nimble.develop
9+
nimble.paths

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ Nimpy also exposes lower level [Buffer protocol](https://docs.python.org/3/c-api
8888
see [raw_buffers.nim](https://github.com/yglukhov/nimpy/blob/master/nimpy/raw_buffers.nim).
8989
[tpyfromnim.nim](https://github.com/yglukhov/nimpy/blob/master/tests/numpytest.nim)
9090
contains a very basic test for this.
91+
92+
[Examples to use raw_buffers with numpy](./docs/numpy.md)
9193
</details>
9294

9395
<details>
@@ -124,11 +126,13 @@ contains a very basic test for this.
124126

125127
</details>
126128

127-
## Exporting Nim types as Python classes
129+
## [Exporting Nim types as Python classes](./docs/export_python_type.md)
128130
Warning! This is experimental.
129131
* An exported type should be a ref object and inherit `PyNimObjectExperimental` directly or indirectly.
130132
* The type will only be exported if at least one exported "method" is defined.
131133
* A proc will be exported as python type method *only* if it's first argument is of the corresponding type and is called `self`. If the first argument is not called `self`, the proc will exported as a global module function.
134+
* If you define functions that looks like initTestType, destroyTestType, `$`, they can be exported as `__init__` and `__repr__` if the requirements are met.
135+
132136
```nim
133137
# mymodule.nim
134138
type TestType = ref object of PyNimObjectExperimental

config.nims

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# begin Nimble config (version 2)
2+
when withDir(thisDir(), system.fileExists("nimble.paths")):
3+
include "nimble.paths"
4+
# end Nimble config

docs/export_python_type.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
## Exporting Nim types as Python classes
2+
3+
Warning! This is experimental.
4+
* An exported type should be a ref object and inherits `PyNimObjectExperimental` directly or indirectly.
5+
* The type will only be exported if at least one exported "method" is defined.
6+
* A proc will be exported as python type method *only* if it's first argument is of the corresponding type and is called `self`. If the first argument is not called `self`, the proc will exported as a global module function.
7+
* If you define functions that looks like initTestType or `$`, they will be exported as `__init__` and `__repr__` if the requirements are met.
8+
9+
#### Simple Example
10+
```nim
11+
# mymodule.nim
12+
type TestType = ref object of PyNimObjectExperimental
13+
myField: string
14+
15+
proc setMyField(self: TestType, value: string) {.exportpy.} =
16+
self.myField = value
17+
18+
proc getMyField(self: TestType): string {.exportpy.} =
19+
self.myField
20+
```
21+
22+
``` py
23+
# test.py
24+
import mymodule
25+
tt = mymodule.TestType()
26+
tt.setMyField("Hello")
27+
assert(tt.getMyField() == "Hello")
28+
```
29+
30+
#### `__init__`, and `__repr__`
31+
* [example](../tests/export_pytype.nim)
32+
```nim
33+
# simple.nim
34+
## compile as simple.so
35+
36+
import nimpy
37+
import strformat
38+
39+
pyExportModule("simple") # only needed if your filename is not simple.nim
40+
41+
type
42+
SimpleObj* = ref object of PyNimObjectExperimental
43+
a* : int
44+
45+
## if
46+
## 1) the function name is like `init##TypeName`
47+
## 2) there is at least one argument
48+
## 3) the first argument name is "self"
49+
## 4) the first argument type is `##TypeName`
50+
## 5) there is no return type
51+
## we export this function as a python object method __init__ (tp_init in PyTypeObject)
52+
proc initSimpleObj*(self : SimpleObj, a : int = 1) {.exportpy} =
53+
echo "Calling initSimpleObj for SimpleObj"
54+
self.a = a
55+
56+
## if
57+
## 1) the function name is like `$`
58+
## 2) there is only one argument
59+
## 3) the first argument name is "self"
60+
## 4) the first argument type is `##TypeName`
61+
## 5) the return type is `string`
62+
## we export this function as a python object method __repr__ (tp_repr in PyTypeObject)
63+
proc `$`*(self : SimpleObj): string {.exportpy.} =
64+
&"SimpleObj : a={self.a}"
65+
66+
67+
## Change doc string
68+
setModuleDocString("This is a test module")
69+
setDocStringForType(SimpleObj, "This is a test type")
70+
```
71+
72+
* Compile as `simple.so`
73+
```bash
74+
nim c --app:lib -o:./simple.so ./simple.nim
75+
```
76+
77+
* Use the exported python type in python
78+
```python
79+
import simple
80+
print(simple.__doc__)
81+
print(simple.SimpleObj.__doc__)
82+
obj = simple.SimpleObj(a = 2)
83+
print(obj)
84+
```

docs/numpy.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
## Use nimpy to access numpy arrays from python
2+
3+
* Get buffer from numpy array (PyObject)
4+
- Note
5+
- numpy will export buffer when the underlying memory block is C-Contiguous
6+
- if the underlying data is not C-Contiguous, there will be a PythonException thrown by numpy
7+
```nim
8+
proc asNimArray*[T](arr : PyObject, mode : int = PyBUF_READ) : ptr UncheckedArray[T] =
9+
var
10+
buf : RawPyBuffer
11+
getBuffer(arr, buf, mode.cint)
12+
```
13+
14+
* Shape and Strides
15+
```nim
16+
type
17+
NimNumpyArray*[T] = object
18+
originalPtr* : PyObject
19+
buf* : ptr UncheckedArray[T]
20+
shape* : seq[int]
21+
strides* : seq[int]
22+
c_contiguous* : bool
23+
f_contiguous* : bool
24+
25+
proc asNimNumpyArray*[T](arr : PyObject, mode : int = PyBUF_READ) : NimNumpyArray[T] =
26+
#[
27+
Function to translate numpy array into an object in Nim to represent the data for convinience.
28+
]#
29+
result.originalPtr = arr
30+
result.buf = asNimArray[T](arr, mode)
31+
result.shape = getAttr(arr, "shape").to(seq[int])
32+
result.strides = getAttr(arr, "strides").to(seq[int])
33+
result.c_contiguous = arr.flags["C_CONTIGUOUS"].to(bool)
34+
result.f_contiguous = arr.flags["F_CONTIGUOUS"].to(bool)
35+
36+
proc accessNumpyMatrix*[T](matrix : NimNumpyArray[T], row, col : int): T =
37+
doAssert matrix.shape == 2 and matrix.strides == 2
38+
return matrix.buf[
39+
row * matrix.strides[0] + col * matrix.strides[1]
40+
]
41+
```
42+
43+
* ArrayMancer Tensor
44+
- Given the exposed buffer, you can use cpuStorageFromBuffer from ArrayMancer Tensor wihout making a copy

0 commit comments

Comments
 (0)