Skip to main content

Python package using Pybind11


Installation​

sudo apt install -y libffi-dev python3 python3-pip python3-dev \
&& python3 -m pip install -U pip setuptools wheel twine keyrings.alt pybind11 build

Package directory structure​

workspace
β”œβ”€β”€ LICENSE
β”œβ”€β”€ MANIFEST.in
β”œβ”€β”€ README.md
β”œβ”€β”€ CHANGELOG
β”œβ”€β”€ setup.cfg
β”œβ”€β”€ setup.py
β”œβ”€β”€ c_src
β”‚ └── ...
└── py_src
└── package
β”œβ”€β”€ test
| β”œβ”€β”€ __init__.py
| └── ...
β”œβ”€β”€ subPackage
β”‚ β”œβ”€β”€ __init__.py
β”‚ └── module1.py
β”œβ”€β”€ __init__.py
└── __main__.py

C/C++​

function​

#include <pybind11/pybind11.h>

namespace py = pybind11;

int add(int i, int j) { return i + j; }

PYBIND11_MODULE(_<package>, m) {
// m.def( "add", &add );
m.def("add",
&add,
"A function which adds two numbers",
py::arg("i") = 1,
py::arg("j") = 2);
}

variable​

#include <pybind11/pybind11.h>

namespace py = pybind11;

PYBIND11_MODULE(_<package>, m) {
// Built-in types and general objects (more on that later)
// are automatically converted.
m.attr("var1") = 10;

// Can be explicitly converted using the function `py::cast`.
py::object var2 = py::cast("It is string");
m.attr("var2") = var2;
}

class​

#include <pybind11/pybind11.h>
#include <string>

namespace py = pybind11;

class TestClass {
public:
TestClass(int a);
~TestClass();
int add(int a, int b);
int sub(int a, int b);
float sub(float a, float b);
std::string get_name(void);
void set_name(std::string name);

private:
std::string name;
};

PYBIND11_MODULE(_<package>, m) {
py::class_<TestClass>(m, "TestClass")
.def(py::init<int>())
.def("add", &TestClass::add)
.def("sub", (int (TestClass::*)(int, int)) & TestClass::sub)
.def("sub", (float (TestClass::*)(float, float)) & TestClass::sub)
// .def_readwrite( "name", &TestClass::name ) // public
.def_property(
"name", &TestClass::get_name, &TestClass::set_name); // private
}

Python​

__init__.py​

from <package>._<package> import *

...

Build​

pyproject.toml​

[build-system]
requires = [
"setuptools>=42",
"wheel",
"pybind11>=2.6.0",
]
build-backend = "setuptools.build_meta"
warning

build-system.requiresλŠ” buildν•  λ•Œλ§Œ μ‚¬μš©λ˜λŠ” νŒ¨ν‚€μ§€μž…λ‹ˆλ‹€. μ„€μΉ˜ 이후에 μ‚¬μš©λ  νŒ¨ν‚€μ§€λŠ” setup.cfgμ—μ„œ κ΄€λ¦¬ν•˜μ„Έμš”.

setup.cfg​

Static metadataλ₯Ό κ΄€λ¦¬ν•˜λŠ” νŒŒμΌμž…λ‹ˆλ‹€. μ„€μΉ˜ 상황에 따라 λ°”λ€Œμ§€ μ•ŠλŠ” λ‚΄μš©μ„ μ μœΌμ„Έμš”.

[metadata]
name = yolov4
url = https://github.com/hhk7734/tensorflow-yolov4
project_urls =
Documentation = https://wiki.loliot.net/docs/lang/python/libraries/yolov4/python-yolov4-about
Source = https://github.com/hhk7734/tensorflow-yolov4

author = Hyeonki Hong
author_email = hhk7734@gmail.com
description = YOLOv4: Optimal Speed and Accuracy of Object Detection
long-description = file: README.md, CHANGELOG
long_description_content_type = text/markdown
keywords = tensorflow, yolo, AI, TPU
license = MIT
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: POSIX :: Linux
Intended Audience :: Developers
Intended Audience :: Science/Research
Topic :: Scientific/Engineering
Topic :: Scientific/Engineering :: Artificial Intelligence
Topic :: Software Development
Topic :: System :: Hardware


[options]
package_dir =
= py_src
packages = find:
zip_safe = False
install_requires =
numpy>=1.18.0

[options.packages.find]
where = py_src
info

install_requires에 같이 μ„€μΉ˜λ˜μ–΄μ•Όν•  νŒ¨ν‚€μ§€λ₯Ό 적으면 λ©λ‹ˆλ‹€.

TODO: extras_require

setup.py​

Dynamic metadataλ₯Ό κ΄€λ¦¬ν•˜λŠ” νŒŒμΌμž…λ‹ˆλ‹€. μ„€μΉ˜ 상황에 따라 λ°”λ€” 수 μžˆλŠ” λ‚΄μš©μ„ ν”„λ‘œκ·Έλž˜λ°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

from glob import glob
from os import path

from setuptools import setup
from pybind11.setup_helpers import ParallelCompile, Pybind11Extension, build_ext

BASE_DIR = path.dirname(path.abspath(__file__))
CHANGELOG_PATH = path.join(BASE_DIR, "CHANGELOG")

with open(CHANGELOG_PATH, "r") as f:
__version__ = f.readline()
__version__ = __version__.split()
__version__ = __version__[1][1:-1]

ParallelCompile("NPY_NUM_BUILD_JOBS").install()

ext_modules = [
Pybind11Extension(
# Ref: distutils.extension.Extension
name="yolov4.common._common",
sources=sorted(glob("c_src/**/*.cpp", recursive=True)),
include_dirs=["c_src/"], # -I
define_macros=[(("VERSION_INFO", __version__))], # -D<string>=<string>
undef_macros=[], # [string] -D<string>
library_dirs=[], # [string] -L<string>
libraries=[], # [string] -l<string>
runtime_library_dirs=[], # [string] -rpath=<string>
extra_objects=[], # [string]
extra_compile_args=[], # [string]
extra_link_args=[], # [string]
),
]


setup(
version=__version__,
ext_modules=ext_modules,
cmdclass={"build_ext": build_ext},
)

__version__ 값을 .cpp파일 μ•ˆμ—μ„œ ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€.

#define MACRO_STRINGIFY(x) #x

PYBIND11_MODULE(_<package>, m) {
#ifdef VERSION_INFO
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#else
m.attr("__version__") = "dev";
#endif
}

MANIFEST.in​

include LICENSE
include README.md
include CHANGELOG
include c_src/*

Test install/uninstall with pip​

python3 -m pip install <directory|git|package>
python3 -m pip uninstall <package>

pip 등둝​

dist​

python3 -m build

Test 등둝/μ„€μΉ˜β€‹

python3 -m twine upload --repository testpypi dist/*
python3 -m pip install \
--index-url https://test.pypi.org/simple/ \
--verbose \
--user \
--no-deps \
<package>

정식 등둝/μ„€μΉ˜β€‹

python3 -m twine upload dist/*
python3 -m pip install <package>

Reference​