Merge branch 'master' into sh_merge_master

This commit is contained in:
Ralf W. Grosse-Kunstleve 2021-08-26 14:46:44 -07:00
commit a655f95a83
26 changed files with 625 additions and 43 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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 {

View File

@ -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***

View File

@ -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;

View File

@ -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) {

View File

@ -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"));

View File

@ -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.

View File

@ -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).

View File

@ -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"

View File

@ -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();
}

View File

@ -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]

View File

@ -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);
} }

View File

@ -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():

View File

@ -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
} }

View File

@ -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()

View File

@ -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)]; });

View File

@ -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,)

View File

@ -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() == [

View File

@ -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)); });

View File

@ -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"