├── .gitignore ├── Makefile ├── builder.py ├── my_module.zig ├── readme.md ├── setup.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | .venv*/ 2 | .idea/ 3 | .pre-commit-config.yaml 4 | .python-version 5 | zig-cache/ 6 | __pycache__/ 7 | hoangdz.egg-info/ 8 | build/ 9 | dist/ 10 | hoangdz.cpython-310-x86_64-linux-gnu.so 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | cover/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | db.sqlite3 72 | db.sqlite3-journal 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | .pybuilder/ 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | # For a library or package, you might want to ignore these files since the code is 97 | # intended to run in multiple environments; otherwise, check them in: 98 | # .python-version 99 | 100 | # pipenv 101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 104 | # install all needed dependencies. 105 | #Pipfile.lock 106 | 107 | # poetry 108 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 109 | # This is especially recommended for binary packages to ensure reproducibility, and is more 110 | # commonly ignored for libraries. 111 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 112 | #poetry.lock 113 | 114 | # pdm 115 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 116 | #pdm.lock 117 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 118 | # in version control. 119 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 120 | .pdm.toml 121 | .pdm-python 122 | .pdm-build/ 123 | 124 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 125 | __pypackages__/ 126 | 127 | # Celery stuff 128 | celerybeat-schedule 129 | celerybeat.pid 130 | 131 | # SageMath parsed files 132 | *.sage.py 133 | 134 | # Environments 135 | .env 136 | .venv 137 | env/ 138 | venv/ 139 | ENV/ 140 | env.bak/ 141 | venv.bak/ 142 | 143 | # Spyder project settings 144 | .spyderproject 145 | .spyproject 146 | 147 | # Rope project settings 148 | .ropeproject 149 | 150 | # mkdocs documentation 151 | /site 152 | 153 | # mypy 154 | .mypy_cache/ 155 | .dmypy.json 156 | dmypy.json 157 | 158 | # Pyre type checker 159 | .pyre/ 160 | 161 | # pytype static type analyzer 162 | .pytype/ 163 | 164 | # Cython debug symbols 165 | cython_debug/ 166 | 167 | # PyCharm 168 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 169 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 170 | # and can be added to the global gitignore or merged into this file. For a more nuclear 171 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 172 | #.idea/ 173 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | clear 3 | pip install . && python3 test.py 4 | 5 | clean: 6 | rm -rf build dist hoangdz.egg-info hoangdz.cpython-311-x86_64-linux-gnu.so __pycache__ 7 | -------------------------------------------------------------------------------- /builder.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools.command.build_ext import build_ext 3 | 4 | 5 | class ZigBuilder(build_ext): 6 | def build_extension(self, ext): 7 | assert len(ext.sources) == 1 8 | 9 | if not os.path.exists(self.build_lib): 10 | os.makedirs(self.build_lib) 11 | 12 | # mode = "Debug" if self.debug else "ReleaseFast" 13 | mode = "ReleaseFast" 14 | 15 | 16 | self.spawn( 17 | [ 18 | "zig",# your zig compiler binary path 19 | "build-lib", 20 | "-O", 21 | mode, 22 | "-lc", 23 | f"-femit-bin={self.get_ext_fullpath(ext.name)}", 24 | "-dynamic", 25 | *[f"-I{d}" for d in self.include_dirs], 26 | ext.sources[0], 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /my_module.zig: -------------------------------------------------------------------------------- 1 | const py = @cImport({ 2 | @cDefine("PY_SSIZE_T_CLEAN", {}); 3 | // @cInclude("python3.10/Python.h"); 4 | @cInclude("Python.h"); 5 | }); 6 | const std = @import("std"); 7 | const print = std.debug.print; 8 | 9 | const PyObject = py.PyObject; 10 | const PyMethodDef = py.PyMethodDef; 11 | const PyModuleDef = py.PyModuleDef; 12 | const PyModuleDef_Base = py.PyModuleDef_Base; 13 | const Py_BuildValue = py.Py_BuildValue; // create Python None value 14 | const PyModule_Create = py.PyModule_Create; 15 | const METH_NOARGS = py.METH_NOARGS; 16 | const PyArg_ParseTuple = py.PyArg_ParseTuple; 17 | const PyLong_FromLong = py.PyLong_FromLong; 18 | 19 | fn zig_add(a: i64, b: i64) i64 { 20 | return a + b; 21 | } 22 | 23 | fn add(self: [*c]PyObject, args: [*c]PyObject) callconv(.C) [*]PyObject { 24 | _ = self; 25 | var a: c_long = undefined; 26 | var b: c_long = undefined; 27 | if (!(py._PyArg_ParseTuple_SizeT(args, "ll", &a, &b) != 0)) return Py_BuildValue(""); 28 | return py.PyLong_FromLong(zig_add(a, b)); 29 | } 30 | 31 | // Function to add two numbers 32 | export fn add1(self: ?*py.PyObject, args: ?*py.PyObject) ?*py.PyObject { 33 | _ = self; 34 | var a: c_int = 0; 35 | var b: c_int = 0; 36 | 37 | // Parse the Python arguments (two integers) 38 | if (py.PyArg_ParseTuple(args, "ii", &a, &b) == 0) { 39 | return null; 40 | } 41 | 42 | // Return the result as a Python object 43 | return py.PyLong_FromLong(zig_add(a, b)); 44 | } 45 | 46 | export fn sum_list(self: ?*PyObject, args: ?*PyObject) ?*PyObject { 47 | _ = self; 48 | var py_list: *PyObject = undefined; 49 | 50 | // Parse the argument 51 | if (PyArg_ParseTuple(args, "O", &py_list) == 0) { 52 | return null; 53 | } 54 | 55 | // Check if the object is a list 56 | if (py.PyList_Check(py_list) == 0) { 57 | py.PyErr_SetString(py.PyExc_TypeError, "Input must be a list"); 58 | return null; 59 | } 60 | 61 | // Get the length of the list 62 | const length: py.Py_ssize_t = py.PyList_Size(py_list); 63 | 64 | var s: c_long = 0; 65 | var i: py.Py_ssize_t = 0; 66 | // Iterate through the list 67 | while (i < length) : (i += 1) { 68 | const item: *PyObject = py.PyList_GetItem(py_list, i); 69 | 70 | // Check if the item is an integer 71 | if (py.PyLong_Check(item) == 1) { 72 | s += py.PyLong_AsLong(item); 73 | } 74 | } 75 | 76 | // Return the result as a Python object 77 | return PyLong_FromLong(s); 78 | } 79 | 80 | export fn mul(self: ?*PyObject, args: ?*PyObject) ?*PyObject { 81 | _ = self; 82 | var a: c_long = undefined; 83 | var b: c_long = undefined; 84 | if (PyArg_ParseTuple(args, "ll", &a, &b) == 0) return Py_BuildValue(""); 85 | return PyLong_FromLong((a * b)); 86 | } 87 | 88 | export fn hello(self: ?*PyObject, args: ?*PyObject) ?*PyObject { 89 | _ = self; 90 | _ = args; 91 | print("Welcome to ziglang!!!\n", .{}); 92 | 93 | return Py_BuildValue(""); 94 | } 95 | 96 | export fn printSt(self: ?*PyObject, args: ?*PyObject) ?*PyObject { 97 | _ = self; 98 | var input: [*:0]u8 = undefined; 99 | if (PyArg_ParseTuple(args, "s", &input) == 0) return Py_BuildValue(""); 100 | print("You entered: {s}\n", .{input}); 101 | return Py_BuildValue(""); 102 | } 103 | 104 | export fn returnArrayWithInput(self: ?*PyObject, args: ?*PyObject) ?*PyObject { 105 | _ = self; 106 | 107 | var N: u32 = undefined; 108 | if (!(py._PyArg_ParseTuple_SizeT(args, "l", &N) != 0)) return Py_BuildValue(""); 109 | const list: ?*PyObject = py.PyList_New(N); 110 | 111 | var i: u32 = 0; 112 | while (i < N) : (i += 1) { 113 | const python_int: ?*PyObject = Py_BuildValue("i", i); 114 | _ = py.PyList_SetItem(list, i, python_int); 115 | } 116 | return list; 117 | } 118 | 119 | var Methods = [_]PyMethodDef{ 120 | PyMethodDef{ 121 | .ml_name = "sum_list", 122 | .ml_meth = sum_list, 123 | .ml_flags = py.METH_VARARGS, 124 | .ml_doc = 125 | \\sum(data) 126 | \\-- 127 | \\ 128 | \\Sum a list of integers 129 | , 130 | }, 131 | PyMethodDef{ 132 | .ml_name = "add", 133 | .ml_meth = add, 134 | .ml_flags = py.METH_VARARGS, 135 | // .ml_doc = "add(a,b)\n--\n\nGreat example function", 136 | .ml_doc = 137 | \\add(a,b) 138 | \\-- 139 | \\ 140 | \\Calculation sum of 2 numbers 141 | , 142 | }, 143 | PyMethodDef{ 144 | .ml_name = "add1", 145 | .ml_meth = add1, 146 | .ml_flags = py.METH_VARARGS, 147 | .ml_doc = "Add two numbers", 148 | }, 149 | PyMethodDef{ 150 | .ml_name = "mul", 151 | .ml_meth = mul, 152 | .ml_flags = py.METH_VARARGS, 153 | .ml_doc = null, 154 | }, 155 | PyMethodDef{ 156 | .ml_name = "hello", 157 | .ml_meth = hello, 158 | .ml_flags = py.METH_NOARGS, 159 | .ml_doc = null, 160 | }, 161 | PyMethodDef{ 162 | .ml_name = "printSt", 163 | .ml_meth = printSt, 164 | .ml_flags = py.METH_VARARGS, 165 | .ml_doc = null, 166 | }, 167 | PyMethodDef{ 168 | .ml_name = "returnArrayWithInput", 169 | .ml_meth = returnArrayWithInput, 170 | .ml_flags = py.METH_VARARGS, 171 | .ml_doc = null, 172 | }, 173 | PyMethodDef{ 174 | .ml_name = null, 175 | .ml_meth = null, 176 | .ml_flags = 0, 177 | .ml_doc = null, 178 | }, 179 | }; 180 | 181 | var module = PyModuleDef{ 182 | .m_base = PyModuleDef_Base{ 183 | .ob_base = PyObject{ 184 | .ob_refcnt = 1, 185 | .ob_type = null, 186 | }, 187 | .m_init = null, 188 | .m_index = 0, 189 | .m_copy = null, 190 | }, 191 | .m_name = "hoangdz", 192 | .m_doc = "experiment create python module in Zig", 193 | .m_size = -1, 194 | .m_methods = &Methods, 195 | .m_slots = null, 196 | .m_traverse = null, 197 | .m_clear = null, 198 | .m_free = null, 199 | }; 200 | 201 | pub export fn PyInit_hoangdz() [*]PyObject { 202 | return PyModule_Create(&module); 203 | } 204 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Steps 2 | 1. Export module: `pip install .` 3 | 2. Test module: `python3 test.py` 4 | 5 | ## Result after running test.py 6 | ![image](https://github.com/user-attachments/assets/39d65924-17f7-41f6-b996-1685b178d2ec) 7 | 8 | 9 | ### Main ideas from [here](https://pypi.org/project/setuptools-zig/) and [here](https://github.com/adamserafini/zaml) 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | from builder import ZigBuilder 3 | 4 | hoangdz = Extension("hoangdz", sources=["./my_module.zig"]) 5 | 6 | setup( 7 | name="hoangdz", 8 | version="0.0.1", 9 | description="a experiment create Python module in Zig", 10 | ext_modules=[hoangdz], 11 | cmdclass={"build_ext": ZigBuilder}, 12 | ) 13 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import numpy as np 4 | 5 | import hoangdz 6 | 7 | 8 | def sum_l(data): 9 | s = 0 10 | for e in data: 11 | s += e 12 | return s 13 | 14 | 15 | a = int(input("enter a: ")) 16 | b = int(input("enter b: ")) 17 | print("sum: ", hoangdz.add(a, b)) 18 | print("sum: ", hoangdz.add1(a, b)) 19 | print("multiple: ", hoangdz.mul(a, b)) 20 | print("type sum: ", type(hoangdz.add(a, b))) 21 | print(hoangdz.hello()) 22 | hoangdz.printSt(input("enter something: ")) 23 | print("Array: ", hoangdz.returnArrayWithInput(100)) 24 | 25 | data = [i for i in range(1_000_000)] 26 | 27 | start = time.time() 28 | print(f"Sum: {hoangdz.sum_list(data)}") 29 | end = time.time() 30 | print(f"hoangdz.sum_list run in: {end - start}") 31 | 32 | start = time.time() 33 | print(f"Sum: {sum_l(data)}") 34 | end = time.time() 35 | print(f"sum_l run in: {end - start}") 36 | 37 | start = time.time() 38 | print(f"Sum: {np.sum(data)}") 39 | end = time.time() 40 | print(f"np.sum run in: {end - start}") 41 | --------------------------------------------------------------------------------