Code annotations
The following examples demonstrate the use of various code annotations supported by clair/c2py to customize the generated Python bindings.
All examples have been compiled with CMake using the instructions from Compiling the examples.
C2PY_IGNORE
The C2PY_IGNORE annotation can be used to exclude specific functions or classes from being exposed in the Python bindings.
// c2py_ignore.cpp
#include "c2py/c2py.hpp"
// Some function we don't want to wrap.
C2PY_IGNORE int f(int x) { return x * 2; }
// Another function we want to wrap.
int g(int y) { return y * 3; }
After generating the extension module, we can use it in Python:
>>> from c2py_ignore import *
>>> g(2)
6
>>> f(2)
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
f(2)
^
NameError: name 'f' is not defined
As expected, only the function g is available in Python, while f has been ignored.
C2PY_RENAME
The C2PY_RENAME annotation can be used to give a wrapped function or class a different name in the Python bindings.
// c2py_rename.cpp
#include "c2py/c2py.hpp"
// We want to rename f to g in the Python bindings.
C2PY_RENAME(g) int f(int x) { return x * 2; }
After generating the extension module, we can use it in Python:
>>> from c2py_rename import *
>>> g(5)
10
>>> f(5)
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
f(5)
^
NameError: name 'f' is not defined
The function f has been rename to g as instructed.
C2PY_MODULE_INIT
The C2PY_MODULE_INIT annotation lets us define a function that will be called when the module is initialized/imported.
// c2py_module_init.cpp
#include "c2py/c2py.hpp"
#include <iostream>
// This function should be called when the module is initialized (imported) in Python.
C2PY_MODULE_INIT void init() { std::cout << "c2py/clair rocks!" << std::endl; }
After generating the extension module, we can use it in Python:
>>> import c2py_module_init
c2py/clair rocks!
Importing the module triggers the execution of the init function, which prints a message to the console.
C2PY_WRAP_AS_METHOD
The C2PY_WRAP_AS_METHOD annotation lets us add a function as a method to the first argument’s class object.
// c2py_wrap_as_method.cpp
#include "c2py/c2py.hpp"
// Some class we want to wrap.
struct myclass {
int x{};
};
// We want to add this function as a method to myclass in the Python bindings.
C2PY_WRAP_AS_METHOD int f(const myclass &m, int y) { return m.x * y; }
After generating the extension module, we can use it in Python:
>>> from c2py_wrap_as_method import *
>>> m = Myclass(x = 100)
>>> m.f(5)
500
>>> f(5)
Traceback (most recent call last):
File "<python-input-5>", line 1, in <module>
f(5)
^
NameError: name 'f' is not defined
The free C++ function f has been added as a method to the Myclass class in Python.
Compiling the examples
We use the following CMake file:
cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
project(code-annotations VERSION 3.2.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(FetchContent)
# Locate Python and NumPy components
find_package(Python COMPONENTS Interpreter Development NumPy)
# Fetch the c2py library
FetchContent_Declare(
c2py
GIT_REPOSITORY https://github.com/flatironinstitute/c2py
GIT_TAG unstable
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(c2py)
# clair-c2py: optionally regenerate bindings
option(Update_Python_Bindings "Use clair python bindings generators" OFF)
if(Update_Python_Bindings)
find_program(clair-c2py clair-c2py REQUIRED)
endif()
# Add an interface target for all Python modules
add_library(${PROJECT_NAME}_python_modules INTERFACE)
# Build and install any python modules
set(module_sources c2py_ignore.cpp c2py_rename.cpp c2py_module_init.cpp c2py_wrap_as_method.cpp)
foreach(module_src ${module_sources})
get_filename_component(module_name ${module_src} NAME_WE)
get_filename_component(module_dir ${module_src} DIRECTORY)
Python_add_library(${module_name} MODULE ${module_src})
add_library(${PROJECT_NAME}::${module_name} ALIAS ${module_name})
target_link_libraries(${module_name} PRIVATE c2py::c2py)
add_dependencies(${PROJECT_NAME}_python_modules ${module_name})
if(Update_Python_Bindings)
set(wrap_cxx "${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.wrap.cxx")
set(depfile "${CMAKE_CURRENT_BINARY_DIR}/${module_name}.cpp.d")
add_custom_command(
OUTPUT ${wrap_cxx} ${depfile}
COMMAND ${clair-c2py} -p ${PROJECT_BINARY_DIR} --generate-depfile ${depfile} ${module_src}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPFILE ${depfile}
)
add_custom_target(wrap_gen_${module_name} DEPENDS ${wrap_cxx})
add_dependencies(${module_name} wrap_gen_${module_name})
endif()
endforeach()
To compile all modules at once, run:
$ mkdir build
$ cd build
$ cmake .. -DUpdate_Python_Bindings=ON
$ make -j 8