mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 21:25:13 +00:00
Merge branch 'master' into sh_merge_master
This commit is contained in:
commit
a655f95a83
@ -3,6 +3,7 @@ FormatStyle: file
|
|||||||
Checks: '
|
Checks: '
|
||||||
*bugprone*,
|
*bugprone*,
|
||||||
cppcoreguidelines-init-variables,
|
cppcoreguidelines-init-variables,
|
||||||
|
cppcoreguidelines-slicing,
|
||||||
clang-analyzer-optin.cplusplus.VirtualCall,
|
clang-analyzer-optin.cplusplus.VirtualCall,
|
||||||
llvm-namespace-comment,
|
llvm-namespace-comment,
|
||||||
misc-misplaced-const,
|
misc-misplaced-const,
|
||||||
|
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@ -68,8 +68,8 @@ nox -l
|
|||||||
# Run linters
|
# Run linters
|
||||||
nox -s lint
|
nox -s lint
|
||||||
|
|
||||||
# Run tests
|
# Run tests on Python 3.9
|
||||||
nox -s tests
|
nox -s tests-3.9
|
||||||
|
|
||||||
# Build and preview docs
|
# Build and preview docs
|
||||||
nox -s docs -- serve
|
nox -s docs -- serve
|
||||||
|
81
.github/workflows/ci.yml
vendored
81
.github/workflows/ci.yml
vendored
@ -862,32 +862,85 @@ jobs:
|
|||||||
run: cmake --build build -t check
|
run: cmake --build build -t check
|
||||||
|
|
||||||
mingw:
|
mingw:
|
||||||
|
name: "🐍 3 • windows-latest • ${{ matrix.sys }}"
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { sys: mingw64, env: x86_64 }
|
||||||
|
- { sys: mingw32, env: i686 }
|
||||||
steps:
|
steps:
|
||||||
- uses: msys2/setup-msys2@v2
|
- uses: msys2/setup-msys2@v2
|
||||||
with:
|
with:
|
||||||
|
msystem: ${{matrix.sys}}
|
||||||
install: >-
|
install: >-
|
||||||
mingw-w64-x86_64-gcc
|
git
|
||||||
mingw-w64-x86_64-python-pip
|
mingw-w64-${{matrix.env}}-gcc
|
||||||
mingw-w64-x86_64-cmake
|
mingw-w64-${{matrix.env}}-python-pip
|
||||||
mingw-w64-x86_64-make
|
mingw-w64-${{matrix.env}}-python-numpy
|
||||||
mingw-w64-x86_64-python-pytest
|
mingw-w64-${{matrix.env}}-python-scipy
|
||||||
mingw-w64-x86_64-eigen3
|
mingw-w64-${{matrix.env}}-cmake
|
||||||
mingw-w64-x86_64-boost
|
mingw-w64-${{matrix.env}}-make
|
||||||
mingw-w64-x86_64-catch
|
mingw-w64-${{matrix.env}}-python-pytest
|
||||||
|
mingw-w64-${{matrix.env}}-eigen3
|
||||||
|
mingw-w64-${{matrix.env}}-boost
|
||||||
|
mingw-w64-${{matrix.env}}-catch
|
||||||
|
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure C++11
|
||||||
# LTO leads to many undefined reference like
|
# LTO leads to many undefined reference like
|
||||||
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
|
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
|
||||||
run: cmake -G "MinGW Makefiles" -S . -B build
|
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -S . -B build
|
||||||
|
|
||||||
- name: Build
|
- name: Build C++11
|
||||||
run: cmake --build build -j 2
|
run: cmake --build build -j 2
|
||||||
|
|
||||||
- name: Python tests
|
- name: Python tests C++11
|
||||||
run: cmake --build build --target pytest
|
run: cmake --build build --target pytest -j 2
|
||||||
|
|
||||||
|
- name: C++11 tests
|
||||||
|
run: cmake --build build --target cpptest -j 2
|
||||||
|
|
||||||
|
- name: Interface test C++11
|
||||||
|
run: cmake --build build --target test_cmake_build
|
||||||
|
|
||||||
|
- name: Clean directory
|
||||||
|
run: git clean -fdx
|
||||||
|
|
||||||
|
- name: Configure C++14
|
||||||
|
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -S . -B build2
|
||||||
|
|
||||||
|
- name: Build C++14
|
||||||
|
run: cmake --build build2 -j 2
|
||||||
|
|
||||||
|
- name: Python tests C++14
|
||||||
|
run: cmake --build build2 --target pytest -j 2
|
||||||
|
|
||||||
|
- name: C++14 tests
|
||||||
|
run: cmake --build build2 --target cpptest -j 2
|
||||||
|
|
||||||
|
- name: Interface test C++14
|
||||||
|
run: cmake --build build2 --target test_cmake_build
|
||||||
|
|
||||||
|
- name: Clean directory
|
||||||
|
run: git clean -fdx
|
||||||
|
|
||||||
|
- name: Configure C++17
|
||||||
|
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -S . -B build3
|
||||||
|
|
||||||
|
- name: Build C++17
|
||||||
|
run: cmake --build build3 -j 2
|
||||||
|
|
||||||
|
- name: Python tests C++17
|
||||||
|
run: cmake --build build3 --target pytest -j 2
|
||||||
|
|
||||||
|
- name: C++17 tests
|
||||||
|
run: cmake --build build3 --target cpptest -j 2
|
||||||
|
|
||||||
|
- name: Interface test C++17
|
||||||
|
run: cmake --build build3 --target test_cmake_build
|
||||||
|
@ -32,7 +32,7 @@ repos:
|
|||||||
exclude: ^noxfile.py$
|
exclude: ^noxfile.py$
|
||||||
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.23.3
|
rev: v2.24.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
|
|
||||||
|
@ -138,9 +138,9 @@ About
|
|||||||
This project was created by `Wenzel
|
This project was created by `Wenzel
|
||||||
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
|
Jakob <http://rgl.epfl.ch/people/wjakob>`_. Significant features and/or
|
||||||
improvements to the code were contributed by Jonas Adler, Lori A. Burns,
|
improvements to the code were contributed by Jonas Adler, Lori A. Burns,
|
||||||
Sylvain Corlay, Eric Cousineau, Ralf Grosse-Kunstleve, Trent Houliston, Axel
|
Sylvain Corlay, Eric Cousineau, Aaron Gokaslan, Ralf Grosse-Kunstleve, Trent Houliston, Axel
|
||||||
Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov Johan Mabille, Tomasz Miąsko,
|
Huebl, @hulucc, Yannick Jadoul, Sergey Lyskov Johan Mabille, Tomasz Miąsko,
|
||||||
Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim
|
Dean Moldovan, Ben Pritchard, Jason Rhinelander, Boris Schäling, Pim
|
||||||
Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart.
|
Schellart, Henry Schreiner, Ivan Smirnov, Boris Staletic, and Patrick Stewart.
|
||||||
|
|
||||||
We thank Google for a generous financial contribution to the continuous
|
We thank Google for a generous financial contribution to the continuous
|
||||||
|
@ -323,6 +323,34 @@ Alternately, to ignore the error, call `PyErr_Clear
|
|||||||
Any Python error must be thrown or cleared, or Python/pybind11 will be left in
|
Any Python error must be thrown or cleared, or Python/pybind11 will be left in
|
||||||
an invalid state.
|
an invalid state.
|
||||||
|
|
||||||
|
Chaining exceptions ('raise from')
|
||||||
|
==================================
|
||||||
|
|
||||||
|
In Python 3.3 a mechanism for indicating that exceptions were caused by other
|
||||||
|
exceptions was introduced:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(1 / 0)
|
||||||
|
except Exception as exc:
|
||||||
|
raise RuntimeError("could not divide by zero") from exc
|
||||||
|
|
||||||
|
To do a similar thing in pybind11, you can use the ``py::raise_from`` function. It
|
||||||
|
sets the current python error indicator, so to continue propagating the exception
|
||||||
|
you should ``throw py::error_already_set()`` (Python 3 only).
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
try {
|
||||||
|
py::eval("print(1 / 0"));
|
||||||
|
} catch (py::error_already_set &e) {
|
||||||
|
py::raise_from(e, PyExc_RuntimeError, "could not divide by zero");
|
||||||
|
throw py::error_already_set();
|
||||||
|
}
|
||||||
|
|
||||||
|
.. versionadded:: 2.8
|
||||||
|
|
||||||
.. _unraisable_exceptions:
|
.. _unraisable_exceptions:
|
||||||
|
|
||||||
Handling unraisable exceptions
|
Handling unraisable exceptions
|
||||||
|
@ -20,6 +20,47 @@ Available types include :class:`handle`, :class:`object`, :class:`bool_`,
|
|||||||
Be sure to review the :ref:`pytypes_gotchas` before using this heavily in
|
Be sure to review the :ref:`pytypes_gotchas` before using this heavily in
|
||||||
your C++ API.
|
your C++ API.
|
||||||
|
|
||||||
|
.. _instantiating_compound_types:
|
||||||
|
|
||||||
|
Instantiating compound Python types from C++
|
||||||
|
============================================
|
||||||
|
|
||||||
|
Dictionaries can be initialized in the :class:`dict` constructor:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
using namespace pybind11::literals; // to bring in the `_a` literal
|
||||||
|
py::dict d("spam"_a=py::none(), "eggs"_a=42);
|
||||||
|
|
||||||
|
A tuple of python objects can be instantiated using :func:`py::make_tuple`:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
py::tuple tup = py::make_tuple(42, py::none(), "spam");
|
||||||
|
|
||||||
|
Each element is converted to a supported Python type.
|
||||||
|
|
||||||
|
A `simple namespace`_ can be instantiated using
|
||||||
|
:func:`py::make_simple_namespace`:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
using namespace pybind11::literals; // to bring in the `_a` literal
|
||||||
|
py::object ns = py::make_simple_namespace("spam"_a=py::none(), "eggs"_a=42);
|
||||||
|
|
||||||
|
Attributes on a namespace can be modified with the :func:`py::delattr`,
|
||||||
|
:func:`py::getattr`, and :func:`py::setattr` functions. Simple namespaces can
|
||||||
|
be useful as lightweight stand-ins for class instances.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``make_simple_namespace`` is not available in Python 2.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
``make_simple_namespace`` added.
|
||||||
|
|
||||||
|
.. _simple namespace: https://docs.python.org/3/library/types.html#types.SimpleNamespace
|
||||||
|
|
||||||
.. _casting_back_and_forth:
|
.. _casting_back_and_forth:
|
||||||
|
|
||||||
Casting back and forth
|
Casting back and forth
|
||||||
@ -30,7 +71,7 @@ types to Python, which can be done using :func:`py::cast`:
|
|||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
MyClass *cls = ..;
|
MyClass *cls = ...;
|
||||||
py::object obj = py::cast(cls);
|
py::object obj = py::cast(cls);
|
||||||
|
|
||||||
The reverse direction uses the following syntax:
|
The reverse direction uses the following syntax:
|
||||||
|
@ -1045,6 +1045,16 @@ template <return_value_policy policy = return_value_policy::automatic_reference,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
template <typename... Args,
|
||||||
|
typename = detail::enable_if_t<args_are_all_keyword_or_ds<Args...>()>>
|
||||||
|
object make_simple_namespace(Args&&... args_) {
|
||||||
|
PyObject *ns = _PyNamespace_New(dict(std::forward<Args>(args_)...).ptr());
|
||||||
|
if (!ns) throw error_already_set();
|
||||||
|
return reinterpret_steal<object>(ns);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// \ingroup annotations
|
/// \ingroup annotations
|
||||||
/// Annotation for arguments
|
/// Annotation for arguments
|
||||||
struct arg {
|
struct arg {
|
||||||
|
@ -315,6 +315,19 @@ extern "C" {
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
|
||||||
|
#define PYBIND11_CATCH_INIT_EXCEPTIONS \
|
||||||
|
catch (pybind11::error_already_set &e) { \
|
||||||
|
pybind11::raise_from(e, PyExc_ImportError, "initialization failed"); \
|
||||||
|
return nullptr; \
|
||||||
|
} catch (const std::exception &e) { \
|
||||||
|
PyErr_SetString(PyExc_ImportError, e.what()); \
|
||||||
|
return nullptr; \
|
||||||
|
} \
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
#define PYBIND11_CATCH_INIT_EXCEPTIONS \
|
#define PYBIND11_CATCH_INIT_EXCEPTIONS \
|
||||||
catch (pybind11::error_already_set &e) { \
|
catch (pybind11::error_already_set &e) { \
|
||||||
PyErr_SetString(PyExc_ImportError, e.what()); \
|
PyErr_SetString(PyExc_ImportError, e.what()); \
|
||||||
@ -324,6 +337,8 @@ extern "C" {
|
|||||||
return nullptr; \
|
return nullptr; \
|
||||||
} \
|
} \
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
***Deprecated in favor of PYBIND11_MODULE***
|
***Deprecated in favor of PYBIND11_MODULE***
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
#include "pybind11.h"
|
#include "pybind11.h"
|
||||||
#include "eval.h"
|
#include "eval.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#if defined(PYPY_VERSION)
|
#if defined(PYPY_VERSION)
|
||||||
# error Embedding the interpreter is not supported with PyPy
|
# error Embedding the interpreter is not supported with PyPy
|
||||||
#endif
|
#endif
|
||||||
@ -83,29 +86,106 @@ struct embedded_module {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wide_char_arg_deleter {
|
||||||
|
void operator()(wchar_t *ptr) const {
|
||||||
|
#if PY_VERSION_HEX >= 0x030500f0
|
||||||
|
// API docs: https://docs.python.org/3/c-api/sys.html#c.Py_DecodeLocale
|
||||||
|
PyMem_RawFree(ptr);
|
||||||
|
#else
|
||||||
|
delete[] ptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline wchar_t *widen_chars(const char *safe_arg) {
|
||||||
|
#if PY_VERSION_HEX >= 0x030500f0
|
||||||
|
wchar_t *widened_arg = Py_DecodeLocale(safe_arg, nullptr);
|
||||||
|
#else
|
||||||
|
wchar_t *widened_arg = nullptr;
|
||||||
|
# if defined(HAVE_BROKEN_MBSTOWCS) && HAVE_BROKEN_MBSTOWCS
|
||||||
|
size_t count = strlen(safe_arg);
|
||||||
|
# else
|
||||||
|
size_t count = mbstowcs(nullptr, safe_arg, 0);
|
||||||
|
# endif
|
||||||
|
if (count != static_cast<size_t>(-1)) {
|
||||||
|
widened_arg = new wchar_t[count + 1];
|
||||||
|
mbstowcs(widened_arg, safe_arg, count + 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return widened_arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Python 2.x/3.x-compatible version of `PySys_SetArgv`
|
||||||
|
inline void set_interpreter_argv(int argc, const char *const *argv, bool add_program_dir_to_path) {
|
||||||
|
// Before it was special-cased in python 3.8, passing an empty or null argv
|
||||||
|
// caused a segfault, so we have to reimplement the special case ourselves.
|
||||||
|
bool special_case = (argv == nullptr || argc <= 0);
|
||||||
|
|
||||||
|
const char *const empty_argv[]{"\0"};
|
||||||
|
const char *const *safe_argv = special_case ? empty_argv : argv;
|
||||||
|
if (special_case)
|
||||||
|
argc = 1;
|
||||||
|
|
||||||
|
auto argv_size = static_cast<size_t>(argc);
|
||||||
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
// SetArgv* on python 3 takes wchar_t, so we have to convert.
|
||||||
|
std::unique_ptr<wchar_t *[]> widened_argv(new wchar_t *[argv_size]);
|
||||||
|
std::vector<std::unique_ptr<wchar_t[], wide_char_arg_deleter>> widened_argv_entries;
|
||||||
|
widened_argv_entries.reserve(argv_size);
|
||||||
|
for (size_t ii = 0; ii < argv_size; ++ii) {
|
||||||
|
widened_argv_entries.emplace_back(widen_chars(safe_argv[ii]));
|
||||||
|
if (!widened_argv_entries.back()) {
|
||||||
|
// A null here indicates a character-encoding failure or the python
|
||||||
|
// interpreter out of memory. Give up.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widened_argv[ii] = widened_argv_entries.back().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pysys_argv = widened_argv.get();
|
||||||
|
#else
|
||||||
|
// python 2.x
|
||||||
|
std::vector<std::string> strings{safe_argv, safe_argv + argv_size};
|
||||||
|
std::vector<char *> char_strings{argv_size};
|
||||||
|
for (std::size_t i = 0; i < argv_size; ++i)
|
||||||
|
char_strings[i] = &strings[i][0];
|
||||||
|
char **pysys_argv = char_strings.data();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path));
|
||||||
|
}
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_END(detail)
|
PYBIND11_NAMESPACE_END(detail)
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
|
Initialize the Python interpreter. No other pybind11 or CPython API functions can be
|
||||||
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
|
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The
|
||||||
optional parameter can be used to skip the registration of signal handlers (see the
|
optional `init_signal_handlers` parameter can be used to skip the registration of
|
||||||
`Python documentation`_ for details). Calling this function again after the interpreter
|
signal handlers (see the `Python documentation`_ for details). Calling this function
|
||||||
has already been initialized is a fatal error.
|
again after the interpreter has already been initialized is a fatal error.
|
||||||
|
|
||||||
If initializing the Python interpreter fails, then the program is terminated. (This
|
If initializing the Python interpreter fails, then the program is terminated. (This
|
||||||
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
|
is controlled by the CPython runtime and is an exception to pybind11's normal behavior
|
||||||
of throwing exceptions on errors.)
|
of throwing exceptions on errors.)
|
||||||
|
|
||||||
|
The remaining optional parameters, `argc`, `argv`, and `add_program_dir_to_path` are
|
||||||
|
used to populate ``sys.argv`` and ``sys.path``.
|
||||||
|
See the |PySys_SetArgvEx documentation|_ for details.
|
||||||
|
|
||||||
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
|
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx
|
||||||
|
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
|
||||||
|
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
|
||||||
\endrst */
|
\endrst */
|
||||||
inline void initialize_interpreter(bool init_signal_handlers = true) {
|
inline void initialize_interpreter(bool init_signal_handlers = true,
|
||||||
|
int argc = 0,
|
||||||
|
const char *const *argv = nullptr,
|
||||||
|
bool add_program_dir_to_path = true) {
|
||||||
if (Py_IsInitialized() != 0)
|
if (Py_IsInitialized() != 0)
|
||||||
pybind11_fail("The interpreter is already running");
|
pybind11_fail("The interpreter is already running");
|
||||||
|
|
||||||
Py_InitializeEx(init_signal_handlers ? 1 : 0);
|
Py_InitializeEx(init_signal_handlers ? 1 : 0);
|
||||||
|
|
||||||
// Make .py files in the working directory available by default
|
detail::set_interpreter_argv(argc, argv, add_program_dir_to_path);
|
||||||
module_::import("sys").attr("path").cast<list>().append(".");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** \rst
|
/** \rst
|
||||||
@ -167,6 +247,8 @@ inline void finalize_interpreter() {
|
|||||||
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
|
Scope guard version of `initialize_interpreter` and `finalize_interpreter`.
|
||||||
This a move-only guard and only a single instance can exist.
|
This a move-only guard and only a single instance can exist.
|
||||||
|
|
||||||
|
See `initialize_interpreter` for a discussion of its constructor arguments.
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
#include <pybind11/embed.h>
|
#include <pybind11/embed.h>
|
||||||
@ -178,8 +260,11 @@ inline void finalize_interpreter() {
|
|||||||
\endrst */
|
\endrst */
|
||||||
class scoped_interpreter {
|
class scoped_interpreter {
|
||||||
public:
|
public:
|
||||||
scoped_interpreter(bool init_signal_handlers = true) {
|
scoped_interpreter(bool init_signal_handlers = true,
|
||||||
initialize_interpreter(init_signal_handlers);
|
int argc = 0,
|
||||||
|
const char *const *argv = nullptr,
|
||||||
|
bool add_program_dir_to_path = true) {
|
||||||
|
initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
scoped_interpreter(const scoped_interpreter &) = delete;
|
scoped_interpreter(const scoped_interpreter &) = delete;
|
||||||
|
@ -198,6 +198,9 @@ struct npy_api {
|
|||||||
// Unused. Not removed because that affects ABI of the class.
|
// Unused. Not removed because that affects ABI of the class.
|
||||||
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *);
|
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *);
|
||||||
PyObject* (*PyArray_Resize_)(PyObject*, PyArray_Dims*, int, int);
|
PyObject* (*PyArray_Resize_)(PyObject*, PyArray_Dims*, int, int);
|
||||||
|
PyObject* (*PyArray_Newshape_)(PyObject*, PyArray_Dims*, int);
|
||||||
|
PyObject* (*PyArray_View_)(PyObject*, PyObject*, PyObject*);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum functions {
|
enum functions {
|
||||||
API_PyArray_GetNDArrayCFeatureVersion = 211,
|
API_PyArray_GetNDArrayCFeatureVersion = 211,
|
||||||
@ -212,10 +215,12 @@ private:
|
|||||||
API_PyArray_NewCopy = 85,
|
API_PyArray_NewCopy = 85,
|
||||||
API_PyArray_NewFromDescr = 94,
|
API_PyArray_NewFromDescr = 94,
|
||||||
API_PyArray_DescrNewFromType = 96,
|
API_PyArray_DescrNewFromType = 96,
|
||||||
|
API_PyArray_Newshape = 135,
|
||||||
|
API_PyArray_Squeeze = 136,
|
||||||
|
API_PyArray_View = 137,
|
||||||
API_PyArray_DescrConverter = 174,
|
API_PyArray_DescrConverter = 174,
|
||||||
API_PyArray_EquivTypes = 182,
|
API_PyArray_EquivTypes = 182,
|
||||||
API_PyArray_GetArrayParamsFromObject = 278,
|
API_PyArray_GetArrayParamsFromObject = 278,
|
||||||
API_PyArray_Squeeze = 136,
|
|
||||||
API_PyArray_SetBaseObject = 282
|
API_PyArray_SetBaseObject = 282
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -243,11 +248,14 @@ private:
|
|||||||
DECL_NPY_API(PyArray_NewCopy);
|
DECL_NPY_API(PyArray_NewCopy);
|
||||||
DECL_NPY_API(PyArray_NewFromDescr);
|
DECL_NPY_API(PyArray_NewFromDescr);
|
||||||
DECL_NPY_API(PyArray_DescrNewFromType);
|
DECL_NPY_API(PyArray_DescrNewFromType);
|
||||||
|
DECL_NPY_API(PyArray_Newshape);
|
||||||
|
DECL_NPY_API(PyArray_Squeeze);
|
||||||
|
DECL_NPY_API(PyArray_View);
|
||||||
DECL_NPY_API(PyArray_DescrConverter);
|
DECL_NPY_API(PyArray_DescrConverter);
|
||||||
DECL_NPY_API(PyArray_EquivTypes);
|
DECL_NPY_API(PyArray_EquivTypes);
|
||||||
DECL_NPY_API(PyArray_GetArrayParamsFromObject);
|
DECL_NPY_API(PyArray_GetArrayParamsFromObject);
|
||||||
DECL_NPY_API(PyArray_Squeeze);
|
|
||||||
DECL_NPY_API(PyArray_SetBaseObject);
|
DECL_NPY_API(PyArray_SetBaseObject);
|
||||||
|
|
||||||
#undef DECL_NPY_API
|
#undef DECL_NPY_API
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
@ -785,6 +793,33 @@ public:
|
|||||||
if (isinstance<array>(new_array)) { *this = std::move(new_array); }
|
if (isinstance<array>(new_array)) { *this = std::move(new_array); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optional `order` parameter omitted, to be added as needed.
|
||||||
|
array reshape(ShapeContainer new_shape) {
|
||||||
|
detail::npy_api::PyArray_Dims d
|
||||||
|
= {reinterpret_cast<Py_intptr_t *>(new_shape->data()), int(new_shape->size())};
|
||||||
|
auto new_array
|
||||||
|
= reinterpret_steal<array>(detail::npy_api::get().PyArray_Newshape_(m_ptr, &d, 0));
|
||||||
|
if (!new_array) {
|
||||||
|
throw error_already_set();
|
||||||
|
}
|
||||||
|
return new_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a view of an array in a different data type.
|
||||||
|
/// This function may fundamentally reinterpret the data in the array.
|
||||||
|
/// It is the responsibility of the caller to ensure that this is safe.
|
||||||
|
/// Only supports the `dtype` argument, the `type` argument is omitted,
|
||||||
|
/// to be added as needed.
|
||||||
|
array view(const std::string &dtype) {
|
||||||
|
auto &api = detail::npy_api::get();
|
||||||
|
auto new_view = reinterpret_steal<array>(api.PyArray_View_(
|
||||||
|
m_ptr, dtype::from_args(pybind11::str(dtype)).release().ptr(), nullptr));
|
||||||
|
if (!new_view) {
|
||||||
|
throw error_already_set();
|
||||||
|
}
|
||||||
|
return new_view;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure that the argument is a NumPy array
|
/// Ensure that the argument is a NumPy array
|
||||||
/// In case of an error, nullptr is returned and the Python error is cleared.
|
/// In case of an error, nullptr is returned and the Python error is cleared.
|
||||||
static array ensure(handle h, int ExtraFlags = 0) {
|
static array ensure(handle h, int ExtraFlags = 0) {
|
||||||
|
@ -1778,7 +1778,7 @@ inline str enum_name(handle arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct enum_base {
|
struct enum_base {
|
||||||
enum_base(handle base, handle parent) : m_base(base), m_parent(parent) { }
|
enum_base(const handle &base, const handle &parent) : m_base(base), m_parent(parent) { }
|
||||||
|
|
||||||
PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) {
|
PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) {
|
||||||
m_base.attr("__entries") = dict();
|
m_base.attr("__entries") = dict();
|
||||||
@ -1928,6 +1928,19 @@ struct enum_base {
|
|||||||
handle m_parent;
|
handle m_parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <bool is_signed, size_t length> struct equivalent_integer {};
|
||||||
|
template <> struct equivalent_integer<true, 1> { using type = int8_t; };
|
||||||
|
template <> struct equivalent_integer<false, 1> { using type = uint8_t; };
|
||||||
|
template <> struct equivalent_integer<true, 2> { using type = int16_t; };
|
||||||
|
template <> struct equivalent_integer<false, 2> { using type = uint16_t; };
|
||||||
|
template <> struct equivalent_integer<true, 4> { using type = int32_t; };
|
||||||
|
template <> struct equivalent_integer<false, 4> { using type = uint32_t; };
|
||||||
|
template <> struct equivalent_integer<true, 8> { using type = int64_t; };
|
||||||
|
template <> struct equivalent_integer<false, 8> { using type = uint64_t; };
|
||||||
|
|
||||||
|
template <typename IntLike>
|
||||||
|
using equivalent_integer_t = typename equivalent_integer<std::is_signed<IntLike>::value, sizeof(IntLike)>::type;
|
||||||
|
|
||||||
PYBIND11_NAMESPACE_END(detail)
|
PYBIND11_NAMESPACE_END(detail)
|
||||||
|
|
||||||
/// Binds C++ enumerations and enumeration classes to Python
|
/// Binds C++ enumerations and enumeration classes to Python
|
||||||
@ -1938,13 +1951,17 @@ public:
|
|||||||
using Base::attr;
|
using Base::attr;
|
||||||
using Base::def_property_readonly;
|
using Base::def_property_readonly;
|
||||||
using Base::def_property_readonly_static;
|
using Base::def_property_readonly_static;
|
||||||
using Scalar = typename std::underlying_type<Type>::type;
|
using Underlying = typename std::underlying_type<Type>::type;
|
||||||
|
// Scalar is the integer representation of underlying type
|
||||||
|
using Scalar = detail::conditional_t<detail::any_of<
|
||||||
|
detail::is_std_char_type<Underlying>, std::is_same<Underlying, bool>
|
||||||
|
>::value, detail::equivalent_integer_t<Underlying>, Underlying>;
|
||||||
|
|
||||||
template <typename... Extra>
|
template <typename... Extra>
|
||||||
enum_(const handle &scope, const char *name, const Extra&... extra)
|
enum_(const handle &scope, const char *name, const Extra&... extra)
|
||||||
: class_<Type>(scope, name, extra...), m_base(*this, scope) {
|
: class_<Type>(scope, name, extra...), m_base(*this, scope) {
|
||||||
constexpr bool is_arithmetic = detail::any_of<std::is_same<arithmetic, Extra>...>::value;
|
constexpr bool is_arithmetic = detail::any_of<std::is_same<arithmetic, Extra>...>::value;
|
||||||
constexpr bool is_convertible = std::is_convertible<Type, Scalar>::value;
|
constexpr bool is_convertible = std::is_convertible<Type, Underlying>::value;
|
||||||
m_base.init(is_arithmetic, is_convertible);
|
m_base.init(is_arithmetic, is_convertible);
|
||||||
|
|
||||||
def(init([](Scalar i) { return static_cast<Type>(i); }), arg("value"));
|
def(init([](Scalar i) { return static_cast<Type>(i); }), arg("value"));
|
||||||
|
@ -382,6 +382,47 @@ private:
|
|||||||
# pragma warning(pop)
|
# pragma warning(pop)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
|
||||||
|
/// Replaces the current Python error indicator with the chosen error, performing a
|
||||||
|
/// 'raise from' to indicate that the chosen error was caused by the original error.
|
||||||
|
inline void raise_from(PyObject *type, const char *message) {
|
||||||
|
// Based on _PyErr_FormatVFromCause:
|
||||||
|
// https://github.com/python/cpython/blob/467ab194fc6189d9f7310c89937c51abeac56839/Python/errors.c#L405
|
||||||
|
// See https://github.com/pybind/pybind11/pull/2112 for details.
|
||||||
|
PyObject *exc = nullptr, *val = nullptr, *val2 = nullptr, *tb = nullptr;
|
||||||
|
|
||||||
|
assert(PyErr_Occurred());
|
||||||
|
PyErr_Fetch(&exc, &val, &tb);
|
||||||
|
PyErr_NormalizeException(&exc, &val, &tb);
|
||||||
|
if (tb != nullptr) {
|
||||||
|
PyException_SetTraceback(val, tb);
|
||||||
|
Py_DECREF(tb);
|
||||||
|
}
|
||||||
|
Py_DECREF(exc);
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
|
||||||
|
PyErr_SetString(type, message);
|
||||||
|
|
||||||
|
PyErr_Fetch(&exc, &val2, &tb);
|
||||||
|
PyErr_NormalizeException(&exc, &val2, &tb);
|
||||||
|
Py_INCREF(val);
|
||||||
|
PyException_SetCause(val2, val);
|
||||||
|
PyException_SetContext(val2, val);
|
||||||
|
PyErr_Restore(exc, val2, tb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the current Python error indicator with the chosen error, performing a 'raise from'
|
||||||
|
/// from the error contained in error_already_set to indicate that the chosen error was
|
||||||
|
/// caused by the original error. After this function is called error_already_set will
|
||||||
|
/// no longer contain an error.
|
||||||
|
inline void raise_from(error_already_set& err, PyObject *type, const char *message) {
|
||||||
|
err.restore();
|
||||||
|
raise_from(type, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
/** \defgroup python_builtins _
|
/** \defgroup python_builtins _
|
||||||
Unless stated otherwise, the following C++ functions behave the same
|
Unless stated otherwise, the following C++ functions behave the same
|
||||||
as their Python counterparts.
|
as their Python counterparts.
|
||||||
|
@ -2,6 +2,8 @@ import nox
|
|||||||
|
|
||||||
nox.options.sessions = ["lint", "tests", "tests_packaging"]
|
nox.options.sessions = ["lint", "tests", "tests_packaging"]
|
||||||
|
|
||||||
|
PYTHON_VERISONS = ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
|
|
||||||
@nox.session(reuse_venv=True)
|
@nox.session(reuse_venv=True)
|
||||||
def lint(session: nox.Session) -> None:
|
def lint(session: nox.Session) -> None:
|
||||||
@ -12,7 +14,7 @@ def lint(session: nox.Session) -> None:
|
|||||||
session.run("pre-commit", "run", "-a")
|
session.run("pre-commit", "run", "-a")
|
||||||
|
|
||||||
|
|
||||||
@nox.session
|
@nox.session(python=PYTHON_VERISONS)
|
||||||
def tests(session: nox.Session) -> None:
|
def tests(session: nox.Session) -> None:
|
||||||
"""
|
"""
|
||||||
Run the tests (requires a compiler).
|
Run the tests (requires a compiler).
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
numpy==1.16.6; python_version<"3.6" and sys_platform!="win32"
|
numpy==1.16.6; python_version<"3.6" and sys_platform!="win32"
|
||||||
numpy==1.18.0; platform_python_implementation=="PyPy" and sys_platform=="darwin" and python_version>="3.6"
|
numpy==1.18.0; platform_python_implementation=="PyPy" and sys_platform=="darwin" and python_version>="3.6"
|
||||||
numpy==1.19.3; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version=="3.6"
|
numpy==1.19.3; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version=="3.6"
|
||||||
numpy==1.20.0; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version>="3.7" and python_version<"3.10"
|
numpy==1.21.2; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version>="3.7" and python_version<"3.10"
|
||||||
|
numpy==1.21.2; platform_python_implementation!="PyPy" and sys_platform=="linux" and python_version=="3.10"
|
||||||
pytest==4.6.9; python_version<"3.5"
|
pytest==4.6.9; python_version<"3.5"
|
||||||
pytest==6.1.2; python_version=="3.5"
|
pytest==6.1.2; python_version=="3.5"
|
||||||
pytest==6.2.1; python_version>="3.6" and python_version<="3.8"
|
pytest==6.2.4; python_version>="3.6"
|
||||||
pytest @ git+https://github.com/pytest-dev/pytest@5d8392cb68fe0b3d6aba3a351d76fc9f02297980; python_version>="3.9"
|
|
||||||
pytest-timeout
|
pytest-timeout
|
||||||
scipy==1.2.3; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version<"3.6"
|
scipy==1.2.3; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version<"3.6"
|
||||||
scipy==1.5.4; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version>="3.6" and python_version<"3.10"
|
scipy==1.5.4; (platform_python_implementation!="PyPy" or sys_platform=="linux") and python_version>="3.6" and python_version<"3.10"
|
||||||
|
@ -23,6 +23,7 @@ public:
|
|||||||
|
|
||||||
std::string the_message() const { return message; }
|
std::string the_message() const { return message; }
|
||||||
virtual int the_answer() const = 0;
|
virtual int the_answer() const = 0;
|
||||||
|
virtual std::string argv0() const = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string message;
|
std::string message;
|
||||||
@ -32,6 +33,7 @@ class PyWidget final : public Widget {
|
|||||||
using Widget::Widget;
|
using Widget::Widget;
|
||||||
|
|
||||||
int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); }
|
int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); }
|
||||||
|
std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); }
|
||||||
};
|
};
|
||||||
|
|
||||||
PYBIND11_EMBEDDED_MODULE(widget_module, m) {
|
PYBIND11_EMBEDDED_MODULE(widget_module, m) {
|
||||||
@ -74,8 +76,24 @@ TEST_CASE("Import error handling") {
|
|||||||
REQUIRE_NOTHROW(py::module_::import("widget_module"));
|
REQUIRE_NOTHROW(py::module_::import("widget_module"));
|
||||||
REQUIRE_THROWS_WITH(py::module_::import("throw_exception"),
|
REQUIRE_THROWS_WITH(py::module_::import("throw_exception"),
|
||||||
"ImportError: C++ Error");
|
"ImportError: C++ Error");
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
|
||||||
|
Catch::Contains("ImportError: initialization failed"));
|
||||||
|
|
||||||
|
auto locals = py::dict("is_keyerror"_a=false, "message"_a="not set");
|
||||||
|
py::exec(R"(
|
||||||
|
try:
|
||||||
|
import throw_error_already_set
|
||||||
|
except ImportError as e:
|
||||||
|
is_keyerror = type(e.__cause__) == KeyError
|
||||||
|
message = str(e.__cause__)
|
||||||
|
)", py::globals(), locals);
|
||||||
|
REQUIRE(locals["is_keyerror"].cast<bool>() == true);
|
||||||
|
REQUIRE(locals["message"].cast<std::string>() == "'missing'");
|
||||||
|
#else
|
||||||
REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
|
REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
|
||||||
Catch::Contains("ImportError: KeyError"));
|
Catch::Contains("ImportError: KeyError"));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("There can be only one interpreter") {
|
TEST_CASE("There can be only one interpreter") {
|
||||||
@ -283,3 +301,25 @@ TEST_CASE("Reload module from file") {
|
|||||||
result = module_.attr("test")().cast<int>();
|
result = module_.attr("test")().cast<int>();
|
||||||
REQUIRE(result == 2);
|
REQUIRE(result == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("sys.argv gets initialized properly") {
|
||||||
|
py::finalize_interpreter();
|
||||||
|
{
|
||||||
|
py::scoped_interpreter default_scope;
|
||||||
|
auto module = py::module::import("test_interpreter");
|
||||||
|
auto py_widget = module.attr("DerivedWidget")("The question");
|
||||||
|
const auto &cpp_widget = py_widget.cast<const Widget &>();
|
||||||
|
REQUIRE(cpp_widget.argv0().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char *argv[] = {strdup("a.out")};
|
||||||
|
py::scoped_interpreter argv_scope(true, 1, argv);
|
||||||
|
free(argv[0]);
|
||||||
|
auto module = py::module::import("test_interpreter");
|
||||||
|
auto py_widget = module.attr("DerivedWidget")("The question");
|
||||||
|
const auto &cpp_widget = py_widget.cast<const Widget &>();
|
||||||
|
REQUIRE(cpp_widget.argv0() == "a.out");
|
||||||
|
}
|
||||||
|
py::initialize_interpreter();
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
|
||||||
from widget_module import Widget
|
from widget_module import Widget
|
||||||
|
|
||||||
|
|
||||||
@ -8,3 +10,6 @@ class DerivedWidget(Widget):
|
|||||||
|
|
||||||
def the_answer(self):
|
def the_answer(self):
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
|
def argv0(self):
|
||||||
|
return sys.argv[0]
|
||||||
|
@ -84,4 +84,65 @@ TEST_SUBMODULE(enums, m) {
|
|||||||
.value("ONE", SimpleEnum::THREE)
|
.value("ONE", SimpleEnum::THREE)
|
||||||
.export_values();
|
.export_values();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// test_enum_scalar
|
||||||
|
enum UnscopedUCharEnum : unsigned char {};
|
||||||
|
enum class ScopedShortEnum : short {};
|
||||||
|
enum class ScopedLongEnum : long {};
|
||||||
|
enum UnscopedUInt64Enum : std::uint64_t {};
|
||||||
|
static_assert(py::detail::all_of<
|
||||||
|
std::is_same<py::enum_<UnscopedUCharEnum>::Scalar, unsigned char>,
|
||||||
|
std::is_same<py::enum_<ScopedShortEnum>::Scalar, short>,
|
||||||
|
std::is_same<py::enum_<ScopedLongEnum>::Scalar, long>,
|
||||||
|
std::is_same<py::enum_<UnscopedUInt64Enum>::Scalar, std::uint64_t>
|
||||||
|
>::value, "Error during the deduction of enum's scalar type with normal integer underlying");
|
||||||
|
|
||||||
|
// test_enum_scalar_with_char_underlying
|
||||||
|
enum class ScopedCharEnum : char { Zero, Positive };
|
||||||
|
enum class ScopedWCharEnum : wchar_t { Zero, Positive };
|
||||||
|
enum class ScopedChar32Enum : char32_t { Zero, Positive };
|
||||||
|
enum class ScopedChar16Enum : char16_t { Zero, Positive };
|
||||||
|
|
||||||
|
// test the scalar of char type enums according to chapter 'Character types'
|
||||||
|
// from https://en.cppreference.com/w/cpp/language/types
|
||||||
|
static_assert(py::detail::any_of<
|
||||||
|
std::is_same<py::enum_<ScopedCharEnum>::Scalar, signed char>, // e.g. gcc on x86
|
||||||
|
std::is_same<py::enum_<ScopedCharEnum>::Scalar, unsigned char> // e.g. arm linux
|
||||||
|
>::value, "char should be cast to either signed char or unsigned char");
|
||||||
|
static_assert(
|
||||||
|
sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 2 ||
|
||||||
|
sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 4
|
||||||
|
, "wchar_t should be either 16 bits (Windows) or 32 (everywhere else)");
|
||||||
|
static_assert(py::detail::all_of<
|
||||||
|
std::is_same<py::enum_<ScopedChar32Enum>::Scalar, std::uint_least32_t>,
|
||||||
|
std::is_same<py::enum_<ScopedChar16Enum>::Scalar, std::uint_least16_t>
|
||||||
|
>::value, "char32_t, char16_t (and char8_t)'s size, signedness, and alignment is determined");
|
||||||
|
#if defined(PYBIND11_HAS_U8STRING)
|
||||||
|
enum class ScopedChar8Enum : char8_t { Zero, Positive };
|
||||||
|
static_assert(std::is_same<py::enum_<ScopedChar8Enum>::Scalar, unsigned char>::value);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// test_char_underlying_enum
|
||||||
|
py::enum_<ScopedCharEnum>(m, "ScopedCharEnum")
|
||||||
|
.value("Zero", ScopedCharEnum::Zero)
|
||||||
|
.value("Positive", ScopedCharEnum::Positive);
|
||||||
|
py::enum_<ScopedWCharEnum>(m, "ScopedWCharEnum")
|
||||||
|
.value("Zero", ScopedWCharEnum::Zero)
|
||||||
|
.value("Positive", ScopedWCharEnum::Positive);
|
||||||
|
py::enum_<ScopedChar32Enum>(m, "ScopedChar32Enum")
|
||||||
|
.value("Zero", ScopedChar32Enum::Zero)
|
||||||
|
.value("Positive", ScopedChar32Enum::Positive);
|
||||||
|
py::enum_<ScopedChar16Enum>(m, "ScopedChar16Enum")
|
||||||
|
.value("Zero", ScopedChar16Enum::Zero)
|
||||||
|
.value("Positive", ScopedChar16Enum::Positive);
|
||||||
|
|
||||||
|
// test_bool_underlying_enum
|
||||||
|
enum class ScopedBoolEnum : bool { FALSE, TRUE };
|
||||||
|
|
||||||
|
// bool is unsigned (std::is_signed returns false) and 1-byte long, so represented with u8
|
||||||
|
static_assert(std::is_same<py::enum_<ScopedBoolEnum>::Scalar, std::uint8_t>::value, "");
|
||||||
|
|
||||||
|
py::enum_<ScopedBoolEnum>(m, "ScopedBoolEnum")
|
||||||
|
.value("FALSE", ScopedBoolEnum::FALSE)
|
||||||
|
.value("TRUE", ScopedBoolEnum::TRUE);
|
||||||
}
|
}
|
||||||
|
@ -218,10 +218,16 @@ def test_binary_operators():
|
|||||||
def test_enum_to_int():
|
def test_enum_to_int():
|
||||||
m.test_enum_to_int(m.Flags.Read)
|
m.test_enum_to_int(m.Flags.Read)
|
||||||
m.test_enum_to_int(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
m.test_enum_to_int(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
||||||
|
m.test_enum_to_int(m.ScopedCharEnum.Positive)
|
||||||
|
m.test_enum_to_int(m.ScopedBoolEnum.TRUE)
|
||||||
m.test_enum_to_uint(m.Flags.Read)
|
m.test_enum_to_uint(m.Flags.Read)
|
||||||
m.test_enum_to_uint(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
m.test_enum_to_uint(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
||||||
|
m.test_enum_to_uint(m.ScopedCharEnum.Positive)
|
||||||
|
m.test_enum_to_uint(m.ScopedBoolEnum.TRUE)
|
||||||
m.test_enum_to_long_long(m.Flags.Read)
|
m.test_enum_to_long_long(m.Flags.Read)
|
||||||
m.test_enum_to_long_long(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
m.test_enum_to_long_long(m.ClassWithUnscopedEnum.EMode.EFirstMode)
|
||||||
|
m.test_enum_to_long_long(m.ScopedCharEnum.Positive)
|
||||||
|
m.test_enum_to_long_long(m.ScopedBoolEnum.TRUE)
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_enum_name():
|
def test_duplicate_enum_name():
|
||||||
@ -230,6 +236,28 @@ def test_duplicate_enum_name():
|
|||||||
assert str(excinfo.value) == 'SimpleEnum: element "ONE" already exists!'
|
assert str(excinfo.value) == 'SimpleEnum: element "ONE" already exists!'
|
||||||
|
|
||||||
|
|
||||||
|
def test_char_underlying_enum(): # Issue #1331/PR #1334:
|
||||||
|
assert type(m.ScopedCharEnum.Positive.__int__()) is int
|
||||||
|
assert int(m.ScopedChar16Enum.Zero) == 0 # int() call should successfully return
|
||||||
|
assert hash(m.ScopedChar32Enum.Positive) == 1
|
||||||
|
assert m.ScopedCharEnum.Positive.__getstate__() == 1 # return type is long in py2.x
|
||||||
|
assert m.ScopedWCharEnum(1) == m.ScopedWCharEnum.Positive
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
# Enum should construct with a int, even with char underlying type
|
||||||
|
m.ScopedWCharEnum("0")
|
||||||
|
|
||||||
|
|
||||||
|
def test_bool_underlying_enum():
|
||||||
|
assert type(m.ScopedBoolEnum.TRUE.__int__()) is int
|
||||||
|
assert int(m.ScopedBoolEnum.FALSE) == 0
|
||||||
|
assert hash(m.ScopedBoolEnum.TRUE) == 1
|
||||||
|
assert m.ScopedBoolEnum.TRUE.__getstate__() == 1
|
||||||
|
assert m.ScopedBoolEnum(1) == m.ScopedBoolEnum.TRUE
|
||||||
|
# Enum could construct with a bool
|
||||||
|
# (bool is a strict subclass of int, and False will be converted to 0)
|
||||||
|
assert m.ScopedBoolEnum(False) == m.ScopedBoolEnum.FALSE
|
||||||
|
|
||||||
|
|
||||||
def test_docstring_signatures():
|
def test_docstring_signatures():
|
||||||
for enum_type in [m.ScopedEnum, m.UnscopedEnum]:
|
for enum_type in [m.ScopedEnum, m.UnscopedEnum]:
|
||||||
for attr in enum_type.__dict__.values():
|
for attr in enum_type.__dict__.values():
|
||||||
|
@ -262,4 +262,24 @@ TEST_SUBMODULE(exceptions, m) {
|
|||||||
m.def("simple_bool_passthrough", [](bool x) {return x;});
|
m.def("simple_bool_passthrough", [](bool x) {return x;});
|
||||||
|
|
||||||
m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); });
|
m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); });
|
||||||
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
|
||||||
|
m.def("raise_from", []() {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "inner");
|
||||||
|
py::raise_from(PyExc_ValueError, "outer");
|
||||||
|
throw py::error_already_set();
|
||||||
|
});
|
||||||
|
|
||||||
|
m.def("raise_from_already_set", []() {
|
||||||
|
try {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "inner");
|
||||||
|
throw py::error_already_set();
|
||||||
|
} catch (py::error_already_set& e) {
|
||||||
|
py::raise_from(e, PyExc_ValueError, "outer");
|
||||||
|
throw py::error_already_set();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,22 @@ def test_error_already_set(msg):
|
|||||||
assert msg(excinfo.value) == "foo"
|
assert msg(excinfo.value) == "foo"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.PY2")
|
||||||
|
def test_raise_from(msg):
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
m.raise_from()
|
||||||
|
assert msg(excinfo.value) == "outer"
|
||||||
|
assert msg(excinfo.value.__cause__) == "inner"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.PY2")
|
||||||
|
def test_raise_from_already_set(msg):
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
m.raise_from_already_set()
|
||||||
|
assert msg(excinfo.value) == "outer"
|
||||||
|
assert msg(excinfo.value.__cause__) == "inner"
|
||||||
|
|
||||||
|
|
||||||
def test_cross_module_exceptions(msg):
|
def test_cross_module_exceptions(msg):
|
||||||
with pytest.raises(RuntimeError) as excinfo:
|
with pytest.raises(RuntimeError) as excinfo:
|
||||||
cm.raise_runtime_error()
|
cm.raise_runtime_error()
|
||||||
|
@ -405,6 +405,16 @@ TEST_SUBMODULE(numpy_array, sm) {
|
|||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sm.def("array_view",
|
||||||
|
[](py::array_t<uint8_t> a, const std::string &dtype) { return a.view(dtype); });
|
||||||
|
|
||||||
|
sm.def("reshape_initializer_list", [](py::array_t<int> a, size_t N, size_t M, size_t O) {
|
||||||
|
return a.reshape({N, M, O});
|
||||||
|
});
|
||||||
|
sm.def("reshape_tuple", [](py::array_t<int> a, const std::vector<int> &new_shape) {
|
||||||
|
return a.reshape(new_shape);
|
||||||
|
});
|
||||||
|
|
||||||
sm.def("index_using_ellipsis",
|
sm.def("index_using_ellipsis",
|
||||||
[](const py::array &a) { return a[py::make_tuple(0, py::ellipsis(), 0)]; });
|
[](const py::array &a) { return a[py::make_tuple(0, py::ellipsis(), 0)]; });
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ def test_array_unchecked_fixed_dims(msg):
|
|||||||
assert m.proxy_auxiliaries2_const_ref(z1)
|
assert m.proxy_auxiliaries2_const_ref(z1)
|
||||||
|
|
||||||
|
|
||||||
def test_array_unchecked_dyn_dims(msg):
|
def test_array_unchecked_dyn_dims():
|
||||||
z1 = np.array([[1, 2], [3, 4]], dtype="float64")
|
z1 = np.array([[1, 2], [3, 4]], dtype="float64")
|
||||||
m.proxy_add2_dyn(z1, 10)
|
m.proxy_add2_dyn(z1, 10)
|
||||||
assert np.all(z1 == [[11, 12], [13, 14]])
|
assert np.all(z1 == [[11, 12], [13, 14]])
|
||||||
@ -444,7 +444,7 @@ def test_initializer_list():
|
|||||||
assert m.array_initializer_list4().shape == (1, 2, 3, 4)
|
assert m.array_initializer_list4().shape == (1, 2, 3, 4)
|
||||||
|
|
||||||
|
|
||||||
def test_array_resize(msg):
|
def test_array_resize():
|
||||||
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype="float64")
|
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype="float64")
|
||||||
m.array_reshape2(a)
|
m.array_reshape2(a)
|
||||||
assert a.size == 9
|
assert a.size == 9
|
||||||
@ -470,12 +470,52 @@ def test_array_resize(msg):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail("env.PYPY")
|
@pytest.mark.xfail("env.PYPY")
|
||||||
def test_array_create_and_resize(msg):
|
def test_array_create_and_resize():
|
||||||
a = m.create_and_resize(2)
|
a = m.create_and_resize(2)
|
||||||
assert a.size == 4
|
assert a.size == 4
|
||||||
assert np.all(a == 42.0)
|
assert np.all(a == 42.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_array_view():
|
||||||
|
a = np.ones(100 * 4).astype("uint8")
|
||||||
|
a_float_view = m.array_view(a, "float32")
|
||||||
|
assert a_float_view.shape == (100 * 1,) # 1 / 4 bytes = 8 / 32
|
||||||
|
|
||||||
|
a_int16_view = m.array_view(a, "int16") # 1 / 2 bytes = 16 / 32
|
||||||
|
assert a_int16_view.shape == (100 * 2,)
|
||||||
|
|
||||||
|
|
||||||
|
def test_array_view_invalid():
|
||||||
|
a = np.ones(100 * 4).astype("uint8")
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
m.array_view(a, "deadly_dtype")
|
||||||
|
|
||||||
|
|
||||||
|
def test_reshape_initializer_list():
|
||||||
|
a = np.arange(2 * 7 * 3) + 1
|
||||||
|
x = m.reshape_initializer_list(a, 2, 7, 3)
|
||||||
|
assert x.shape == (2, 7, 3)
|
||||||
|
assert list(x[1][4]) == [34, 35, 36]
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
m.reshape_initializer_list(a, 1, 7, 3)
|
||||||
|
assert str(excinfo.value) == "cannot reshape array of size 42 into shape (1,7,3)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reshape_tuple():
|
||||||
|
a = np.arange(3 * 7 * 2) + 1
|
||||||
|
x = m.reshape_tuple(a, (3, 7, 2))
|
||||||
|
assert x.shape == (3, 7, 2)
|
||||||
|
assert list(x[1][4]) == [23, 24]
|
||||||
|
y = m.reshape_tuple(x, (x.size,))
|
||||||
|
assert y.shape == (42,)
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
m.reshape_tuple(a, (3, 7, 1))
|
||||||
|
assert str(excinfo.value) == "cannot reshape array of size 42 into shape (3,7,1)"
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
m.reshape_tuple(a, ())
|
||||||
|
assert str(excinfo.value) == "cannot reshape array of size 42 into shape ()"
|
||||||
|
|
||||||
|
|
||||||
def test_index_using_ellipsis():
|
def test_index_using_ellipsis():
|
||||||
a = m.index_using_ellipsis(np.zeros((5, 6, 7)))
|
a = m.index_using_ellipsis(np.zeros((5, 6, 7)))
|
||||||
assert a.shape == (6,)
|
assert a.shape == (6,)
|
||||||
|
@ -63,14 +63,20 @@ def partial_ld_offset():
|
|||||||
def partial_dtype_fmt():
|
def partial_dtype_fmt():
|
||||||
ld = np.dtype("longdouble")
|
ld = np.dtype("longdouble")
|
||||||
partial_ld_off = partial_ld_offset()
|
partial_ld_off = partial_ld_offset()
|
||||||
return dt_fmt().format(ld.itemsize, partial_ld_off, partial_ld_off + ld.itemsize)
|
partial_size = partial_ld_off + ld.itemsize
|
||||||
|
partial_end_padding = partial_size % np.dtype("uint64").alignment
|
||||||
|
return dt_fmt().format(
|
||||||
|
ld.itemsize, partial_ld_off, partial_size + partial_end_padding
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def partial_nested_fmt():
|
def partial_nested_fmt():
|
||||||
ld = np.dtype("longdouble")
|
ld = np.dtype("longdouble")
|
||||||
partial_nested_off = 8 + 8 * (ld.alignment > 8)
|
partial_nested_off = 8 + 8 * (ld.alignment > 8)
|
||||||
partial_ld_off = partial_ld_offset()
|
partial_ld_off = partial_ld_offset()
|
||||||
partial_nested_size = partial_nested_off * 2 + partial_ld_off + ld.itemsize
|
partial_size = partial_ld_off + ld.itemsize
|
||||||
|
partial_end_padding = partial_size % np.dtype("uint64").alignment
|
||||||
|
partial_nested_size = partial_nested_off * 2 + partial_size + partial_end_padding
|
||||||
return "{{'names':['a'], 'formats':[{}], 'offsets':[{}], 'itemsize':{}}}".format(
|
return "{{'names':['a'], 'formats':[{}], 'offsets':[{}], 'itemsize':{}}}".format(
|
||||||
partial_dtype_fmt(), partial_nested_off, partial_nested_size
|
partial_dtype_fmt(), partial_nested_off, partial_nested_size
|
||||||
)
|
)
|
||||||
@ -91,10 +97,12 @@ def test_format_descriptors():
|
|||||||
ldbl_fmt = ("4x" if ld.alignment > 4 else "") + ld.char
|
ldbl_fmt = ("4x" if ld.alignment > 4 else "") + ld.char
|
||||||
ss_fmt = "^T{?:bool_:3xI:uint_:f:float_:" + ldbl_fmt + ":ldbl_:}"
|
ss_fmt = "^T{?:bool_:3xI:uint_:f:float_:" + ldbl_fmt + ":ldbl_:}"
|
||||||
dbl = np.dtype("double")
|
dbl = np.dtype("double")
|
||||||
|
end_padding = ld.itemsize % np.dtype("uint64").alignment
|
||||||
partial_fmt = (
|
partial_fmt = (
|
||||||
"^T{?:bool_:3xI:uint_:f:float_:"
|
"^T{?:bool_:3xI:uint_:f:float_:"
|
||||||
+ str(4 * (dbl.alignment > 4) + dbl.itemsize + 8 * (ld.alignment > 8))
|
+ str(4 * (dbl.alignment > 4) + dbl.itemsize + 8 * (ld.alignment > 8))
|
||||||
+ "xg:ldbl_:}"
|
+ "xg:ldbl_:"
|
||||||
|
+ (str(end_padding) + "x}" if end_padding > 0 else "}")
|
||||||
)
|
)
|
||||||
nested_extra = str(max(8, ld.alignment))
|
nested_extra = str(max(8, ld.alignment))
|
||||||
assert m.print_format_descriptors() == [
|
assert m.print_format_descriptors() == [
|
||||||
|
@ -70,6 +70,19 @@ TEST_SUBMODULE(pytypes, m) {
|
|||||||
m.def("dict_contains",
|
m.def("dict_contains",
|
||||||
[](const py::dict &dict, const char *val) { return dict.contains(val); });
|
[](const py::dict &dict, const char *val) { return dict.contains(val); });
|
||||||
|
|
||||||
|
// test_tuple
|
||||||
|
m.def("get_tuple", []() { return py::make_tuple(42, py::none(), "spam"); });
|
||||||
|
|
||||||
|
#if PY_VERSION_HEX >= 0x03030000
|
||||||
|
// test_simple_namespace
|
||||||
|
m.def("get_simple_namespace", []() {
|
||||||
|
auto ns = py::make_simple_namespace("attr"_a=42, "x"_a="foo", "wrong"_a=1);
|
||||||
|
py::delattr(ns, "wrong");
|
||||||
|
py::setattr(ns, "right", py::int_(2));
|
||||||
|
return ns;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
// test_str
|
// test_str
|
||||||
m.def("str_from_string", []() { return py::str(std::string("baz")); });
|
m.def("str_from_string", []() { return py::str(std::string("baz")); });
|
||||||
m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); });
|
m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); });
|
||||||
|
@ -99,6 +99,19 @@ def test_dict(capture, doc):
|
|||||||
assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}
|
assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}
|
||||||
|
|
||||||
|
|
||||||
|
def test_tuple():
|
||||||
|
assert m.get_tuple() == (42, None, "spam")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.PY2")
|
||||||
|
def test_simple_namespace():
|
||||||
|
ns = m.get_simple_namespace()
|
||||||
|
assert ns.attr == 42
|
||||||
|
assert ns.x == "foo"
|
||||||
|
assert ns.right == 2
|
||||||
|
assert not hasattr(ns, "wrong")
|
||||||
|
|
||||||
|
|
||||||
def test_str(doc):
|
def test_str(doc):
|
||||||
assert m.str_from_string().encode().decode() == "baz"
|
assert m.str_from_string().encode().decode() == "baz"
|
||||||
assert m.str_from_bytes().encode().decode() == "boo"
|
assert m.str_from_bytes().encode().decode() == "boo"
|
||||||
|
Loading…
Reference in New Issue
Block a user