├── .clang-format
├── .github
├── dependabot.yml
├── release-drafter.yml
└── workflows
│ ├── labeler.yml
│ ├── main.yml
│ ├── master-merge.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
├── Doxyfile
├── Makefile
└── source
│ ├── appendix.rst
│ ├── arrays.rst
│ ├── conf.py
│ ├── index.rst
│ ├── install.rst
│ ├── shock-and-awe.rst
│ ├── tutorial.rst
│ └── tutorial
│ ├── functions.rst
│ ├── libpy_tutorial
│ ├── __init__.py
│ ├── arrays.cc
│ ├── classes.cc
│ ├── data
│ │ └── original.png
│ ├── exceptions.cc
│ ├── function.cc
│ ├── ndarrays.cc
│ └── scalar_functions.cc
│ └── setup.py
├── etc
├── asan-path
├── build-and-run
├── detect-compiler.cc
├── ext_suffix.py
├── ld_flags.py
└── python_version.py
├── gdb
└── libpy-gdb.py
├── include
└── libpy
│ ├── abi.h
│ ├── any.h
│ ├── any_vector.h
│ ├── autoclass.h
│ ├── autofunction.h
│ ├── automodule.h
│ ├── borrowed_ref.h
│ ├── buffer.h
│ ├── build_tuple.h
│ ├── call_function.h
│ ├── char_sequence.h
│ ├── datetime64.h
│ ├── demangle.h
│ ├── detail
│ ├── api.h
│ ├── autoclass_cache.h
│ ├── autoclass_object.h
│ ├── no_destruct_wrapper.h
│ ├── numpy.h
│ └── python.h
│ ├── devirtualize.h
│ ├── dict_range.h
│ ├── exception.h
│ ├── from_object.h
│ ├── getattr.h
│ ├── gil.h
│ ├── hash.h
│ ├── itertools.h
│ ├── library_wrappers
│ └── sparsehash.h
│ ├── meta.h
│ ├── ndarray_view.h
│ ├── numpy_utils.h
│ ├── object_map_key.h
│ ├── owned_ref.h
│ ├── range.h
│ ├── scope_guard.h
│ ├── singletons.h
│ ├── str_convert.h
│ ├── stream.h
│ ├── table.h
│ ├── table_details.h
│ ├── to_object.h
│ └── util.h
├── libpy
├── __init__.py
├── _build-and-run
├── _detect-compiler.cc
├── build.py
└── include
├── setup.py
├── src
├── abi.cc
├── autoclass.cc
├── buffer.cc
├── demangle.cc
├── dict_range.cc
├── exception.cc
├── gil.cc
├── object_map_key.cc
└── range.cc
├── testleaks.supp
├── tests
├── .dir-locals.el
├── __init__.py
├── _runner.cc
├── _test_automodule.cc
├── conftest.py
├── cxx.py
├── library_wrappers
│ └── test_sparsehash.cc
├── test_any.cc
├── test_any_vector.cc
├── test_autoclass.cc
├── test_autofunction.cc
├── test_automodule.py
├── test_call_function.cc
├── test_cxx.py
├── test_datetime64.cc
├── test_demangle.cc
├── test_devirtualize.cc
├── test_dict_range.cc
├── test_exception.cc
├── test_from_object.cc
├── test_getattr.cc
├── test_hash.cc
├── test_itertools.cc
├── test_meta.cc
├── test_ndarray_view.cc
├── test_numpy_utils.h
├── test_object_map_key.cc
├── test_range.cc
├── test_scope_guard.cc
├── test_scoped_ref.cc
├── test_singletons.cc
├── test_str_convert.cc
├── test_table.cc
├── test_to_object.cc
├── test_util.cc
└── test_utils.h
├── tox.ini
└── version
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: llvm
2 |
3 | BreakBeforeBraces: Custom
4 | BraceWrapping:
5 | BeforeElse: true
6 | BeforeCatch: true
7 |
8 | AccessModifierOffset: -4
9 | AlignEscapedNewlines: Right
10 | AllowAllParametersOfDeclarationOnNextLine: false
11 | AllowShortBlocksOnASingleLine: false
12 | AllowShortFunctionsOnASingleLine: Empty
13 | AlwaysBreakTemplateDeclarations: true
14 | BinPackArguments: false
15 | BinPackParameters: false
16 | BreakConstructorInitializers: BeforeColon
17 | ColumnLimit: 90
18 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
19 | IndentWidth: 4
20 | NamespaceIndentation: None
21 | PointerAlignment: Left
22 | SortIncludes: true
23 | SortUsingDeclarations: true
24 | SpacesBeforeTrailingComments: 2
25 | SpacesInSquareBrackets: false
26 | SpaceAfterCStyleCast: true
27 | SpaceAfterTemplateKeyword: false
28 |
29 | PenaltyBreakAssignment: 60
30 | PenaltyBreakBeforeFirstCallParameter: 175
31 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "github-actions" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 |
2 | name-template: '$NEXT_PATCH_VERSION 🌈'
3 | tag-template: '$NEXT_PATCH_VERSION'
4 | categories:
5 | - title: 'API Changes'
6 | labels:
7 | - 'api'
8 | - title: 'Benchmark Changes'
9 | labels:
10 | - 'bench'
11 | - title: 'Build Changes'
12 | labels:
13 | - 'build'
14 | - title: 'Bug Fixes'
15 | labels:
16 | - 'bug'
17 | - title: 'Deprecation'
18 | labels:
19 | - 'deprecation'
20 | - title: 'Development Enhancements'
21 | labels:
22 | - 'development'
23 | - title: 'Documentation Updates'
24 | labels:
25 | - 'documentation'
26 | - title: 'Enhancements'
27 | labels:
28 | - 'enhancement'
29 | - title: 'Maintenance'
30 | labels:
31 | - 'maintenance'
32 | - title: 'Reverts'
33 | labels:
34 | - 'revert'
35 | - title: 'Style Changes'
36 | labels:
37 | - 'style'
38 | - title: 'Test Changes'
39 | labels:
40 | - 'test'
41 | - title: 'Release Changes'
42 | labels:
43 | - 'release'
44 | template: |
45 | # What’s Changed
46 | $CHANGES
47 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: "Pull Request Labeler"
2 | on:
3 | pull_request_target:
4 | types: [opened, synchronize, reopened, edited]
5 |
6 | jobs:
7 | pr-labeler:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Label the PR
11 | uses: gerrymanoim/pr-prefix-labeler@v3
12 | env:
13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build-and-test:
13 |
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | os: [ubuntu-20.04, ubuntu-18.04, macos-10.15]
19 | python-version: [3.5, 3.6, 3.8]
20 | compiler: [gcc, clang]
21 | exclude:
22 | - os: macos-10.15
23 | compiler: gcc
24 | include:
25 | - python-version: 3.5
26 | numpy: 1.11.3
27 | - python-version: 3.6
28 | numpy: 1.19
29 | - python-version: 3.8
30 | numpy: 1.19
31 | steps:
32 | - uses: actions/checkout@v2
33 | with:
34 | submodules: 'recursive'
35 | - name: Set release name env variable (ubuntu)
36 | if: startsWith(matrix.os, 'ubuntu')
37 | run: |
38 | echo ::set-env name=UBUNTU_RELEASE::$(lsb_release -sc)
39 | - name: Install newer clang (ubuntu)
40 | if: startsWith(matrix.os, 'ubuntu') && matrix.compiler == 'clang'
41 | run: |
42 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -
43 | sudo add-apt-repository "deb http://apt.llvm.org/$UBUNTU_RELEASE/ llvm-toolchain-$UBUNTU_RELEASE-10 main" -y
44 | sudo apt-get update -q
45 | sudo apt-get install -y clang-10 lld-10 libc++-10-dev libc++abi-10-dev clang-tools-10
46 | echo ::set-env name=AR::llvm-ar-10
47 | - name: Install newer clang (macos)
48 | if: startsWith(matrix.os, 'macos') && matrix.compiler == 'clang'
49 | run: |
50 | brew install llvm
51 | - name: Set up Python ${{ matrix.python-version }}
52 | uses: actions/setup-python@v2.1.4
53 | with:
54 | python-version: ${{ matrix.python-version }}
55 | - name: Install python dependencies
56 | env:
57 | PYTHONWARNINGS: ignore:DEPRECATION::pip._internal.cli.base_command
58 | run: |
59 | python -m pip install --upgrade pip
60 | pip install pytest==4.4.1 numpy==${{ matrix.numpy }}
61 | - name: Install c++ dependencies (ubuntu)
62 | if: startsWith(matrix.os, 'ubuntu')
63 | run: |
64 | sudo apt-get -y install libsparsehash-dev doxygen
65 | - name: Install c++ dependencies (macos)
66 | if: startsWith(matrix.os, 'macos')
67 | run: |
68 | brew install google-sparsehash doxygen
69 | - name: Set llvm related envvars (macos)
70 | if: startsWith(matrix.os, 'macos')
71 | run: |
72 | echo ::set-env name=EXTRA_INCLUDE_DIRS::/usr/local/include/
73 | echo ::add-path::/usr/local/opt/llvm/bin
74 | echo ::set-env name=LDFLAGS::-L/usr/local/opt/llvm/lib
75 | echo ::set-env name=CPPFLAGS::-I/usr/local/opt/llvm/include
76 | echo ::set-env name=AR::llvm-ar
77 | - name: Set clang envvars
78 | if: matrix.compiler == 'clang'
79 | run: |
80 | echo ::set-env name=CC::clang-10
81 | echo ::set-env name=CXX::clang++
82 | - name: Set gcc envvars
83 | if: matrix.compiler == 'gcc'
84 | run: |
85 | echo ::set-env name=CC::gcc-9
86 | echo ::set-env name=CXX::g++-9
87 | - name: Run the tests
88 | run: |
89 | make -j2 test
90 | - name: Build and install from an sdist
91 | run: |
92 | python setup.py sdist
93 | pip install dist/libpy-*.tar.gz
94 | - name: Check that docs can be built
95 | run: |
96 | pip install sphinx sphinx_rtd_theme breathe ipython
97 | make docs
98 |
--------------------------------------------------------------------------------
/.github/workflows/master-merge.yml:
--------------------------------------------------------------------------------
1 | name: On Master Merge
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | draft-release-publish:
10 | name: Draft a new release
11 | runs-on: ubuntu-latest
12 | steps:
13 | # Drafts your next Release notes as Pull Requests are merged into "master"
14 | - uses: release-drafter/release-drafter@v5
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python 🐍 distributions 📦 to PyPI
2 | on:
3 | release:
4 | types: [published]
5 |
6 | jobs:
7 | build-n-publish:
8 | name: Build and publish Python 🐍 distributions 📦 to TestPyPI
9 | runs-on: ubuntu-18.04
10 | steps:
11 | - uses: actions/checkout@v2
12 | with:
13 | submodules: 'recursive'
14 | - name: Set up Python 3.8
15 | uses: actions/setup-python@v2.1.4
16 | with:
17 | python-version: 3.8
18 | - name: Install python dependencies
19 | env:
20 | PYTHONWARNINGS: ignore:DEPRECATION::pip._internal.cli.base_command
21 | run: |
22 | python -m pip install --upgrade pip
23 | pip install numpy==1.19
24 | - name: Install c++ dependencies (ubuntu)
25 | run: |
26 | sudo apt-get -y install libsparsehash-dev doxygen
27 | - name: Set gcc envvars
28 | run: |
29 | echo ::set-env name=CC::gcc-8
30 | echo ::set-env name=CXX::g++-8
31 | - name: Build sdist
32 | run: python setup.py sdist
33 |
34 | - name: Publish distribution 📦 to Test PyPI
35 | uses: pypa/gh-action-pypi-publish@master
36 | with:
37 | skip_existing: true
38 | password: ${{ secrets.test_pypi_password }}
39 | repository_url: https://test.pypi.org/legacy/
40 |
41 | - name: Install from test and test running
42 | run: |
43 | pip install --extra-index-url https://test.pypi.org/simple libpy
44 | mkdir tmp && cd tmp && python -c 'import libpy;print(libpy.__version__)'
45 | pip uninstall -y libpy
46 | cd ..
47 |
48 | - name: Publish distribution 📦 to PyPI
49 | uses: pypa/gh-action-pypi-publish@master
50 | with:
51 | skip_existing: true
52 | password: ${{ secrets.pypi_password }}
53 |
54 | - name: Install and test running
55 | run: |
56 | pip install libpy
57 | cd tmp && python -c 'import libpy;print(libpy.__version__)'
58 | cd ..
59 |
60 | - name: Build the docs
61 | run: |
62 | pip install sphinx sphinx_rtd_theme breathe ipython
63 | make docs
64 |
65 | - name: Deploy the docs
66 | uses: peaceiris/actions-gh-pages@v3
67 | with:
68 | github_token: ${{ secrets.GITHUB_TOKEN }}
69 | publish_dir: ./docs/build/html
70 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 |
3 | # Mac OSX folder properties
4 | .DS_Store
5 |
6 |
7 | # test runner built with `make test`
8 | tests/run
9 | bench/run
10 |
11 | # doc artifacts
12 | html/*
13 | docs/doxygen-build/*
14 | docs/build/*
15 | docs/.built-doxygen
16 | docs/.installed-tutorial
17 |
18 | # scratch file for testing linking
19 | scratch.cc
20 | a.out
21 |
22 | # compiled artifacts
23 | *.o
24 | *.a
25 | *.so*
26 |
27 | # make artifacts
28 | *.d
29 | .make/*
30 |
31 | # coverage artifacts
32 | *.gcov
33 | *.gcno
34 | *.gcda
35 |
36 | # etags
37 | TAGS
38 |
39 | # Local makefile overrides.
40 | Makefile.local
41 |
42 | .gdb_history
43 |
44 | *.pyc
45 |
46 | # make helper
47 | .compiler_flags
48 |
49 | testbin
50 |
51 | # jenkins test output
52 | libpy_report.xml
53 |
54 | # tox envs
55 | .tox/*
56 | .cache/*
57 |
58 | # pytest artifact
59 | .coverage
60 |
61 | # python support artifacts
62 | *.egg-info/*
63 | docs/source/tutorial/libpy_tutorial.egg-info/*
64 | docs/source/savefig/*
65 | dist/*
66 |
67 | venv*/*
68 |
69 | .dir-locals.el
70 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "submodules/googletest"]
2 | path = submodules/googletest
3 | url = git@github.com:google/googletest
4 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # buildtime data
2 | include Makefile
3 | include etc/detect-compiler.cc
4 | include etc/build-and-run
5 | include etc/ext_suffix.py
6 | include etc/asan-path
7 | include etc/ld_flags.py
8 | include etc/python_version.py
9 | include version
10 | recursive-include src/ *.cc
11 | recursive-include include/ *.h
12 |
13 | # runtime data
14 | include LICENSE
15 | include libpy/_build-and-run
16 | include libpy/_detect-compiler.cc
17 | recursive-include libpy *.h
18 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ``libpy``
2 | =========
3 |
4 | .. image:: https://github.com/quantopian/libpy/workflows/CI/badge.svg
5 | :alt: GitHub Actions status
6 | :target: https://github.com/quantopian/libpy/actions?query=workflow%3ACI+branch%3Amaster
7 |
8 | .. image:: https://badge.fury.io/py/libpy.svg
9 | :target: https://badge.fury.io/py/libpy
10 |
11 | ``libpy`` is a library to help you write amazing Python extensions in C++.
12 | ``libpy`` makes it easy to expose C++ code to Python.
13 | ``libpy`` lets you automatically wrap functions and classes.
14 | ``libpy`` is designed for high performance and safety: libpy extension modules should be both faster and safer than using the C API directly.
15 |
16 | `Full documentation `_
17 |
18 | Requirements
19 | ------------
20 |
21 | libpy supports:
22 |
23 | - macOS/Linux
24 | - Python >=3.5
25 |
26 | libpy requires:
27 |
28 | - gcc>=9 or clang>=10
29 | - numpy>=1.11.3
30 |
31 | Optional Requirements
32 | ---------------------
33 |
34 | libpy optionally provides wrappers for the following libraries:
35 |
36 | - google sparsehash
37 |
38 |
39 | Install
40 | -------
41 |
42 | To install for development:
43 |
44 | .. code-block:: bash
45 |
46 | $ make
47 |
48 | Otherwise, ``pip install libpy``, making sure ``CC`` and ``CXX`` environment variables are set to the the right compiler.
49 |
50 | **Note**: The installation of ``libpy`` will use the ``python`` executable to
51 | figure out information about your environment. If you are not using a virtual
52 | environment or ``python`` does not point to the Python installation you want
53 | to use (checked with ``which python`` and ``python --version``) you must
54 | point to your Python executable using the ``PYTHON`` environment variable,
55 | i.e. ``PYTHON=python3 make`` or ``PYTHON=python3 pip3 install libpy``.
56 |
57 | Tests
58 | -----
59 |
60 | To run the unit tests, invoke:
61 |
62 | .. code-block:: bash
63 |
64 | $ make test
65 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | SPHINXOPTS ?=
2 | SPHINXBUILD ?= sphinx-build
3 | SOURCEDIR = source
4 | BUILDDIR = build
5 |
6 | # Put it first so that "make" without argument is like "make help".
7 | .PHONY: help
8 | help:
9 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
10 |
11 | .PHONY: Makefile
12 |
13 | # Catch-all target: route all unknown targets to Sphinx using the new
14 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
15 | %: .built-doxygen .installed-tutorial Makefile
16 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
17 |
18 | Doxyfile:
19 | touch $@
20 |
21 | .built-doxygen: Doxyfile $(shell find ../include/libpy/ -type f -name '*.h')
22 | doxygen
23 | @touch $@
24 |
25 | .installed-tutorial:
26 | pip install -e source/tutorial
27 | @touch $@
28 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | project = 'libpy'
2 | copyright = '2020, Quantopian Inc.'
3 | author = 'Quantopian Inc.'
4 |
5 | # The full version, including alpha/beta/rc tags
6 | release = '0.1.0'
7 |
8 | extensions = [
9 | 'breathe',
10 | 'IPython.sphinxext.ipython_console_highlighting',
11 | 'IPython.sphinxext.ipython_directive',
12 | 'sphinx.ext.autodoc',
13 | ]
14 |
15 | breathe_projects = {'libpy': '../doxygen-build/xml'}
16 | breathe_default_project = 'libpy'
17 |
18 |
19 | # Add any paths that contain templates here, relative to this directory.
20 | templates_path = ['_templates']
21 |
22 | # List of patterns, relative to source directory, that match files and
23 | # directories to ignore when looking for source files.
24 | # This pattern also affects html_static_path and html_extra_path.
25 | exclude_patterns = []
26 |
27 | # The theme to use for HTML and HTML Help pages. See the documentation for
28 | # a list of builtin themes.
29 | #
30 | html_theme = "sphinx_rtd_theme"
31 |
32 | # Add any paths that contain custom static files (such as style sheets) here,
33 | # relative to this directory. They are copied after the builtin static files,
34 | # so a file named "default.css" will overwrite the builtin "default.css".
35 | html_static_path = ['_static']
36 |
37 | highlight_language = 'cpp'
38 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to libpy's documentation!
2 | =================================
3 |
4 | ``libpy`` is a library to help you write amazing Python extensions in C++.
5 | ``libpy`` makes it easy to expose C++ code to Python.
6 | ``libpy`` lets you automatically wrap functions and classes.
7 | ``libpy`` is designed for high performance and safety: libpy extension modules should be both faster and safer than using the C API directly.
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | shock-and-awe
14 | install
15 | tutorial
16 | arrays
17 | appendix
18 |
19 |
20 |
21 | Indices and tables
22 | ==================
23 |
24 | * :ref:`genindex`
25 | * :ref:`modindex`
26 | * :ref:`search`
27 |
--------------------------------------------------------------------------------
/docs/source/install.rst:
--------------------------------------------------------------------------------
1 | Setup
2 | =====
3 |
4 | Requirements
5 | ------------
6 |
7 | lipy supports:
8 |
9 | - macOS/Linux
10 | - Python >=3.5
11 |
12 | lipy requires:
13 |
14 | - gcc>=9 or clang>=10
15 | - numpy>=1.11.3
16 |
17 | Optional Requirements
18 | ---------------------
19 |
20 | libpy optionally provides wrappers for the following libraries:
21 |
22 | - google sparsehash
23 |
24 | Install
25 | -------
26 |
27 | To install for development:
28 |
29 | .. code-block:: bash
30 |
31 | $ make
32 |
33 | Otherwise, ``pip install libpy``, making sure ``CC`` and ``CXX`` environment variables are set to the the right compiler.
34 |
35 | .. note::
36 | The installation of ``libpy`` will use the ``python`` executable to
37 | figure out information about your environment. If you are not using a virtual
38 | environment or ``python`` does not point to the Python installation you want
39 | to use (checked with ``which python`` and ``python --version``) you must
40 | point to your Python executable using the ``PYTHON`` environment variable,
41 | i.e. ``PYTHON=python3 make`` or ``PYTHON=python3 pip3 install libpy``.
42 |
43 | Tests
44 | -----
45 |
46 | To run the unit tests, invoke:
47 |
48 | .. code-block:: bash
49 |
50 | $ make test
51 |
--------------------------------------------------------------------------------
/docs/source/shock-and-awe.rst:
--------------------------------------------------------------------------------
1 | ====================
2 | Shock and Awe [#f1]_
3 | ====================
4 |
5 | A *concise* overview of ``libpy``. For an introduction to extending Python with C or C++ please see `the Python documentation `_ or `Joe Jevnik's C Extension Tutorial `_.
6 |
7 | Simple Scalar Functions
8 | =======================
9 |
10 | We start by building simple scalar functions in C++ which we can call from Python.
11 |
12 | .. ipython:: python
13 |
14 | from libpy_tutorial import scalar_functions
15 |
16 | A simple scalar function:
17 |
18 | .. literalinclude:: tutorial/libpy_tutorial/scalar_functions.cc
19 | :lines: 11-13
20 |
21 | .. ipython:: python
22 |
23 | scalar_functions.bool_scalar(False)
24 |
25 | A great way to use ``libpy`` is to write the code that needs to be fast in C++ and expose that code via Python. Let's estimate ``pi`` using a monte carlo simulation:
26 |
27 | .. literalinclude:: tutorial/libpy_tutorial/scalar_functions.cc
28 | :lines: 15-30
29 |
30 | .. ipython:: python
31 |
32 | scalar_functions.monte_carlo_pi(10000000)
33 |
34 | Of course, we can build C++ functions that support all the features of regular Python functions.
35 |
36 | ``libpy`` supports optional args:
37 |
38 | .. literalinclude:: tutorial/libpy_tutorial/scalar_functions.cc
39 | :lines: 34-36
40 |
41 | .. ipython:: python
42 |
43 | scalar_functions.optional_arg(b"An argument was passed")
44 | scalar_functions.optional_arg()
45 |
46 | and keyword/optional keyword arguments:
47 |
48 | .. literalinclude:: tutorial/libpy_tutorial/scalar_functions.cc
49 | :lines: 38-44
50 |
51 | .. ipython:: python
52 |
53 | scalar_functions.keyword_args(kw_arg_kwd=1)
54 | scalar_functions.keyword_args(kw_arg_kwd=1, opt_kw_arg_kwd=55)
55 |
56 |
57 | Working With Arrays
58 | ===================
59 |
60 | In order to write performant code it is often useful to write vectorized functions that act on arrays. Thus, libpy has extenstive support for ``numpy`` arrays.
61 |
62 | .. ipython:: python
63 |
64 | from libpy_tutorial import arrays
65 | import numpy as np
66 |
67 | We can take ``numpy`` arrays as input:
68 |
69 | .. literalinclude:: tutorial/libpy_tutorial/arrays.cc
70 | :lines: 11-17
71 |
72 | .. ipython:: python
73 |
74 | some_numbers = np.arange(20000)
75 | arrays.simple_sum(some_numbers)
76 |
77 | and return them as output:
78 |
79 | .. literalinclude:: tutorial/libpy_tutorial/arrays.cc
80 | :lines: 23-43
81 |
82 | .. ipython:: python
83 |
84 | prime_mask = arrays.is_prime(some_numbers)
85 | some_numbers[prime_mask][:100]
86 |
87 | .. note:: ``numpy`` arrays passed to C++ are `ranges `_.
88 |
89 | .. literalinclude:: tutorial/libpy_tutorial/arrays.cc
90 | :lines: 19-21
91 |
92 | .. ipython:: python
93 |
94 | arrays.simple_sum_iterator(some_numbers)
95 |
96 | N Dimensional Arrays
97 | ====================
98 |
99 | We can also work with n-dimensional arrays. As a motivating example, let's sharpen an image. Specifically - we will sharpen:
100 |
101 | .. ipython:: python
102 |
103 | from PIL import Image
104 | import matplotlib.pyplot as plt # to show the image in documenation
105 | import numpy as np
106 | import pkg_resources
107 | img_file = pkg_resources.resource_stream("libpy_tutorial", "data/original.png")
108 | img = Image.open(img_file)
109 | @savefig original.png width=200px
110 | plt.imshow(img)
111 |
112 | .. literalinclude:: tutorial/libpy_tutorial/ndarrays.cc
113 | :lines: 10-55
114 |
115 | .. ipython:: python
116 |
117 | pixels = np.array(img)
118 | kernel = np.array([
119 | [0, -1, 0],
120 | [-1, 5, -1],
121 | [0, -1, 0]
122 | ]) # already normalized
123 | from libpy_tutorial import ndarrays
124 | res = ndarrays.apply_kernel(pixels, kernel)
125 | @savefig sharpened.png width=200px
126 | plt.imshow(res)
127 |
128 |
129 | .. note:: We are able to pass a shaped n-dimensional array as input and return one as output.
130 |
131 |
132 | Creating Classes
133 | ================
134 |
135 | ``libpy`` also allows you to construct C++ classes and then easily expose them as if they are regular Python classes.
136 |
137 | .. ipython:: python
138 |
139 | from libpy_tutorial.classes import Vec3d
140 |
141 | C++ classes are able to emulate all the features of Python classes:
142 |
143 | .. literalinclude:: tutorial/libpy_tutorial/classes.cc
144 | :lines: 9-67
145 |
146 | .. literalinclude:: tutorial/libpy_tutorial/classes.cc
147 | :lines: 93-106
148 |
149 | .. ipython:: python
150 |
151 | Vec3d.__doc__
152 | v = Vec3d(1, 2, 3)
153 | v
154 | str(v)
155 | v.x(), v.y(), v.z()
156 | w = Vec3d(4, 5, 6); w
157 | v + w
158 | v * w
159 | v.magnitude()
160 |
161 | Exceptions
162 | ==========
163 |
164 | Working with exceptions is also important.
165 |
166 | .. ipython:: python
167 |
168 | from libpy_tutorial import exceptions
169 |
170 | We can throw exceptions in C++ that will then be dealt with in Python. Two patterns:
171 |
172 | 1. Throw your own exception: ``throw py::exception(type, msg...)``, maybe in response to an exception from a C-API function.
173 | 2. Throw a C++ exception directly.
174 |
175 | .. literalinclude:: tutorial/libpy_tutorial/exceptions.cc
176 | :lines: 11-17
177 |
178 | ::
179 |
180 | In [40]: exceptions.throw_value_error(4)
181 | ---------------------------------------------------------------------------
182 | ValueError Traceback (most recent call last)
183 | in
184 | ----> 1 exceptions.throw_value_error(4)
185 |
186 | ValueError: You passed 4 and this is the exception
187 |
188 | In [41]: exceptions.raise_from_cxx()
189 | ---------------------------------------------------------------------------
190 | RuntimeError Traceback (most recent call last)
191 | in
192 | ----> 1 exceptions.raise_from_cxx()
193 |
194 | RuntimeError: a C++ exception was raised: Supposedly a bad argument was used
195 |
196 | .. rubric:: Footnotes
197 |
198 | .. [#f1] With naming credit to the intorduction of `Q for Mortals `_.
199 |
--------------------------------------------------------------------------------
/docs/source/tutorial.rst:
--------------------------------------------------------------------------------
1 | Tutorial
2 | ========
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents:
7 |
8 | tutorial/functions
9 |
--------------------------------------------------------------------------------
/docs/source/tutorial/functions.rst:
--------------------------------------------------------------------------------
1 | =========
2 | Functions
3 | =========
4 |
5 | The simplest unit of code that can be exposed to Python from C++ is a function.
6 | ``libpy`` supports automatically converting C++ functions into Python functions by adapting the parameter types and return type.
7 |
8 | ``libpy`` uses :cpp:func:`py::autofunction` to convert a C++ function into a Python function definition [#f1]_.
9 | The result of :cpp:func:`py::autofunction` can be attached to a Python module object and made available to Python.
10 |
11 | A Simple C++ Function
12 | =====================
13 |
14 | Let's start by writing a simple C++ function to expose to Python:
15 |
16 | .. code-block:: c++
17 |
18 | double fma(double a, double b, double c) {
19 | return a * b + c;
20 | }
21 |
22 |
23 | ``fma`` is a standard C++ function with no knowledge of Python.
24 |
25 |
26 | Adapting a Function
27 | ===================
28 |
29 | To adapt ``fma`` into a Python function, we need to use :cpp:func:`py::autofunction`.
30 |
31 | .. code-block:: c++
32 |
33 | PyMethodDef fma_methoddef = py::autofunction("fma");
34 |
35 | :cpp:func:`py::autofunction` is a template function which takes as a template argument the C++ function to adapt.
36 | :cpp:func:`py::autofunction` also takes a string which is the name of the function as it will be exposed to Python.
37 | The Python function name does not need to match the C++ name.
38 | :cpp:func:`py::autofunction` takes an optional second argument: a string to use as the Python docstring.
39 | For example, a docstring could be added to ``fma`` with:
40 |
41 | .. code-block:: c++
42 |
43 | PyMethodDef fma_methoddef = py::autofunction("fma", "Fused Multiply Add");
44 |
45 | .. warning::
46 |
47 | Currently the ``name`` and ``doc`` string parameters **must outlive** the resulting :c:struct:`PyMethodDef`.
48 | In practice, this means it should be a static string, or string literal.
49 |
50 | Adding the Function to a Module
51 | ===============================
52 |
53 | To use an adapted function from Python, it must be attached to a module so that it may be imported by Python code.
54 | To create a Python method, we can use :c:macro:`LIBPY_AUTOMETHOD`.
55 | :c:macro:`LIBPY_AUTOMETHOD` is a macro which takes in the package name, the module name, and the set of functions to add.
56 | Following the call to :c:macro:`LIBPY_AUTOMETHOD`, we must provide a function which is called when the module is first imported.
57 | To just add functions, our body can be a simple ``return false`` to indicate that no errors occurred.
58 |
59 | .. code-block:: c++
60 |
61 | LIBPY_AUTOMODULE(libpy_tutorial, function, ({fma_methoddef}))
62 | (py::borrowed_ref<>) {
63 | return false;
64 | }
65 |
66 | Building and Importing the Module
67 | =================================
68 |
69 | To build a libpy extension, we can use ``setup.py`` and libpy's :class:`~libpy.build.LibpyExtension` class.
70 |
71 | In the ``setup.py``\'s ``setup`` call, we can add a list of ``ext_modules`` to be built:
72 |
73 | .. code-block:: python
74 |
75 | from libpy.build import LibpyExtension
76 |
77 | setup(
78 | # ...
79 | ext_modules=[
80 | LibpyExtension(
81 | 'libpy_tutorial.function',
82 | ['libpy_tutorial/function.cc'],
83 | ),
84 | ],
85 | # ...
86 | )
87 |
88 | Now, the extension can be built with:
89 |
90 | .. code-block:: bash
91 |
92 | $ python setup.py build_ext --inplace
93 |
94 | Finally, the function can be imported and used from python:
95 |
96 |
97 | .. ipython:: python
98 |
99 | import libpy # we need to ensure we import libpy before importing our extension
100 | from libpy_tutorial.function import fma
101 | fma(2.0, 3.0, 4.0)
102 |
103 | .. rubric:: Footnotes
104 |
105 | .. [#f1] :cpp:func:`py::autofunction` creates a :c:struct:`PyMethodDef` instance, which is not yet a Python object.
106 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/__init__.py:
--------------------------------------------------------------------------------
1 | import libpy # noqa
2 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/arrays.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace libpy_tutorial {
10 | std::int64_t simple_sum(py::array_view values) {
11 | std::int64_t out = 0;
12 | for (auto value : values) {
13 | out += value;
14 | }
15 | return out;
16 | }
17 |
18 | std::int64_t simple_sum_iterator(py::array_view values) {
19 | return std::accumulate(values.begin(), values.end(), 0);
20 | }
21 |
22 | void negate_inplace(py::array_view values) {
23 | std::transform(values.cbegin(),
24 | values.cend(),
25 | values.begin(),
26 | [](std::int64_t v) { return -v; });
27 | }
28 |
29 | bool check_prime(std::int64_t n) {
30 | if (n <= 3) {
31 | return n > 1;
32 | }
33 | else if (n % 2 == 0 || n % 3 == 0) {
34 | return false;
35 | }
36 | for (auto i = 5; std::pow(i, 2) < n; i += 6) {
37 | if (n % i == 0 || n % (i + 2) == 0) {
38 | return false;
39 | }
40 | }
41 | return true;
42 | }
43 |
44 | py::owned_ref<> is_prime(py::array_view values) {
45 | std::vector out(values.size());
46 | std::transform(values.begin(), values.end(), out.begin(), check_prime);
47 |
48 | return py::move_to_numpy_array(std::move(out));
49 | }
50 |
51 | LIBPY_AUTOMODULE(libpy_tutorial,
52 | arrays,
53 | ({py::autofunction("simple_sum"),
54 | py::autofunction("simple_sum_iterator"),
55 | py::autofunction("negate_inplace"),
56 | py::autofunction("is_prime")}))
57 | (py::borrowed_ref<>) {
58 | return false;
59 | }
60 | } // namespace libpy_tutorial
61 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/classes.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | namespace libpy_tutorial {
10 | class vec3d {
11 | private:
12 | std::array m_values;
13 |
14 | public:
15 | vec3d(double x, double y, double z) : m_values({x, y, z}) {}
16 |
17 | double x() const {
18 | return m_values[0];
19 | }
20 |
21 | double y() const {
22 | return m_values[1];
23 | }
24 |
25 | double z() const {
26 | return m_values[2];
27 | }
28 |
29 | vec3d operator+(const vec3d& other) const {
30 | return {x() + other.x(), y() + other.y(), z() + other.z()};
31 | }
32 |
33 | vec3d operator-(const vec3d& other) const {
34 | return {x() - other.x(), y() - other.y(), z() - other.z()};
35 | }
36 |
37 | double operator*(const vec3d& other) const {
38 | return std::inner_product(m_values.begin(),
39 | m_values.end(),
40 | other.m_values.begin(),
41 | 0.0);
42 | }
43 |
44 | double magnitude() const {
45 | return std::sqrt(*this * *this);
46 | }
47 | };
48 |
49 | std::ostream& operator<<(std::ostream& s, const vec3d& v) {
50 | return s << '{' << v.x() << ", " << v.y() << ", " << v.z() << '}';
51 | }
52 |
53 | // `repr` could also be a member function, but free functions are useful for adding
54 | // a Python repr without modifying the methods of the type.
55 | std::string repr(const vec3d& v) {
56 | std::stringstream ss;
57 | ss << "Vec3d(" << v.x() << ", " << v.y() << ", " << v.z() << ')';
58 | return ss.str();
59 | }
60 | } // namespace libpy_tutorial
61 |
62 | namespace py::dispatch {
63 | // Make it possible to convert a `vec3d` into a Python object.
64 | template<>
65 | struct LIBPY_NO_EXPORT to_object
66 | : public py::autoclass::to_object {};
67 | } // namespace py::dispatch
68 |
69 | namespace libpy_tutorial {
70 |
71 | using namespace std::string_literals;
72 |
73 | LIBPY_AUTOMODULE(libpy_tutorial, classes, ({}))
74 | (py::borrowed_ref<> m) {
75 | py::owned_ref t =
76 | py::autoclass(PyModule_GetName(m.get()) + ".Vec3d"s)
77 | .doc("An efficient 3-vector.") // add a class docstring
78 | .new_() //__new__ takes parameters
79 | // bind the named methods to Python
80 | .def<&vec3d::x>("x")
81 | .def<&vec3d::y>("y")
82 | .def<&vec3d::z>("z")
83 | .def<&vec3d::magnitude>("magnitude")
84 | .str() // set `operator<<(std::ostream&, vec3d) to `str(x)` in Python
85 | .repr() // set `repr` to be the result of `repr(x)` in Python
86 | .arithmetic() // bind the arithmetic operators to their Python
87 | // equivalents
88 | .type();
89 | return PyObject_SetAttrString(m.get(), "Vec3d", static_cast(t));
90 | }
91 | } // namespace libpy_tutorial
92 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/data/original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantopian/libpy/e174ee103db76a9d0fcd29165d54c676ed1f2629/docs/source/tutorial/libpy_tutorial/data/original.png
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/exceptions.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | namespace libpy_tutorial {
10 |
11 | void throw_value_error(int a) {
12 | throw py::exception(PyExc_ValueError, "You passed ", a, " and this is the exception");
13 | }
14 |
15 | void raise_from_cxx() {
16 | throw std::invalid_argument("Supposedly a bad argument was used");
17 | }
18 |
19 | LIBPY_AUTOMODULE(libpy_tutorial,
20 | exceptions,
21 | ({py::autofunction("throw_value_error"),
22 | py::autofunction("raise_from_cxx")}))
23 | (py::borrowed_ref<>) {
24 | return false;
25 | }
26 |
27 | } // namespace libpy_tutorial
28 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/function.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | namespace libpy_tutorial {
5 | double fma(double a, double b, double c) {
6 | return a * b + c;
7 | }
8 |
9 | PyMethodDef fma_methoddef = py::autofunction("fma", "Fused Multiply Add");
10 |
11 | LIBPY_AUTOMODULE(libpy_tutorial, function, ({fma_methoddef}))
12 | (py::borrowed_ref<>) {
13 | return false;
14 | }
15 | } // namespace libpy_tutorial
16 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/ndarrays.cc:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace libpy_tutorial {
9 |
10 | py::owned_ref<> apply_kernel(py::ndarray_view pixels,
11 | py::ndarray_view kernel) {
12 |
13 | auto n_dimensions = pixels.shape()[2];
14 | auto n_rows = pixels.shape()[0];
15 | auto n_columns = pixels.shape()[1];
16 |
17 | auto k_rows = kernel.shape()[0];
18 | auto k_columns = kernel.shape()[1];
19 | std::vector out(n_dimensions * n_rows * n_columns, 0);
20 | py::ndarray_view out_view(out.data(),
21 | pixels.shape(),
22 | {static_cast(n_dimensions * n_rows),
23 | static_cast(n_dimensions),
24 | 1});
25 |
26 | for (std::size_t dim = 0; dim < n_dimensions; ++dim) {
27 | for (std::size_t row = 0; row < n_rows; ++row) {
28 | for (std::size_t column = 0; column < n_columns; ++column) {
29 |
30 | auto accumulated_sum = 0.0;
31 |
32 | for (std::size_t k_row = 0; k_row < k_rows; ++k_row) {
33 | for (std::size_t k_column = 0; k_column < k_columns; ++k_column) {
34 |
35 | auto input_row_idx = row + 1 - k_row;
36 | auto input_column_idx = column + 1 - k_column;
37 |
38 | if (input_row_idx < n_rows && input_column_idx < n_columns) {
39 | accumulated_sum +=
40 | pixels(input_row_idx, input_column_idx, dim) *
41 | kernel(k_row, k_column);
42 | }
43 | }
44 | }
45 | if (accumulated_sum < 0) {
46 | accumulated_sum = 0;
47 | }
48 | else if (accumulated_sum > 255) {
49 | accumulated_sum = 255;
50 | }
51 | out_view(row, column, dim) = accumulated_sum;
52 | }
53 | }
54 | }
55 | return py::move_to_numpy_array(std::move(out),
56 | py::new_dtype(),
57 | pixels.shape(),
58 | pixels.strides());
59 | }
60 |
61 | LIBPY_AUTOMODULE(libpy_tutorial,
62 | ndarrays,
63 | ({py::autofunction("apply_kernel")}))
64 | (py::borrowed_ref<>) {
65 | return false;
66 | }
67 | } // namespace libpy_tutorial
68 |
--------------------------------------------------------------------------------
/docs/source/tutorial/libpy_tutorial/scalar_functions.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace libpy_tutorial {
10 |
11 | bool bool_scalar(bool a) {
12 | return !a;
13 | }
14 |
15 | double monte_carlo_pi(int n_samples) {
16 | int accumulator = 0;
17 |
18 | std::random_device rd; // Will be used to obtain a seed for the random number engine
19 | std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
20 | std::uniform_real_distribution<> dis(0, 1);
21 |
22 | for (int i = 0; i < n_samples; ++i) {
23 | auto x = dis(gen);
24 | auto y = dis(gen);
25 | if ((std::pow(x, 2) + std::pow(y, 2)) < 1.0) {
26 | accumulator += 1;
27 | }
28 | }
29 | return 4.0 * accumulator / n_samples;
30 | }
31 |
32 | using namespace py::cs::literals;
33 |
34 | std::string optional_arg(py::arg::optional opt_arg) {
35 | return opt_arg.get().value_or("default value");
36 | }
37 |
38 | py::owned_ref<>
39 | keyword_args(py::arg::kwd kw_arg_kwd,
40 | py::arg::opt_kwd opt_kw_arg_kwd) {
41 |
42 | return py::build_tuple(kw_arg_kwd.get(), opt_kw_arg_kwd.get());
43 | }
44 |
45 | LIBPY_AUTOMODULE(libpy_tutorial,
46 | scalar_functions,
47 | ({py::autofunction("bool_scalar"),
48 | py::autofunction("monte_carlo_pi"),
49 | py::autofunction("optional_arg"),
50 | py::autofunction("keyword_args")}))
51 | (py::borrowed_ref<>) {
52 | return false;
53 | }
54 |
55 | } // namespace libpy_tutorial
56 |
--------------------------------------------------------------------------------
/docs/source/tutorial/setup.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import glob
3 | import os
4 | import sys
5 |
6 | from libpy.build import LibpyExtension
7 | from setuptools import find_packages, setup
8 |
9 | if ast.literal_eval(os.environ.get("LIBPY_TUTORIAL_DEBUG_BUILD", "0")):
10 | optlevel = 0
11 | debug_symbols = True
12 | max_errors = 5
13 | else:
14 | optlevel = 3
15 | debug_symbols = False
16 | max_errors = None
17 |
18 |
19 | def extension(*args, **kwargs):
20 | extra_compile_args = []
21 | if sys.platform == 'darwin':
22 | extra_compile_args.append('-mmacosx-version-min=10.15')
23 |
24 | return LibpyExtension(
25 | *args,
26 | optlevel=optlevel,
27 | debug_symbols=debug_symbols,
28 | werror=True,
29 | max_errors=max_errors,
30 | include_dirs=["."] + kwargs.pop("include_dirs", []),
31 | extra_compile_args=extra_compile_args,
32 | depends=glob.glob("**/*.h", recursive=True),
33 | **kwargs
34 | )
35 |
36 |
37 | install_requires = [
38 | 'setuptools',
39 | 'libpy',
40 | 'matplotlib',
41 | 'pillow',
42 | ]
43 |
44 | setup(
45 | name="libpy_tutorial",
46 | version="0.1.0",
47 | description="Tutorial for libpy",
48 | author="Quantopian Inc.",
49 | author_email="opensource@quantopian.com",
50 | packages=find_packages(),
51 | package_data={
52 | "": ["*.png"],
53 | },
54 | include_package_data=True,
55 | install_requires=install_requires,
56 | license="Apache 2.0",
57 | url="https://github.com/quantopian/libpy",
58 | ext_modules=[
59 | extension(
60 | "libpy_tutorial.scalar_functions",
61 | ["libpy_tutorial/scalar_functions.cc"],
62 | ),
63 | extension(
64 | "libpy_tutorial.arrays",
65 | ["libpy_tutorial/arrays.cc"],
66 | ),
67 | extension(
68 | "libpy_tutorial.ndarrays",
69 | ["libpy_tutorial/ndarrays.cc"],
70 | ),
71 | extension(
72 | "libpy_tutorial.exceptions",
73 | ["libpy_tutorial/exceptions.cc"],
74 | ),
75 | extension(
76 | "libpy_tutorial.classes",
77 | ["libpy_tutorial/classes.cc"],
78 | ),
79 | extension(
80 | "libpy_tutorial.function",
81 | ["libpy_tutorial/function.cc"],
82 | ),
83 | ],
84 | )
85 |
--------------------------------------------------------------------------------
/etc/asan-path:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ubuntu="/usr/lib/gcc/x86_64-linux-gnu/$($CXX -dumpversion)/libasan.so"
4 | arch="/usr/lib/libasan.so"
5 | macos="/usr/local/lib/gcc/$($CXX -dumpversion)/libasan.dylib"
6 | if [ -f "$ubuntu" ];then
7 | echo $ubuntu
8 | elif [ -f "$arch" ];then
9 | echo $arch
10 | elif [ -f "$macos" ];then
11 | echo $macos
12 | else
13 | echo "could not find libasan.so"
14 | exit 1
15 | fi
16 |
--------------------------------------------------------------------------------
/etc/build-and-run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dir=$(mktemp -d)
4 | file=$dir/a.out
5 | ${CXX:-g++} $@ -o $file
6 | $file
7 | rm -rf dir
8 |
--------------------------------------------------------------------------------
/etc/detect-compiler.cc:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main() {
4 | const char* name;
5 | #if defined(__clang__)
6 | name = "CLANG";
7 | #elif defined (__GNUC__)
8 | name = "GCC";
9 | #else
10 | name = "UNKNOWN";
11 | #endif
12 | std::cout << name << '\n';
13 | return 0;
14 | }
15 |
--------------------------------------------------------------------------------
/etc/ext_suffix.py:
--------------------------------------------------------------------------------
1 | import sysconfig
2 | print(sysconfig.get_config_var('EXT_SUFFIX') or '.so')
3 |
--------------------------------------------------------------------------------
/etc/ld_flags.py:
--------------------------------------------------------------------------------
1 | # via https://github.com/python/cpython/blob/deb016224cc506503fb05e821a60158c83918ed4/Misc/python-config.in#L50 # noqa
2 |
3 | import sysconfig
4 |
5 | libs = []
6 | libpl = sysconfig.get_config_vars('LIBPL')
7 | if libpl:
8 | libs.append("-L"+libpl[0])
9 |
10 | libpython = sysconfig.get_config_var('LIBPYTHON')
11 | if libpython:
12 | libs.append(libpython)
13 | libs.extend(sysconfig.get_config_vars("LIBS", "SYSLIBS"))
14 | print(' '.join(libs))
15 |
--------------------------------------------------------------------------------
/etc/python_version.py:
--------------------------------------------------------------------------------
1 | import sys
2 | print(' '.join(map(str, sys.version_info[:2])))
3 |
--------------------------------------------------------------------------------
/gdb/libpy-gdb.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import gdb
4 | import numpy as np
5 |
6 |
7 | def pretty_printer(cls):
8 | gdb.pretty_printers.append(cls.maybe_construct)
9 | return cls
10 |
11 |
12 | @pretty_printer
13 | class Datetime64:
14 | _pattern = re.compile(
15 | r'^py::datetime64 > >$'
17 | )
18 |
19 | _units = {
20 | (1, 1000000000): 'ns',
21 | (1, 1000000): 'ms',
22 | (1, 1000): 'us',
23 | (1, 1): 's',
24 | (60, 1): 'm',
25 | (60 * 60, 1): 'h',
26 | (60 * 60 * 24, 1): 'D',
27 | }
28 |
29 | def __init__(self, val, count, unit):
30 | self.val = val
31 | self.count = count
32 | self.unit = unit
33 |
34 | @classmethod
35 | def maybe_construct(cls, val):
36 | underlying = gdb.types.get_basic_type(val.type)
37 |
38 | match = cls._pattern.match(str(underlying))
39 | if match is None:
40 | return None
41 |
42 | num = int(match[1])
43 | den = int(match[2])
44 | return cls(val, int(val['m_value']['__r']), cls._units[num, den])
45 |
46 | def children(self):
47 | yield 'm_value', str(np.datetime64(self.count, self.unit))
48 |
49 | def to_string(self):
50 | return f'py::datetime64'
51 |
--------------------------------------------------------------------------------
/include/libpy/abi.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libpy/detail/api.h"
4 | #include "libpy/exception.h"
5 |
6 | namespace py::abi {
7 | /** Structure for holding the ABI version of the libpy library.
8 |
9 | @note This must match `libpy.load_library._abi_verson` in the Python support library.
10 | */
11 | struct abi_version {
12 | int major;
13 | int minor;
14 | int patch;
15 | };
16 |
17 | LIBPY_EXPORT std::ostream& operator<<(std::ostream&, abi_version);
18 |
19 | namespace detail {
20 | constexpr abi_version header_libpy_abi_version{LIBPY_MAJOR_VERSION,
21 | LIBPY_MINOR_VERSION,
22 | LIBPY_MICRO_VERSION};
23 | } // namespace detail
24 |
25 | /** The version of the libpy shared object.
26 | */
27 | extern "C" LIBPY_EXPORT abi_version libpy_abi_version;
28 |
29 | /** Check if two abi versions are compatible.
30 |
31 | @param provider The version of the implementation provider.
32 | @param consumer The version of the implementation consumer.
33 | */
34 | inline bool compatible_versions(abi_version provider, abi_version consumer) {
35 | return provider.major == consumer.major && provider.minor >= consumer.minor;
36 | }
37 |
38 | /** Check that the ABI of the libpy object is compatible with an extension module
39 | compiled against libpy.
40 |
41 | @return true with a Python exception raised if the ABI versions are incompatible.
42 | */
43 | inline bool ensure_compatible_libpy_abi() {
44 | if (!compatible_versions(libpy_abi_version, detail::header_libpy_abi_version)) {
45 | py::raise(PyExc_ImportError)
46 | << "libpy compiled version is incompatible with the compiled version of this "
47 | "extension module\nlibpy version: "
48 | << libpy_abi_version
49 | << "\nthis library: " << detail::header_libpy_abi_version;
50 | return true;
51 | }
52 | return false;
53 | }
54 | } // namespace py::abi
55 |
--------------------------------------------------------------------------------
/include/libpy/automodule.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "libpy/abi.h"
8 | #include "libpy/borrowed_ref.h"
9 | #include "libpy/detail/api.h"
10 | #include "libpy/detail/numpy.h"
11 | #include "libpy/detail/python.h"
12 | #include "libpy/owned_ref.h"
13 |
14 | #define _libpy_XSTR(s) #s
15 | #define _libpy_STR(s) _libpy_XSTR(s)
16 |
17 | #define _libpy_MODULE_PATH(parent, name) _libpy_STR(parent) "." _libpy_STR(name)
18 |
19 | #define _libpy_XCAT(a, b) a##b
20 | #define _libpy_CAT(a, b) _libpy_XCAT(a, b)
21 |
22 | #define _libpy_MODINIT_NAME(name) _libpy_CAT(PyInit_, name)
23 | #define _libpy_MODULE_CREATE(path) PyModule_Create(&_libpy_module)
24 |
25 | /** Define a Python module.
26 |
27 | For example, to create a module `my_package.submodule.my_module` with two functions
28 | `f` and `g` and one type `T`:
29 |
30 | \code
31 | LIBPY_AUTOMODULE(my_package.submodule,
32 | my_module,
33 | ({py::autofunction("f"),
34 | py::autofunction("g")}))
35 | (py::borrowed_ref<> m) {
36 | py::borrowed_ref t = py::autoclass("T").new_().type();
37 | return PyObject_SetAttrString(m.get(), "T", static_cast(t));
38 | }
39 | \endcode
40 |
41 | @param parent A symbol indicating the parent module.
42 | @param name The leaf name of the module.
43 | @param methods `({...})` list of objects representing the functions to add to the
44 | module. Note this list must be surrounded by parentheses.
45 | */
46 | #define LIBPY_AUTOMODULE(parent, name, methods) \
47 | bool _libpy_user_mod_init(py::borrowed_ref<>); \
48 | PyMODINIT_FUNC _libpy_MODINIT_NAME(name)() LIBPY_EXPORT; \
49 | PyMODINIT_FUNC _libpy_MODINIT_NAME(name)() { \
50 | import_array(); \
51 | if (py::abi::ensure_compatible_libpy_abi()) { \
52 | return nullptr; \
53 | } \
54 | static std::vector ms methods; \
55 | ms.emplace_back(PyMethodDef({nullptr})); \
56 | static PyModuleDef _libpy_module{ \
57 | PyModuleDef_HEAD_INIT, \
58 | _libpy_MODULE_PATH(parent, name), \
59 | nullptr, \
60 | -1, \
61 | ms.data(), \
62 | }; \
63 | py::owned_ref m(_libpy_MODULE_CREATE(_libpy_MODULE_PATH(parent, name))); \
64 | if (!m) { \
65 | return nullptr; \
66 | } \
67 | try { \
68 | if (_libpy_user_mod_init(m)) { \
69 | return nullptr; \
70 | } \
71 | } \
72 | catch (const std::exception& e) { \
73 | py::raise_from_cxx_exception(e); \
74 | return nullptr; \
75 | } \
76 | catch (...) { \
77 | if (!PyErr_Occurred()) { \
78 | py::raise(PyExc_RuntimeError) << "an unknown C++ exception was raised"; \
79 | return nullptr; \
80 | } \
81 | } \
82 | return std::move(m).escape(); \
83 | } \
84 | bool _libpy_user_mod_init
85 |
--------------------------------------------------------------------------------
/include/libpy/borrowed_ref.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libpy/detail/python.h"
4 |
5 | namespace py {
6 | template
7 | class owned_ref;
8 |
9 | /** A type that explicitly indicates that a Python object is a borrowed
10 | reference. This is implicitly convertible from a regular `PyObject*` or a
11 | `py::owned_ref`. This type should be used to accept Python object parameters like:
12 |
13 | \code
14 | int f(py::borrowed_ref<> a, py::borrowed_ref<> b);
15 | \endcode
16 |
17 | This allows calling this function with a `py::owned_ref`,
18 | `PyObject*`, or a `py::borrowed_ref`.
19 |
20 | `py::borrowed_ref<>` should be used instead of `PyObject*` wherever possible to avoid
21 | ambiguity.
22 |
23 | @note A `borrowed_ref` may hold a value of `nullptr`.
24 | */
25 | template
26 | class borrowed_ref {
27 | private:
28 | T* m_ref;
29 |
30 | public:
31 | /** The type of the underlying pointer.
32 | */
33 | using element_type = T;
34 |
35 | /** Default construct a borrowed ref to a `nullptr`.
36 | */
37 | constexpr borrowed_ref() : m_ref(nullptr) {}
38 | constexpr borrowed_ref(std::nullptr_t) : m_ref(nullptr) {}
39 | constexpr borrowed_ref(T* ref) : m_ref(ref) {}
40 | constexpr borrowed_ref(const py::owned_ref& ref) : m_ref(ref.get()) {}
41 |
42 | constexpr borrowed_ref(const borrowed_ref&) = default;
43 | constexpr borrowed_ref& operator=(const borrowed_ref& ob) = default;
44 |
45 | /** Get the underlying pointer.
46 |
47 | @return The pointer managed by this `borrowed_ref`.
48 | */
49 | constexpr T* get() const {
50 | return m_ref;
51 | }
52 |
53 | explicit constexpr operator T*() const {
54 | return m_ref;
55 | }
56 |
57 | // use an enable_if to resolve the ambiguous dispatch when T is PyObject
58 | template::value>>
60 | explicit operator PyObject*() const {
61 | return reinterpret_cast(m_ref);
62 | }
63 |
64 | T& operator*() const {
65 | return *m_ref;
66 | }
67 |
68 | T* operator->() const {
69 | return m_ref;
70 | }
71 |
72 | explicit operator bool() const {
73 | return m_ref;
74 | }
75 |
76 | /** Object identity comparison.
77 |
78 | @return `get() == other.get()`.
79 | */
80 | bool operator==(borrowed_ref other) const {
81 | return get() == other.get();
82 | }
83 |
84 | /** Object identity comparison.
85 |
86 | @return `get() != other.get()`.
87 | */
88 | bool operator!=(borrowed_ref other) const {
89 | return get() != other.get();
90 | }
91 | };
92 | } // namespace py
93 |
--------------------------------------------------------------------------------
/include/libpy/buffer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "libpy/borrowed_ref.h"
8 | #include "libpy/demangle.h"
9 | #include "libpy/detail/api.h"
10 | #include "libpy/detail/python.h"
11 | #include "libpy/exception.h"
12 |
13 | namespace py {
14 | namespace detail {
15 | struct buffer_free {
16 | inline void operator()(Py_buffer* view) {
17 | if (view) {
18 | PyBuffer_Release(view);
19 | delete view;
20 | }
21 | }
22 | };
23 | } // namespace detail
24 |
25 | /** A smart pointer adapter for `Py_buffer` that ensures the underlying buffer is
26 | released.
27 | */
28 | using buffer = std::unique_ptr;
29 |
30 | enum buffer_format_code : char {};
31 |
32 | /** The Python buffor format character for the given type.
33 |
34 | If there is no corresponding buffer format character, this template evaluates to
35 | `'\0'`.
36 | */
37 | template
38 | inline constexpr buffer_format_code buffer_format{'\0'};
39 |
40 | template<>
41 | inline constexpr buffer_format_code buffer_format{'c'};
42 |
43 | template<>
44 | inline constexpr buffer_format_code buffer_format{'b'};
45 |
46 | template<>
47 | inline constexpr buffer_format_code buffer_format{'B'};
48 |
49 | template<>
50 | inline constexpr buffer_format_code buffer_format{'?'};
51 |
52 | template<>
53 | inline constexpr buffer_format_code buffer_format{'h'};
54 |
55 | template<>
56 | inline constexpr buffer_format_code buffer_format{'H'};
57 |
58 | template<>
59 | inline constexpr buffer_format_code buffer_format{'i'};
60 |
61 | template<>
62 | inline constexpr buffer_format_code buffer_format{'I'};
63 |
64 | template<>
65 | inline constexpr buffer_format_code buffer_format{'l'};
66 |
67 | template<>
68 | inline constexpr buffer_format_code buffer_format{'L'};
69 |
70 | template<>
71 | inline constexpr buffer_format_code buffer_format{'q'};
72 |
73 | template<>
74 | inline constexpr buffer_format_code buffer_format{'Q'};
75 |
76 | template<>
77 | inline constexpr buffer_format_code buffer_format{'f'};
78 |
79 | template<>
80 | inline constexpr buffer_format_code buffer_format{'d'};
81 |
82 | namespace detail {
83 | // clang-format off
84 | template
85 | constexpr bool buffer_format_compatible =
86 | (std::is_integral_v == std::is_integral_v &&
87 | std::is_signed_v == std::is_signed_v &&
88 | sizeof(A) == sizeof(B)) ||
89 | // special case for char and unsigned char; bytes objects report
90 | // that they are unsigned but we want to be able to read them into
91 | // std::string_view
92 | ((std::is_same_v && std::is_same_v) ||
93 | (std::is_same_v && std::is_same_v));
94 | // clang-format on
95 |
96 | using buffer_format_types = std::tuple;
110 |
111 | template
112 | struct buffer_compatible_format_chars;
113 |
114 | template
115 | struct buffer_compatible_format_chars> {
116 | using type = decltype(py::cs::cat(
117 | std::conditional_t,
118 | py::cs::char_sequence>,
119 | py::cs::char_sequence<>>{},
120 | typename buffer_compatible_format_chars>::type{}));
121 | };
122 |
123 | template
124 | struct buffer_compatible_format_chars> {
125 | using type = py::cs::char_sequence<>;
126 | };
127 | } // namespace detail
128 |
129 | /** Get a Python `Py_Buffer` from the given object.
130 |
131 | @param ob The object to read the buffer from.
132 | @flags The Python buffer request flags.
133 | @return buf A smart-pointer adapted `Py_Buffer`.
134 | @throws An exception is thrown if `ob` doesn't expose the buffer interface.
135 | */
136 | LIBPY_EXPORT py::buffer get_buffer(py::borrowed_ref<> ob, int flags);
137 |
138 | /** Check if a buffer format character is compatible with the given C++ type.
139 |
140 | @tparam T The type to check.
141 | @param fmt The Python buffer code.
142 | @return Is the buffer code compatible with `T`?
143 | */
144 | template
145 | bool buffer_type_compatible(buffer_format_code fmt) {
146 | auto arr = py::cs::to_array(
147 | typename detail::buffer_compatible_format_chars::
148 | type{});
149 | for (char c : arr) {
150 | if (fmt == c) {
151 | return true;
152 | }
153 | }
154 | return false;
155 | }
156 |
157 | /** Check if a buffer's type is compatible with the given C++ type.
158 |
159 | @tparam T The type to check.
160 | @param buf The buffer to check.
161 | @return Is the buffer code compatible with `T`?
162 | */
163 | template
164 | bool buffer_type_compatible(const py::buffer& buf) {
165 | if (!buf->format || std::strlen(buf->format) != 1) {
166 | return false;
167 | }
168 | return buffer_type_compatible(buffer_format_code{*buf->format});
169 | }
170 | } // namespace py
171 |
--------------------------------------------------------------------------------
/include/libpy/build_tuple.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libpy/owned_ref.h"
4 | #include "libpy/to_object.h"
5 |
6 | namespace py {
7 | /** Build a Python tuple from a variadic amount of arguments.
8 |
9 | All parameters are adapted using `py::to_object`.
10 |
11 | @param args The arguments to adapt into Python objects and pack into a tuple.
12 | @return A new Python tuple or `nullptr` with a Python exception set.
13 | @see py::to_object
14 | */
15 | template
16 | py::owned_ref<> build_tuple(const Args&... args) {
17 | py::owned_ref out(PyTuple_New(sizeof...(args)));
18 | if (!out) {
19 | return nullptr;
20 | }
21 |
22 | Py_ssize_t ix = 0;
23 | (PyTuple_SET_ITEM(out.get(), ix++, py::to_object(args).escape()), ...);
24 | for (ix = 0; ix < static_cast(sizeof...(args)); ++ix) {
25 | if (!PyTuple_GET_ITEM(out.get(), ix)) {
26 | return nullptr;
27 | }
28 | }
29 |
30 | return out;
31 | }
32 | } // namespace py
33 |
--------------------------------------------------------------------------------
/include/libpy/call_function.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "libpy/borrowed_ref.h"
9 | #include "libpy/build_tuple.h"
10 | #include "libpy/detail/python.h"
11 | #include "libpy/exception.h"
12 | #include "libpy/numpy_utils.h"
13 | #include "libpy/owned_ref.h"
14 | #include "libpy/to_object.h"
15 |
16 | namespace py {
17 | /** Call a python function on C++ data.
18 |
19 | @param function The function to call
20 | @param args The arguments to call it with, these will be adapted to
21 | temporary python objects.
22 | @return The result of the function call or nullptr if an error occurred.
23 | */
24 | template
25 | owned_ref<> call_function(py::borrowed_ref<> function, Args&&... args) {
26 | auto pyargs = py::build_tuple(std::forward(args)...);
27 | return owned_ref(PyObject_CallObject(function.get(), pyargs.get()));
28 | }
29 |
30 | /** Call a python function on C++ data.
31 |
32 | @param function The function to call
33 | @param args The arguments to call it with, these will be adapted to
34 | temporary python objects.
35 | @return The result of the function call. If the function throws a Python
36 | exception, a `py::exception` will be thrown.
37 | */
38 | template
39 | owned_ref<> call_function_throws(py::borrowed_ref<> function, Args&&... args) {
40 | auto pyargs = py::build_tuple(std::forward(args)...);
41 | owned_ref res(PyObject_CallObject(function.get(), pyargs.get()));
42 | if (!res) {
43 | throw py::exception{};
44 | }
45 | return res;
46 | }
47 |
48 | /** Call a python method on C++ data.
49 |
50 | @param ob The object to call the method on.
51 | @param method The method to call, this must be null-terminated.
52 | @param args The arguments to call it with, these will be adapted to
53 | temporary python objects.
54 | @return The result of the method call or nullptr if an error occurred.
55 | */
56 | template
57 | owned_ref<>
58 | call_method(py::borrowed_ref<> ob, const std::string& method, Args&&... args) {
59 | owned_ref bound_method(PyObject_GetAttrString(ob.get(), method.data()));
60 | if (!bound_method) {
61 | return nullptr;
62 | }
63 |
64 | return call_function(bound_method.get(), std::forward(args)...);
65 | }
66 |
67 | /** Call a python method on C++ data.
68 |
69 | @param ob The object to call the method on.
70 | @param method The method to call, this must be null-terminated.
71 | @param args The arguments to call it with, these will be adapted to
72 | temporary python objects.
73 | @return The result of the method call or nullptr. If the method throws a
74 | Python exception, a `py::exception` will be thrown.
75 | */
76 | template
77 | owned_ref<>
78 | call_method_throws(py::borrowed_ref<> ob, const std::string& method, Args&&... args) {
79 | owned_ref bound_method(PyObject_GetAttrString(ob.get(), method.data()));
80 | if (!bound_method) {
81 | throw py::exception{};
82 | }
83 |
84 | return call_function_throws(bound_method.get(), std::forward(args)...);
85 | }
86 | } // namespace py
87 |
--------------------------------------------------------------------------------
/include/libpy/char_sequence.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace py::cs {
8 | /** A compile time sequence of characters.
9 | */
10 | template
11 | using char_sequence = std::integer_sequence;
12 |
13 | inline namespace literals {
14 | /** User defined literal for creating a `py::cs::char_sequence` value.
15 |
16 | \code
17 | using a = py::cs::char_sequence<'a', 'b', 'c', 'd'>;
18 | using b = decltype("abcd"_cs);
19 |
20 | static_assert(std::is_same_v);
21 | \endcode
22 | */
23 | template
24 | constexpr char_sequence operator""_cs() {
25 | return {};
26 | }
27 |
28 | /** User defined literal for creating a `std::array` of characters.
29 |
30 | \code
31 | constexpr std::array a = {'a', 'b', 'c', 'd'};
32 | constexpr std::array b = "abcd"_arr;
33 |
34 | static_assert(a == b);
35 | \endcode
36 |
37 | @note This does not add a nul terminator to the array.
38 | */
39 | template
40 | constexpr std::array operator""_arr() {
41 | return {cs...};
42 | }
43 | }; // namespace literals
44 |
45 | namespace detail {
46 | template
47 | constexpr auto binary_cat(char_sequence, char_sequence) {
48 | return char_sequence{};
49 | }
50 | } // namespace detail
51 |
52 | /** Concatenate character sequences.
53 | */
54 | constexpr char_sequence<> cat() {
55 | return {};
56 | }
57 |
58 | /** Concatenate character sequences.
59 | */
60 | template
61 | constexpr auto cat(Cs cs) {
62 | return cs;
63 | }
64 |
65 | /** Concatenate character sequences.
66 | */
67 | template
68 | constexpr auto cat(Cs cs, Ds ds) {
69 | return detail::binary_cat(cs, ds);
70 | }
71 |
72 | /** Concatenate character sequences.
73 | */
74 | template
75 | constexpr auto cat(Cs cs, Ds ds, Ts... es) {
76 | return detail::binary_cat(detail::binary_cat(cs, ds), cat(es...));
77 | }
78 |
79 | /** Convert a character sequence into a `std::array` with a trailing null byte.
80 | */
81 | template
82 | constexpr auto to_array(char_sequence) {
83 | return std::array{cs..., '\0'};
84 | }
85 |
86 | namespace detail {
87 | template
88 | struct intersperse;
89 |
90 | // recursive base case
91 | template
92 | struct intersperse> {
93 | constexpr static char_sequence<> value{};
94 | };
95 |
96 | template
97 | struct intersperse> {
98 | constexpr static auto value = cat(char_sequence{},
99 | intersperse>::value);
100 | };
101 | }; // namespace detail
102 |
103 | /** Intersperse a character between all the characters of a `char_sequence`.
104 |
105 | @tparam c The character to intersperse into the sequence
106 | @param cs The sequence to intersperse the character into.
107 | */
108 | template
109 | constexpr auto intersperse(Cs) {
110 | return detail::intersperse::value;
111 | }
112 |
113 | namespace detail {
114 | template
115 | struct join;
116 |
117 | // recursive base case
118 | template
119 | struct join {
120 | constexpr static char_sequence<> value{};
121 | };
122 |
123 | template
124 | struct join, char_sequence, Tail...> {
125 | private:
126 | using joiner = char_sequence;
127 |
128 | public:
129 | constexpr static auto value =
130 | cs::cat(char_sequence{},
131 | std::conditional_t>{},
132 | join::value);
133 | };
134 | } // namespace detail
135 |
136 | /** Join a sequence of compile-time strings together with another compile-time
137 | string.
138 |
139 | This is like `joiner.join(cs)` in Python.
140 |
141 | @param joiner The string to join with.
142 | @param cs... The strings to join together.
143 | */
144 | template
145 | constexpr auto join(J, Cs...) {
146 | return detail::join::value;
147 | }
148 | } // namespace py::cs
149 |
--------------------------------------------------------------------------------
/include/libpy/demangle.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "libpy/detail/api.h"
10 |
11 | namespace py::util {
12 | LIBPY_BEGIN_EXPORT
13 | /** Exception raised when an invalid `py::demangle_string` call is performed.
14 | */
15 | class demangle_error : public std::exception {
16 | private:
17 | std::string m_msg;
18 |
19 | public:
20 | inline demangle_error(const std::string& msg) : m_msg(msg) {}
21 |
22 | inline const char* what() const noexcept override {
23 | return m_msg.data();
24 | }
25 | };
26 |
27 | class invalid_mangled_name : public demangle_error {
28 | public:
29 | inline invalid_mangled_name() : demangle_error("invalid mangled name") {}
30 | };
31 |
32 | /** Demangle the given string.
33 |
34 | @param cs The mangled symbol or type name.
35 | @return The demangled string.
36 | */
37 | std::string demangle_string(const char* cs);
38 |
39 | /** Demangle the given string.
40 |
41 | @param cs The mangled symbol or type name.
42 | @return The demangled string.
43 | */
44 | std::string demangle_string(const std::string& cs);
45 | LIBPY_END_EXPORT
46 |
47 | /** Get the name for a given type. If the demangled name cannot be given, returns the
48 | mangled name.
49 |
50 | @tparam T The type to get the name of.
51 | @return The type's name.
52 | */
53 | template
54 | std::string type_name() {
55 | const char* name = typeid(T).name();
56 | std::string out;
57 | try {
58 | out = demangle_string(name);
59 | }
60 | catch (const invalid_mangled_name&) {
61 | out = name;
62 | }
63 |
64 | if (std::is_lvalue_reference_v) {
65 | out.push_back('&');
66 | }
67 | else if (std::is_rvalue_reference_v) {
68 | out.insert(out.end(), 2, '&');
69 | }
70 |
71 | return out;
72 | }
73 | } // namespace py::util
74 |
--------------------------------------------------------------------------------
/include/libpy/detail/api.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | /** Marker for a single function or type to indicate that it should be exported in the
4 | shared object.
5 |
6 | # Examples
7 | ```
8 | LIBPY_EXPORT int public_function(float);
9 |
10 | struct LIBPY_EXPORT public_struct {};
11 | ```
12 |
13 | @note The visibility modifiers for types must appear between the initial keyword and
14 | the name of the type. Type visibility is applied only to "vague linkage entities"
15 | associated with the type. For example a vtable or typeinfo node. Public types do not
16 | automatically make all of their members public.
17 | */
18 | #define LIBPY_EXPORT __attribute__((visibility("default")))
19 |
20 | /** Marker for a single function or type to indicate that it should not be exported in the
21 | libpy shared object. The default visibility is *hidden*, so this can be used inside a
22 | `LIBPY_BEGIN_EXPORT/LIBPY_END_EXPORT` block to turn off only some types and functions.
23 |
24 | # Examples
25 | ```
26 | LIBPY_NO_EXPORT int hidden_function(float);
27 |
28 | struct LIBPY_NO_EXPORT hidden_struct {};
29 | ```
30 |
31 | @note The visibility modifiers for types must appear between the initial keyword and
32 | the name of the type. Type visibility is applied only to "vague linkage entities"
33 | associated with the type. For example a vtable or typeinfo node. Public types do not
34 | automatically make all of their members public.
35 | */
36 | #define LIBPY_NO_EXPORT __attribute__((visibility("hidden")))
37 |
38 | /** Temporarily change the default visibility to public.
39 |
40 | # Examples
41 | ```
42 | int hidden_function(float);
43 |
44 | LIBPY_BEGIN_EXPORT
45 | int public_function(float);
46 | LIBPY_END_EXPORT
47 |
48 | int another_hidden_function(float);
49 | ```
50 |
51 | @see LIBPY_END_EXPORT
52 | */
53 | #define LIBPY_BEGIN_EXPORT _Pragma("GCC visibility push(default)")
54 |
55 | /** Close the scope entered by `LIBPY_BEGIN_EXPORT`.
56 |
57 | @see LIBPY_BEGIN_EXPORT
58 | */
59 | #define LIBPY_END_EXPORT _Pragma("GCC visibility pop")
60 |
--------------------------------------------------------------------------------
/include/libpy/detail/autoclass_cache.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | /* This file is lifted out of `autoclass.h` because both `from_object.h` and
3 | `autoclass.h` need to read this cache, but `autoclass.h` depends on `from_object.h`.
4 | */
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "libpy/borrowed_ref.h"
12 | #include "libpy/detail/api.h"
13 | #include "libpy/detail/no_destruct_wrapper.h"
14 | #include "libpy/detail/python.h"
15 | #include "libpy/owned_ref.h"
16 |
17 | namespace py::detail {
18 | using unbox_fn = void* (*)(py::borrowed_ref<>);
19 | struct autoclass_storage {
20 | // Pointer to the function which handles unboxing objects of this type. The resulting
21 | // pointer can be safely cast to the static type of the contained object.
22 | unbox_fn unbox;
23 |
24 | // Borrowed reference to the type that this struct contains storage for.
25 | py::borrowed_ref type;
26 |
27 | // The method storage for `type`. We may use a vector because this is just a
28 | // collection of pointers and ints. `PyMethodDef` objects may move around until
29 | // we call `PyType_FromSpec`, at that point pointer will be taken to these objects
30 | // and we cannot relocate them.
31 | std::vector methods;
32 |
33 | // The storage for `type.tp_name`, and the method `name` and `doc` fields. This uses a
34 | // forward list because the `PyMethodDef` objects and `PyTypeObject` point to strings
35 | // in this list. Because of small buffer optimization (SBO), `std::string` does not
36 | // have reference stability on it's contents across a move. `std::forward_list` gives
37 | // us the reference stability that we need. We don't use `std::list` because we want
38 | // to limit the overhead of this structure.
39 | std::forward_list strings;
40 |
41 | // Storage for the `PyMethodDef` of the callback owned by `cleanup_wr`. This is not
42 | // in the `methods` array, because that array is passed as the `Py_tp_methods` slot
43 | // and will become the methods of the type. This is a free function.
44 | PyMethodDef callback_method;
45 |
46 | // A Python weakref that will delete this object from `autoclass_type_cache` when the
47 | // type dies.
48 | py::owned_ref<> cleanup_wr;
49 |
50 | // The Python base class for this type.
51 | py::owned_ref m_pybase;
52 |
53 | autoclass_storage() = default;
54 |
55 | autoclass_storage(unbox_fn unbox, std::string&& name)
56 | : unbox(unbox),
57 | type(nullptr),
58 | strings({std::move(name)}),
59 | callback_method({nullptr, nullptr, 0, nullptr}) {}
60 | };
61 |
62 | // A map from a C++ RTTI object to the Python class that was created to wrap it using
63 | // `py::autoclass`.
64 | LIBPY_EXPORT extern no_destruct_wrapper<
65 | std::unordered_map>>
66 | autoclass_type_cache;
67 | } // namespace py::detail
68 |
--------------------------------------------------------------------------------
/include/libpy/detail/autoclass_object.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libpy/borrowed_ref.h"
4 | #include "libpy/detail/python.h"
5 |
6 | namespace py::detail {
7 | template
8 | struct autoclass_object : public b {
9 | using base = b;
10 |
11 | T value;
12 |
13 | static T& unbox(py::borrowed_ref ob) {
14 | return static_cast(ob.get())->value;
15 | }
16 |
17 | template>>
18 | static T& unbox(py::borrowed_ref<> self) {
19 | return unbox(reinterpret_cast(self.get()));
20 | }
21 | };
22 |
23 | template
24 | struct autoclass_interface_object : public PyObject {
25 | using base = PyObject;
26 |
27 | I* virt_storage_ptr;
28 |
29 | static I& unbox(py::borrowed_ref<> ob) {
30 | return *std::launder(
31 | static_cast(ob.get())->virt_storage_ptr);
32 | }
33 | };
34 |
35 | template
36 | using autoclass_interface_instance_object =
37 | autoclass_object>;
38 | } // namespace py::detail
39 |
--------------------------------------------------------------------------------
/include/libpy/detail/no_destruct_wrapper.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace py::detail {
6 | /** A wrapper which prevents a destructor from being called. This is useful for
7 | static objects that hold `py::owned_ref` objects which may not be able
8 | to be cleaned up do to the interpreter state.
9 | */
10 | template
11 | class no_destruct_wrapper {
12 | public:
13 | /** Forward all arguments to the underlying object.
14 | */
15 | template
16 | no_destruct_wrapper(Args... args) {
17 | new (&m_storage) T(std::forward(args)...);
18 | }
19 |
20 | T& get() {
21 | return *std::launder(reinterpret_cast(&m_storage));
22 | }
23 |
24 | const T& get() const {
25 | return *std::launder(reinterpret_cast(&m_storage));
26 | }
27 |
28 | private:
29 | std::aligned_storage_t m_storage;
30 | };
31 | } // namespace libpy::detail
32 |
--------------------------------------------------------------------------------
/include/libpy/detail/numpy.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // Numpy expects code is being used in a way that has a 1:1 correspondence from source
4 | // file to Python extension (as a shared object), for example:
5 | //
6 | // // my_extension.c
7 | // #include arrayobject.h
8 | // ...
9 | // PyMODINIT_FUNC PyInit_myextension() {
10 | // import_array();
11 | // // Rest of module setup.
12 | // }
13 | //
14 | // Normally when writing a C++ library, header files provide declarations for functions
15 | // and types but not the definitions (except for templates and inline functions) and the
16 | // actual definition lives in one or more C++ files that get linked together into a single
17 | // shared object. Code that just wants to consume the library can include the files so
18 | // that the compiler knows the types and signatures provided by the library, but it
19 | // doesn't need to include the definitions in the consumer's compiled output. Instead,
20 | // when the object is loaded, it will also load the shared object and resolve all of the
21 | // symbols to the definitions in the shared object. This allows the implementation to
22 | // changed in backwards compatible ways and ensures that in a single process, all users of
23 | // the library have the same version. One downside is that the linker uses it's own path
24 | // resolution machinery to find the actual shared object file to use. Also, the resolution
25 | // happens when the shared object is loaded, no code can run before the symbols are
26 | // resolved. Numpy doesn't want users to need to deal with C/C++ linker stuff, and just
27 | // wants to be able to use the Python import system to find the numpy shared object(s). To
28 | // do this, numpy uses it's own linking system. The `numpy/arrayobject.h` will put a
29 | // `static void** PyArray_API = nullptr` name into *each* object that includes it. Many if
30 | // not all of the API functions in numpy are actually macros that resolve to something
31 | // like: `((PyArrayAPIObject*) PyArray_API)->function`. The `import_array()` macro will
32 | // import (through Python) the needed numpy extension modules to get the `PyArray_API` out
33 | // of a capsule-like object. `import_array()` is doing something very similar to what a
34 | // linker does, but without special compiler/linker assistance.
35 | //
36 | // This whole system works fine for when a single TU turns into a single object; however,
37 | // the test suite for libpy links all the test files together along with `main.cc` into a
38 | // single program. This has made it very hard to figure out when and how to initialize the
39 | // `PyArray_API` value. Instead, we now set a macro when compiling for the tests
40 | // (`LIBPY_COMPILING_FOR_TESTS`) which will control the `NO_IMPORT_ARRAY` flag. This flag
41 | // tells numpy to declare the `PyArray_API` flag as an `extern "C" void** PyArray_API`,
42 | // meaning we expect to have this symbol defined by another object we are to be linked
43 | // with. In `main.cc` we also set `LIBPY_TEST_MAIN` to disable `NO_IMPORT_ARRAY` which
44 | // causes changes the declaration of `PyArray_API` to change to: `#define PyArray_API
45 | // PY_ARRAY_UNIQUE_SYMBOL` and then `void** PyArray_API`. Importantly, this removes the
46 | // `static` and `extern` causing the symbol to have external linkage. Then, because the
47 | // tests are declaring the same symbol as extern, they will all resolve to the same
48 | // `PyArray_API` instance and we only need to call `import_array` once in `main.cc`.
49 | #if LIBPY_COMPILING_FOR_TESTS
50 | #define PY_ARRAY_UNIQUE_SYMBOL PyArray_API_libpy
51 | #ifndef LIBPY_TEST_MAIN
52 | #define NO_IMPORT_ARRAY
53 | #endif
54 | #endif
55 | #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
56 | #include "libpy/detail/python.h"
57 | #include
58 |
--------------------------------------------------------------------------------
/include/libpy/detail/python.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // NOTE: This is a relic of supporting both py2 and py3, but this provides
4 | // A standard place to find this header and make python version dependent
5 | // modifications if necessary.
6 | #include
7 |
--------------------------------------------------------------------------------
/include/libpy/dict_range.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "libpy/borrowed_ref.h"
6 | #include "libpy/detail/api.h"
7 | #include "libpy/detail/python.h"
8 | #include "libpy/exception.h"
9 |
10 | namespace py {
11 | LIBPY_BEGIN_EXPORT
12 | /** A range which iterates over the (key, value) pairs of a Python dictionary.
13 | */
14 | class dict_range {
15 | private:
16 | py::owned_ref<> m_map;
17 |
18 | class iterator {
19 | public:
20 | using value_type = std::pair, py::borrowed_ref<>>;
21 | using reference = value_type&;
22 |
23 | private:
24 | py::borrowed_ref<> m_map;
25 | Py_ssize_t m_pos;
26 | value_type m_item;
27 |
28 | public:
29 | inline iterator() : m_map(nullptr), m_pos(-1), m_item(nullptr, nullptr) {}
30 |
31 | explicit iterator(py::borrowed_ref<> map);
32 |
33 | iterator(const iterator&) = default;
34 | iterator& operator=(const iterator&) = default;
35 |
36 | reference operator*();
37 | value_type* operator->();
38 |
39 | iterator& operator++();
40 | iterator operator++(int);
41 |
42 | bool operator!=(const iterator& other) const;
43 | bool operator==(const iterator& other) const;
44 | };
45 |
46 | public:
47 | /** Create an object which iterates a Python dictionary as key, value pairs.
48 |
49 | @note This does not do a type check, `map` must be a Python dictionary.
50 | @param map The map to create a range over.
51 | */
52 | explicit dict_range(py::borrowed_ref<> map);
53 |
54 | /** Assert that `map` is a Python dictionary and then construct a
55 | `dict_range`.
56 |
57 | @param map The object to check and then make a view over.
58 | @return A new dict range.
59 | */
60 | static dict_range checked(py::borrowed_ref<> map);
61 |
62 | iterator begin() const;
63 | iterator end() const;
64 | };
65 | LIBPY_END_EXPORT
66 | } // namespace py
67 |
--------------------------------------------------------------------------------
/include/libpy/getattr.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "libpy/borrowed_ref.h"
6 | #include "libpy/exception.h"
7 | #include "libpy/owned_ref.h"
8 |
9 | namespace py {
10 | /** Look up an attribute on a Python object and return a new owning reference.
11 |
12 | @param ob The object to look up the attribute on.
13 | @param attr The name of the attribute to lookup.
14 | @return A new reference to `gettattr(ob, attr)` or `nullptr` with a Python
15 | exception set on failure.
16 | */
17 | inline py::owned_ref<> getattr(py::borrowed_ref<> ob, const std::string& attr) {
18 | return py::owned_ref{PyObject_GetAttrString(ob.get(), attr.data())};
19 | }
20 |
21 | /** Look up an attribute on a Python object and return a new owning reference.
22 |
23 | @param ob The object to look up the attribute on.
24 | @param attr The name of the attribute to lookup.
25 | @return A new reference to `gettattr(ob, attr)`. If the attribute doesn't
26 | exist, a `py::exception` will be thrown.
27 | */
28 | inline py::owned_ref<> getattr_throws(py::borrowed_ref<> ob, const std::string& attr) {
29 | PyObject* res = PyObject_GetAttrString(ob.get(), attr.data());
30 | if (!res) {
31 | throw py::exception{};
32 | }
33 | return py::owned_ref{res};
34 | }
35 |
36 | inline py::owned_ref<> nested_getattr(py::borrowed_ref<> ob) {
37 | return py::owned_ref<>::new_reference(ob);
38 | }
39 |
40 | /** Perform nested getattr calls with intermediate error checking.
41 |
42 | @param ob The root object to look up the attribute on.
43 | @param attrs The name of the attributes to lookup.
44 | @return A new reference to `getattr(gettattr(ob, attrs[0]), attrs[1]), ...`
45 | or `nullptr` with a Python exception set on failure.
46 | */
47 | template
48 | py::owned_ref<> nested_getattr(py::borrowed_ref<> ob, const T& head, const Ts&... tail) {
49 | py::owned_ref<> result = getattr(ob, head);
50 | if (!result) {
51 | return nullptr;
52 | }
53 | return nested_getattr(result, tail...);
54 | }
55 |
56 | /** Perform nested getattr calls with intermediate error checking.
57 |
58 | @param ob The root object to look up the attribute on.
59 | @param attrs The name of the attributes to lookup.
60 | @return A new reference to `getattr(gettattr(ob, attrs[0]), attrs[1]), ...`.
61 | If an attribute in the chain doesn't exist, a `py::exception` will
62 | be thrown.
63 | */
64 | template
65 | py::owned_ref<> nested_getattr_throws(const Ts&... args) {
66 | auto res = nested_getattr(args...);
67 | if (!res) {
68 | throw py::exception{};
69 | }
70 | return res;
71 | }
72 | } // namespace py
73 |
--------------------------------------------------------------------------------
/include/libpy/gil.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "libpy/detail/api.h"
4 | #include "libpy/detail/python.h"
5 |
6 | namespace py {
7 | LIBPY_BEGIN_EXPORT
8 | /** A wrapper around the threadstate.
9 | */
10 | struct gil final {
11 | private:
12 | LIBPY_EXPORT static thread_local PyThreadState* m_save;
13 |
14 | public:
15 | gil() = delete;
16 |
17 | /** Release the GIL. The GIL must be held.
18 |
19 | @note `release` is a low-level utility. Please see `release_block` for
20 | a safer alternative.
21 | */
22 | static inline void release() {
23 | m_save = PyEval_SaveThread();
24 | }
25 |
26 | /** Acquire the GIL. The GIL must not be held.
27 |
28 | @note `acquire` is a low-level utility. Please see `hold_block` for
29 | a safer alternative.
30 | */
31 | static inline void acquire() {
32 | PyEval_RestoreThread(m_save);
33 | m_save = nullptr;
34 | }
35 |
36 | /** Release the GIL if it is not already released.
37 |
38 | @note `ensure_released` is a low-level utility. Please see
39 | `release_block` for a safer alternative.
40 | */
41 | static inline void ensure_released() {
42 | if (held()) {
43 | release();
44 | }
45 | }
46 |
47 | /** Acquire the GIL if we do not already hold it.
48 |
49 | @note `ensure_acquired` is a low-level utility. Please see `hold_block`
50 | for a safer alternative.
51 | */
52 | static inline void ensure_acquired() {
53 | if (!held()) {
54 | acquire();
55 | }
56 | }
57 |
58 | /** Check if the gil is currently held.
59 | */
60 | static inline bool held() {
61 | return PyGILState_Check();
62 | }
63 |
64 | /** RAII resource for ensuring that the gil is released in a given block.
65 |
66 | For example:
67 |
68 | \code
69 | // the gil may or may not be released here
70 | {
71 | py::gil::release_block released;
72 | // the gil is now definitely released
73 | }
74 | // the gil may or may not be released here
75 | \endcode
76 | */
77 | struct release_block final {
78 | private:
79 | bool m_acquire;
80 |
81 | public:
82 | inline release_block() : m_acquire(gil::held()) {
83 | gil::ensure_released();
84 | }
85 |
86 | /** Reset this gil back to the state it was in when this object was created.
87 | */
88 | inline void dismiss() {
89 | if (m_acquire) {
90 | gil::acquire();
91 | m_acquire = false;
92 | }
93 | }
94 |
95 | inline ~release_block() {
96 | dismiss();
97 | }
98 | };
99 |
100 | /** RAII resource for ensuring that the gil is held in a given block.
101 |
102 | For example:
103 |
104 | \code
105 | // the gil may or may not be held here
106 | {
107 | py::gil::hold_block held;
108 | // the gil is now definitely held
109 | }
110 | // the gil may or may not be held here
111 | \endcode
112 | */
113 | struct hold_block final {
114 | private:
115 | bool m_release;
116 |
117 | public:
118 | inline hold_block() : m_release(!gil::held()) {
119 | gil::ensure_acquired();
120 | }
121 |
122 | /** Reset this gil back to the state it was in when this object was created.
123 | */
124 | inline void dismiss() {
125 | if (m_release) {
126 | gil::release();
127 | m_release = false;
128 | }
129 | }
130 |
131 | inline ~hold_block() {
132 | dismiss();
133 | }
134 | };
135 | };
136 | LIBPY_END_EXPORT
137 | } // namespace py
138 |
--------------------------------------------------------------------------------
/include/libpy/hash.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace py {
8 | namespace detail {
9 | /** Combine two hashes into one. This algorithm is taken from `boost`.
10 | */
11 | template
12 | T bin_hash_combine(T hash_1, T hash_2) {
13 | return hash_1 ^ (hash_2 + 0x9e3779b9 + (hash_1 << 6) + (hash_1 >> 2));
14 | }
15 | } // namespace detail
16 |
17 | /** Combine two or more hashes into one.
18 | */
19 | template
20 | auto hash_combine(T head, Ts... tail) {
21 | ((head = detail::bin_hash_combine(head, tail)), ...);
22 | return head;
23 | }
24 |
25 | /** Hash multiple values by `hash_combine`ing them together.
26 | */
27 | template
28 | auto hash_many(const Ts&... vs) {
29 | return hash_combine(std::hash{}(vs)...);
30 | }
31 |
32 | /** Hash a tuple by `hash_many`ing all the values together.
33 | */
34 | template
35 | auto hash_tuple(const std::tuple& t) {
36 | return std::apply)>(hash_many, t);
37 | }
38 |
39 | /** Hash a buffer of characters using the same algorithm as
40 | `std::hash`
41 |
42 | @param buf The buffer to hash.
43 | @param len The length of the buffer.
44 | @return The hash of the string.
45 | */
46 | inline std::size_t hash_buffer(const char* buf, std::size_t len) {
47 | return std::hash{}(std::string_view{buf, len});
48 | }
49 | } // namespace py
50 |
--------------------------------------------------------------------------------
/include/libpy/library_wrappers/sparsehash.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "libpy/to_object.h"
11 |
12 | namespace py {
13 | /** A wrapper around `google::dense_hash_map` which uses `std::hash` instead of
14 | `tr1::hash` and requires an empty key at construction time.
15 |
16 | @tparam Key The key type.
17 | @tparam T The value type.
18 | @tparam HashFcn The key hash functor type.
19 | @tparam Alloc The allocator object to use. In general, don't change this.
20 | */
21 | template, // change the default to std::hash
24 | typename EqualKey = std::equal_to,
25 | typename Alloc = google::libc_allocator_with_realloc>>
26 | struct dense_hash_map : public google::dense_hash_map {
27 | private:
28 | using base = google::dense_hash_map;
29 |
30 | public:
31 | dense_hash_map() = delete; // User must give a missing value.
32 |
33 | /**
34 | @param empty_key An element of type `Key` which denotes an empty slot.
35 | This value can not itself be used as a valid key.
36 | @param expected_size A size hint for the map.
37 | */
38 | dense_hash_map(const Key& empty_key, std::size_t expected_size = 0)
39 | : base(expected_size) {
40 | if (empty_key != empty_key) {
41 | // the first insert will hang forever if `empty_key != empty_key`
42 | throw std::invalid_argument{"dense_hash_map: empty_key != empty_key"};
43 | }
44 | this->set_empty_key(empty_key);
45 | }
46 |
47 | dense_hash_map(const dense_hash_map& cpfrom) : base(cpfrom) {}
48 |
49 | dense_hash_map(dense_hash_map&& mvfrom) noexcept {
50 | this->swap(mvfrom);
51 | }
52 |
53 | dense_hash_map& operator=(const dense_hash_map& cpfrom) {
54 | base::operator=(cpfrom);
55 | return *this;
56 | }
57 |
58 | dense_hash_map& operator=(dense_hash_map&& mvfrom) noexcept {
59 | this->swap(mvfrom);
60 | return *this;
61 | }
62 | };
63 |
64 | /** A wrapper around `google::sparse_hash_map` which uses `std::hash` instead of
65 | `tr1::hash` and requires an empty key at construction time.
66 |
67 | @tparam Key The key type.
68 | @tparam T The value type.
69 | @tparam HashFcn The key hash functor type.
70 | @tparam Alloc The allocator object to use. In general, don't change this.
71 | */
72 | template, // change the default to std::hash
75 | typename EqualKey = std::equal_to,
76 | typename Alloc = google::libc_allocator_with_realloc>>
77 | struct sparse_hash_map
78 | : public google::sparse_hash_map {
79 | private:
80 | using base = google::sparse_hash_map;
81 |
82 | public:
83 | using base::sparse_hash_map;
84 |
85 | sparse_hash_map(const sparse_hash_map& cpfrom) : base(cpfrom) {}
86 |
87 | sparse_hash_map(sparse_hash_map&& mvfrom) noexcept {
88 | this->swap(mvfrom);
89 | }
90 |
91 | sparse_hash_map& operator=(const sparse_hash_map& cpfrom) {
92 | base::operator=(cpfrom);
93 | return *this;
94 | }
95 |
96 | sparse_hash_map& operator=(sparse_hash_map&& mvfrom) noexcept {
97 | this->swap(mvfrom);
98 | return *this;
99 | }
100 | };
101 |
102 | template,
104 | typename EqualKey = std::equal_to,
105 | typename Alloc = google::libc_allocator_with_realloc>
106 | struct dense_hash_set : public google::dense_hash_set {
107 | private:
108 | using base = google::dense_hash_set