https://github.com/das-dias/pythonclibrarytutorial
A simple Python C library tutorial / template project.
https://github.com/das-dias/pythonclibrarytutorial
c-python-extensions
Last synced: 10 months ago
JSON representation
A simple Python C library tutorial / template project.
- Host: GitHub
- URL: https://github.com/das-dias/pythonclibrarytutorial
- Owner: das-dias
- Created: 2024-02-04T12:06:53.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-02-05T13:36:21.000Z (almost 2 years ago)
- Last Synced: 2025-02-15T01:35:04.736Z (12 months ago)
- Topics: c-python-extensions
- Language: Python
- Homepage:
- Size: 6.18 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# PythonCLibraryTutorial
A tutorial on how to create a Python C library using the Python C API,
and how to use it in a Python program.
## TOC:
- [PythonCLibraryTutorial](#pythonclibrarytutorial)
- [TOC:](#toc)
- [Introduction](#introduction)
- [Repository Structure](#repository-structure)
- [API Flow](#api-flow)
- [Makefile](#makefile)
- [Building the C Library](#building-the-c-library)
- [Running the Python Program](#running-the-python-program)
## Introduction
This tutorial is based on the [RealPython.com](https://realpython.com/build-python-c-extension-module/) tutorial for packaging a C Library in a Python module.
Further extensions to the code were performed by following the stuff written at [the official Python docs](https://docs.python.org/3/extending/extending.html#building-arbitrary-values).
This tutorial extends the previous by:
1. using a Python virtual environment together with a
[```Makefile```](./lib/Makefile) to tell the ```clang``` compiler where the Python header files are located.
2. defining a C structure to be wrapped by a Python class object.
## Repository Structure
The repository is structured as follows:
```
PythonCLibraryTutorial/
│
├── lib/
│ ├── init.c # defines the cpyfputs C Python Library extension module
│ ├── Makefile
│ ├── src/
│ │ └── pyfputs.c
│ └── include/
│ └── pyfputs.h
│
├── pyfputs/
│ ├── __init__.py
│ ├── __main__.py
│ └── example_class.py
│
├── setup.py
└── README.md
```
## API Flow
A call to the ```fputs``` C library is made through the ```lib/init.c::PyInit_cpyfputs``` function. This function is called by the Python interpreter when the Python module is imported, creating the C library module linkage to the Python interpreter. The ```PyInit_cpyfputs``` function is declared in the [```lib/init.c```](./lib/init.c) file.
After the ```PyInit_cpyfputs``` function creates the link to the module's symbols, the declared C methods and datastructures within the ```lib/init.c::PyFputs_methods``` and ```lib/init.c::ExampleClass_methods``` structs are made available to the Python interpreter. The [```ExampleClass```](./lib/include/pyfputs.h) is also instantiated as a Python Object of the module in the initialization function. The methods and datastructures are declared source of the C library and are now ready to be used within the Python module.
## Makefile
The [```Makefile```](./lib/Makefile) is used to build the C library. In this example, its sole purpose is to tell the ```clang``` compiler where the Python header files are located. The ```Makefile``` is located in the [```lib```](./lib) directory.
```make
# Makefile
CFLAGS = -I venv/include/python3.11/
```
Because I am using a Python virtual environment, the Python header files are located in the ```venv/include/python3.11/``` directory. The ```-I``` flag tells the compiler (```clang``` in my case, but it also works for ```gcc```) where to find the header files.
## Building the C Library
Building the library is quite straightfoward. This tutorial takes use of a (deprecated!) ```setup.py``` file to build the Python C library. The ```setup.py``` file is located in the root directory of the repository.
```python
from distutils.core import setup, Extension
def main():
setup(
name="PyFputs",
version="1.0.0",
description="Python interface for the fputs C library function",
author="Diogo Andre",
author_email="your_email@gmail.com",
ext_modules=[Extension(
"cpyfputs",
[ "lib/init.c", "lib/src/pyfputs.c"],
swig_opts=["-modern", "-I./venv/include/python3.11"],
)],
packages=["pyfputs"],
)
if __name__ == "__main__":
main()
```
A different approach is being researched to build the library using Python-native (non-deprecated) tools. If you have any suggestions, please let me know by creating an issue or a pull request.
## Running the Python Program
To run the Python program, simply run the following command in the root directory of the repository:
```bash
$ python pyfputs
```
The [```__main__.py```](./pyfputs/__main__.py) file in the [```pyfputs```](./pyfputs) directory will be executed, creating three new files in the root directory of the repository: ```hello.txt```, ```hello_from_exampleclass.txt``` and ```hello_from_exampleclass2.txt```. There will be stuff written in each of these files.
```python
from pyfputs import fputs, ExampleClassWrapper
if __name__ == "__main__":
fputs("Hello, World!", "hello.txt")
print("Wrote to hello.txt")
obj = ExampleClassWrapper("hello_from_exampleclass.txt")
obj.fputs("Hello, World! From the ExampleClass!")
print("Wrote to hello_from_exampleclass.txt")
obj2 = ExampleClassWrapper()
print("Current filename: ", obj2.filename)
obj2.filename = "hello_from_exampleclass2.txt"
obj2.fputs("Hello, World! From the ExampleClass! Again!")
print("Wrote to hello_from_exampleclass2.txt")
```