Merge branch 'master' into v2.10

This commit is contained in:
Henry Schreiner 2022-12-20 18:22:46 -05:00
commit 037641780d
62 changed files with 2958 additions and 1040 deletions

View File

@ -6,7 +6,8 @@ body:
- type: markdown
attributes:
value: |
Maintainers will only make a best effort to triage PRs. Please do your best to make the issue as easy to act on as possible, and only open if clearly a problem with pybind11 (ask first if unsure).
Please do your best to make the issue as easy to act on as possible, and only submit here if there is clearly a problem with pybind11 (ask first if unsure). **Note that a reproducer in a PR is much more likely to get immediate attention.**
- type: checkboxes
id: steps
attributes:
@ -20,6 +21,13 @@ body:
- label: Consider asking first in the [Gitter chat room](https://gitter.im/pybind/Lobby) or in a [Discussion](https:/pybind/pybind11/discussions/new).
required: false
- type: input
id: version
attributes:
label: What version (or hash if on master) of pybind11 are you using?
validations:
required: true
- type: textarea
id: description
attributes:
@ -40,6 +48,14 @@ body:
The code should be minimal, have no external dependencies, isolate the
function(s) that cause breakage. Submit matched and complete C++ and
Python snippets that can be easily compiled and run to diagnose the
issue. If possible, make a PR with a new, failing test to give us a
starting point to work on!
issue. — Note that a reproducer in a PR is much more likely to get
immediate attention: failing tests in the pybind11 CI are the best
starting point for working out fixes.
render: text
- type: input
id: regression
attributes:
label: Is this a regression? Put the last known working version here if it is.
description: Put the last known working version here if this is a regression.
value: Not a regression

View File

@ -17,6 +17,8 @@ env:
PIP_ONLY_BINARY: numpy
FORCE_COLOR: 3
PYTEST_TIMEOUT: 300
# For cmake:
VERBOSE: 1
jobs:
# This is the "main" test suite, which tests a large number of different
@ -25,7 +27,7 @@ jobs:
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, windows-2022, macos-latest]
runs-on: [ubuntu-20.04, windows-2022, macos-latest]
python:
- '3.6'
- '3.9'
@ -42,12 +44,12 @@ jobs:
# We support an optional key: args, for cmake args
include:
# Just add a key
- runs-on: ubuntu-latest
- runs-on: ubuntu-20.04
python: '3.6'
args: >
-DPYBIND11_FINDPYTHON=ON
-DCMAKE_CXX_FLAGS="-D_=1"
- runs-on: ubuntu-latest
- runs-on: ubuntu-20.04
python: 'pypy-3.8'
args: >
-DPYBIND11_FINDPYTHON=ON
@ -194,13 +196,13 @@ jobs:
python-debug: false
name: "🐍 ${{ matrix.python-version }}${{ matrix.python-debug && '-dbg' || '' }} (deadsnakes)${{ matrix.valgrind && ' • Valgrind' || '' }} • x64"
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Setup Python ${{ matrix.python-version }} (deadsnakes)
uses: deadsnakes/action@v2.1.1
uses: deadsnakes/action@v3.0.0
with:
python-version: ${{ matrix.python-version }}
debug: ${{ matrix.python-debug }}
@ -918,7 +920,7 @@ jobs:
- name: Configure C++11
# LTO leads to many undefined reference like
# `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&)
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DCMAKE_VERBOSE_MAKEFILE=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build
- name: Build C++11
run: cmake --build build -j 2
@ -936,7 +938,7 @@ jobs:
run: git clean -fdx
- name: Configure C++14
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DCMAKE_VERBOSE_MAKEFILE=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build2
- name: Build C++14
run: cmake --build build2 -j 2
@ -954,7 +956,7 @@ jobs:
run: git clean -fdx
- name: Configure C++17
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DCMAKE_VERBOSE_MAKEFILE=ON -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3
run: cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -S . -B build3
- name: Build C++17
run: cmake --build build3 -j 2
@ -967,3 +969,138 @@ jobs:
- name: Interface test C++17
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target test_cmake_build
windows_clang:
strategy:
matrix:
os: [windows-latest]
python: ['3.10']
runs-on: "${{ matrix.os }}"
name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-latest"
steps:
- name: Show env
run: env
- name: Checkout
uses: actions/checkout@v3
- name: Set up Clang
uses: egor-tensin/setup-clang@v1
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13
- name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@v3
- name: Run pip installs
run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt
- name: Show Clang++ version
run: clang++ --version
- name: Show CMake version
run: cmake --version
# TODO: WERROR=ON
- name: Configure Clang
run: >
cmake -G Ninja -S . -B .
-DPYBIND11_WERROR=OFF
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
- name: Build
run: cmake --build . -j 2
- name: Python tests
run: cmake --build . --target pytest -j 2
- name: C++ tests
run: cmake --build . --target cpptest -j 2
- name: Interface test
run: cmake --build . --target test_cmake_build -j 2
- name: Clean directory
run: git clean -fdx
macos_brew_install_llvm:
name: "macos-latest • brew install llvm"
runs-on: macos-latest
env:
# https://apple.stackexchange.com/questions/227026/how-to-install-recent-clang-with-homebrew
LDFLAGS: '-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib'
steps:
- name: Update PATH
run: echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH
- name: Show env
run: env
- name: Checkout
uses: actions/checkout@v3
- name: Show Clang++ version before brew install llvm
run: clang++ --version
- name: brew install llvm
run: brew install llvm
- name: Show Clang++ version after brew install llvm
run: clang++ --version
- name: Update CMake
uses: jwlawson/actions-setup-cmake@v1.13
- name: Run pip installs
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r tests/requirements.txt
python3 -m pip install numpy
python3 -m pip install scipy
- name: Show CMake version
run: cmake --version
- name: CMake Configure
run: >
cmake -S . -B .
-DPYBIND11_WERROR=ON
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=17
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)")
- name: Build
run: cmake --build . -j 2
- name: Python tests
run: cmake --build . --target pytest -j 2
- name: C++ tests
run: cmake --build . --target cpptest -j 2
- name: Interface test
run: cmake --build . --target test_cmake_build -j 2
- name: Clean directory
run: git clean -fdx

View File

@ -9,6 +9,10 @@ on:
- stable
- v*
env:
# For cmake:
VERBOSE: 1
jobs:
# This tests various versions of CMake in various combinations, to make sure
# the configure step passes.
@ -16,12 +20,12 @@ jobs:
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: [ubuntu-20.04, macos-latest, windows-latest]
arch: [x64]
cmake: ["3.23"]
include:
- runs-on: ubuntu-latest
- runs-on: ubuntu-20.04
arch: x64
cmake: 3.4

View File

@ -14,6 +14,8 @@ on:
env:
FORCE_COLOR: 3
# For cmake:
VERBOSE: 1
jobs:
pre-commit:

View File

@ -10,7 +10,11 @@ jobs:
steps:
- uses: actions/labeler@main
if: github.event.pull_request.merged == true
if: >
github.event.pull_request.merged == true &&
!startsWith(github.event.pull_request.title, 'chore(deps):') &&
!startsWith(github.event.pull_request.title, 'ci(fix):') &&
!startsWith(github.event.pull_request.title, 'docs(changelog):')
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler_merged.yml

View File

@ -98,13 +98,13 @@ jobs:
- uses: actions/download-artifact@v3
- name: Publish standard package
uses: pypa/gh-action-pypi-publish@v1.5.1
uses: pypa/gh-action-pypi-publish@v1.6.4
with:
password: ${{ secrets.pypi_password }}
packages_dir: standard/
- name: Publish global package
uses: pypa/gh-action-pypi-publish@v1.5.1
uses: pypa/gh-action-pypi-publish@v1.6.4
with:
password: ${{ secrets.pypi_password_global }}
packages_dir: global/

View File

@ -11,6 +11,8 @@ concurrency:
env:
PIP_ONLY_BINARY: numpy
# For cmake:
VERBOSE: 1
jobs:
standard:

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ pybind11Targets.cmake
/pybind11/share/*
/docs/_build/*
.ipynb_checkpoints/
tests/main.cpp

View File

@ -24,7 +24,7 @@ exclude: ^tools/JoinPaths.cmake$
repos:
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.3.0"
rev: "v4.4.0"
hooks:
- id: check-added-large-files
- id: check-case-conflict
@ -41,7 +41,7 @@ repos:
# Upgrade old Python syntax
- repo: https://github.com/asottile/pyupgrade
rev: "v2.38.2"
rev: "v3.3.0"
hooks:
- id: pyupgrade
args: [--py36-plus]
@ -54,7 +54,7 @@ repos:
# Black, the code formatter, natively supports pre-commit
- repo: https://github.com/psf/black
rev: "22.8.0" # Keep in sync with blacken-docs
rev: "22.10.0" # Keep in sync with blacken-docs
hooks:
- id: black
@ -64,7 +64,7 @@ repos:
hooks:
- id: blacken-docs
additional_dependencies:
- black==22.8.0 # keep in sync with black hook
- black==22.10.0 # keep in sync with black hook
# Changes tabs to spaces
- repo: https://github.com/Lucas-C/pre-commit-hooks
@ -80,7 +80,7 @@ repos:
# Autoremoves unused imports
- repo: https://github.com/hadialqattan/pycln
rev: "v2.1.1"
rev: "v2.1.2"
hooks:
- id: pycln
stages: [manual]
@ -108,7 +108,7 @@ repos:
# Flake8 also supports pre-commit natively (same author)
- repo: https://github.com/PyCQA/flake8
rev: "5.0.4"
rev: "6.0.0"
hooks:
- id: flake8
exclude: ^(docs/.*|tools/.*)$
@ -116,7 +116,7 @@ repos:
# PyLint has native support - not always usable, but works for us
- repo: https://github.com/PyCQA/pylint
rev: "v2.15.3"
rev: "v2.15.8"
hooks:
- id: pylint
files: ^pybind11
@ -132,7 +132,7 @@ repos:
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v0.981"
rev: "v0.991"
hooks:
- id: mypy
args: []
@ -141,7 +141,7 @@ repos:
# Checks the manifest for missing files (native support)
- repo: https://github.com/mgedmin/check-manifest
rev: "0.48"
rev: "0.49"
hooks:
- id: check-manifest
# This is a slow hook, so only run this if --hook-stage manual is passed
@ -152,7 +152,7 @@ repos:
# Use tools/codespell_ignore_lines_from_errors.py
# to rebuild .codespell-ignore-lines
- repo: https://github.com/codespell-project/codespell
rev: "v2.2.1"
rev: "v2.2.2"
hooks:
- id: codespell
exclude: ".supp$"
@ -175,7 +175,7 @@ repos:
# Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v14.0.6"
rev: "v15.0.4"
hooks:
- id: clang-format
types_or: [c++, c, cuda]

View File

@ -126,6 +126,8 @@ set(PYBIND11_HEADERS
include/pybind11/complex.h
include/pybind11/options.h
include/pybind11/eigen.h
include/pybind11/eigen/matrix.h
include/pybind11/eigen/tensor.h
include/pybind11/embed.h
include/pybind11/eval.h
include/pybind11/gil.h

View File

@ -177,9 +177,12 @@ section.
may be explicitly (re-)thrown to delegate it to the other,
previously-declared existing exception translators.
Note that ``libc++`` and ``libstdc++`` `behave differently <https://stackoverflow.com/questions/19496643/using-clang-fvisibility-hidden-and-typeinfo-and-type-erasure/28827430>`_
with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI boundaries need to be explicitly exported, as exercised in ``tests/test_exceptions.h``.
See also: "Problems with C++ exceptions" under `GCC Wiki <https://gcc.gnu.org/wiki/Visibility>`_.
Note that ``libc++`` and ``libstdc++`` `behave differently under macOS
<https://stackoverflow.com/questions/19496643/using-clang-fvisibility-hidden-and-typeinfo-and-type-erasure/28827430>`_
with ``-fvisibility=hidden``. Therefore exceptions that are used across ABI
boundaries need to be explicitly exported, as exercised in
``tests/test_exceptions.h``. See also:
"Problems with C++ exceptions" under `GCC Wiki <https://gcc.gnu.org/wiki/Visibility>`_.
Local vs Global Exception Translators

View File

@ -324,6 +324,15 @@ The class ``options`` allows you to selectively suppress auto-generated signatur
m.def("add", [](int a, int b) { return a + b; }, "A function which adds two numbers");
}
pybind11 also appends all members of an enum to the resulting enum docstring.
This default behavior can be disabled by using the ``disable_enum_members_docstring()``
function of the ``options`` class.
With ``disable_user_defined_docstrings()`` all user defined docstrings of
``module_::def()``, ``class_::def()`` and ``enum_()`` are disabled, but the
function signatures and enum members are included in the docstring, unless they
are disabled separately.
Note that changes to the settings affect only function bindings created during the
lifetime of the ``options`` instance. When it goes out of scope at the end of the module's init function,
the default settings are restored to prevent unwanted side effects.

View File

@ -15,6 +15,60 @@ IN DEVELOPMENT
Changes will be summarized here periodically.
Version 2.10.2 (Dec 20, 2022)
-----------------------------
Changes:
* ``scoped_interpreter`` constructor taking ``PyConfig``.
`#4330 <https://github.com/pybind/pybind11/pull/4330>`_
* ``pybind11/eigen/tensor.h`` adds converters to and from ``Eigen::Tensor`` and
``Eigen::TensorMap``.
`#4201 <https://github.com/pybind/pybind11/pull/4201>`_
* ``PyGILState_Check()``'s were integrated to ``pybind11::handle``
``inc_ref()`` & ``dec_ref()``. The added GIL checks are guarded by
``PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF``, which is the default only if
``NDEBUG`` is not defined.
`#4246 <https://github.com/pybind/pybind11/pull/4246>`_
* Add option for enable/disable enum members in docstring.
`#2768 <https://github.com/pybind/pybind11/pull/2768>`_
* Fixed typing of ``KeysView``, ``ValuesView`` and ``ItemsView`` in ``bind_map``.
`#4353 <https://github.com/pybind/pybind11/pull/4353>`_
Bug fixes:
* Bug fix affecting only Python 3.6 under very specific, uncommon conditions:
move ``PyEval_InitThreads()`` call to the correct location.
`#4350 <https://github.com/pybind/pybind11/pull/4350>`_
* Fix segfault bug when passing foreign native functions to functional.h.
`#4254 <https://github.com/pybind/pybind11/pull/4254>`_
Build system improvements:
* Support setting PYTHON_LIBRARIES manually for Windows ARM cross-compilation
(classic mode).
`#4406 <https://github.com/pybind/pybind11/pull/4406>`_
* Extend IPO/LTO detection for ICX (a.k.a IntelLLVM) compiler.
`#4402 <https://github.com/pybind/pybind11/pull/4402>`_
* Allow calling ``find_package(pybind11 CONFIG)`` multiple times from separate
directories in the same CMake project and properly link Python (new mode).
`#4401 <https://github.com/pybind/pybind11/pull/4401>`_
* ``multiprocessing_set_spawn`` in pytest fixture for added safety.
`#4377 <https://github.com/pybind/pybind11/pull/4377>`_
* Fixed a bug in two pybind11/tools cmake scripts causing "Unknown arguments specified" errors.
`#4327 <https://github.com/pybind/pybind11/pull/4327>`_
Version 2.10.1 (Oct 31, 2022)
-----------------------------
@ -95,7 +149,6 @@ Bug fixes:
finalization.
`#4192 <https://github.com/pybind/pybind11/pull/4192>`_
Performance and style:
* Reserve space in set and STL map casters if possible. This will prevent

View File

@ -29,6 +29,9 @@
#include <vector>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename type, typename SFINAE = void>
@ -88,7 +91,8 @@ public:
template <typename T_, \
::pybind11::detail::enable_if_t< \
std::is_same<type, ::pybind11::detail::remove_cv_t<T_>>::value, \
int> = 0> \
int> \
= 0> \
static ::pybind11::handle cast( \
T_ *src, ::pybind11::return_value_policy policy, ::pybind11::handle parent) { \
if (!src) \
@ -389,7 +393,7 @@ struct string_caster {
// For UTF-8 we avoid the need for a temporary `bytes` object by using
// `PyUnicode_AsUTF8AndSize`.
if (PYBIND11_SILENCE_MSVC_C4127(UTF_N == 8)) {
if (UTF_N == 8) {
Py_ssize_t size = -1;
const auto *buffer
= reinterpret_cast<const CharT *>(PyUnicode_AsUTF8AndSize(load_src.ptr(), &size));
@ -416,7 +420,7 @@ struct string_caster {
= reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
// Skip BOM for UTF-16/32
if (PYBIND11_SILENCE_MSVC_C4127(UTF_N > 8)) {
if (UTF_N > 8) {
buffer++;
length--;
}
@ -572,7 +576,7 @@ public:
// figure out how long the first encoded character is in bytes to distinguish between these
// two errors. We also allow want to allow unicode characters U+0080 through U+00FF, as
// those can fit into a single char value.
if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 8) && str_len > 1 && str_len <= 4) {
if (StringCaster::UTF_N == 8 && str_len > 1 && str_len <= 4) {
auto v0 = static_cast<unsigned char>(value[0]);
// low bits only: 0-127
// 0b110xxxxx - start of 2-byte sequence
@ -598,7 +602,7 @@ public:
// UTF-16 is much easier: we can only have a surrogate pair for values above U+FFFF, thus a
// surrogate pair with total length 2 instantly indicates a range error (but not a "your
// string was too long" error).
else if (PYBIND11_SILENCE_MSVC_C4127(StringCaster::UTF_N == 16) && str_len == 2) {
else if (StringCaster::UTF_N == 16 && str_len == 2) {
one_char = static_cast<CharT>(value[0]);
if (one_char >= 0xD800 && one_char < 0xE000) {
throw value_error("Character code point not in range(0x10000)");

View File

@ -11,14 +11,75 @@
#define PYBIND11_VERSION_MAJOR 2
#define PYBIND11_VERSION_MINOR 10
#define PYBIND11_VERSION_PATCH 1
#define PYBIND11_VERSION_PATCH 2
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
// Additional convention: 0xD = dev
#define PYBIND11_VERSION_HEX 0x020A0100
#define PYBIND11_VERSION_HEX 0x020A0200
#define PYBIND11_NAMESPACE_BEGIN(name) namespace name {
#define PYBIND11_NAMESPACE_END(name) }
// Define some generic pybind11 helper macros for warning management.
//
// Note that compiler-specific push/pop pairs are baked into the
// PYBIND11_NAMESPACE_BEGIN/PYBIND11_NAMESPACE_END pair of macros. Therefore manual
// PYBIND11_WARNING_PUSH/PYBIND11_WARNING_POP are usually only needed in `#include` sections.
//
// If you find you need to suppress a warning, please try to make the suppression as local as
// possible using these macros. Please also be sure to push/pop with the pybind11 macros. Please
// only use compiler specifics if you need to check specific versions, e.g. Apple Clang vs. vanilla
// Clang.
#if defined(_MSC_VER)
# define PYBIND11_COMPILER_MSVC
# define PYBIND11_PRAGMA(...) __pragma(__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning(push))
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning(pop))
#elif defined(__INTEL_COMPILER)
# define PYBIND11_COMPILER_INTEL
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(warning push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(warning pop)
#elif defined(__clang__)
# define PYBIND11_COMPILER_CLANG
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(clang diagnostic push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(clang diagnostic push)
#elif defined(__GNUC__)
# define PYBIND11_COMPILER_GCC
# define PYBIND11_PRAGMA(...) _Pragma(#__VA_ARGS__)
# define PYBIND11_WARNING_PUSH PYBIND11_PRAGMA(GCC diagnostic push)
# define PYBIND11_WARNING_POP PYBIND11_PRAGMA(GCC diagnostic pop)
#endif
#ifdef PYBIND11_COMPILER_MSVC
# define PYBIND11_WARNING_DISABLE_MSVC(name) PYBIND11_PRAGMA(warning(disable : name))
#else
# define PYBIND11_WARNING_DISABLE_MSVC(name)
#endif
#ifdef PYBIND11_COMPILER_CLANG
# define PYBIND11_WARNING_DISABLE_CLANG(name) PYBIND11_PRAGMA(clang diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_CLANG(name)
#endif
#ifdef PYBIND11_COMPILER_GCC
# define PYBIND11_WARNING_DISABLE_GCC(name) PYBIND11_PRAGMA(GCC diagnostic ignored name)
#else
# define PYBIND11_WARNING_DISABLE_GCC(name)
#endif
#ifdef PYBIND11_COMPILER_INTEL
# define PYBIND11_WARNING_DISABLE_INTEL(name) PYBIND11_PRAGMA(warning disable name)
#else
# define PYBIND11_WARNING_DISABLE_INTEL(name)
#endif
#define PYBIND11_NAMESPACE_BEGIN(name) \
namespace name { \
PYBIND11_WARNING_PUSH
#define PYBIND11_NAMESPACE_END(name) \
PYBIND11_WARNING_POP \
}
// Robust support for some features and loading modules compiled against different pybind versions
// requires forcing hidden visibility on pybind code, so we enforce this by setting the attribute
@ -96,13 +157,10 @@
#endif
#if !defined(PYBIND11_EXPORT_EXCEPTION)
# ifdef __MINGW32__
// workaround for:
// error: 'dllexport' implies default visibility, but xxx has already been declared with a
// different visibility
# define PYBIND11_EXPORT_EXCEPTION
# else
# if defined(__apple_build_version__)
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT
# else
# define PYBIND11_EXPORT_EXCEPTION
# endif
#endif
@ -154,9 +212,9 @@
/// Include Python header, disable linking to pythonX_d.lib on Windows in debug mode
#if defined(_MSC_VER)
# pragma warning(push)
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4505)
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
# pragma warning(disable : 4505)
# if defined(_DEBUG) && !defined(Py_DEBUG)
// Workaround for a VS 2022 issue.
// NOTE: This workaround knowingly violates the Python.h include order requirement:
@ -239,7 +297,7 @@
# define _DEBUG
# undef PYBIND11_DEBUG_MARKER
# endif
# pragma warning(pop)
PYBIND11_WARNING_POP
#endif
#include <cstddef>
@ -265,6 +323,15 @@
# define PYBIND11_HAS_U8STRING
#endif
// See description of PR #4246:
#if !defined(NDEBUG) && !defined(PY_ASSERT_GIL_HELD_INCREF_DECREF) \
&& !(defined(PYPY_VERSION) \
&& defined(_MSC_VER)) /* PyPy Windows: pytest hangs indefinitely at the end of the \
process (see PR #4268) */ \
&& !defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
# define PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF
#endif
// #define PYBIND11_STR_LEGACY_PERMISSIVE
// If DEFINED, pybind11::str can hold PyUnicodeObject or PyBytesObject
// (probably surprising and never documented, but this was the
@ -904,12 +971,6 @@ using expand_side_effects = bool[];
PYBIND11_NAMESPACE_END(detail)
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4275)
// warning C4275: An exported class was derived from a class that wasn't exported.
// Can be ignored when derived from a STL class.
#endif
/// C++ bindings of builtin Python exceptions
class PYBIND11_EXPORT_EXCEPTION builtin_exception : public std::runtime_error {
public:
@ -917,9 +978,6 @@ public:
/// Set the error using the Python C API
virtual void set_error() const = 0;
};
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
#define PYBIND11_RUNTIME_EXCEPTION(name, type) \
class PYBIND11_EXPORT_EXCEPTION name : public builtin_exception { \
@ -1148,17 +1206,6 @@ constexpr
# define PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(...)
#endif
#if defined(_MSC_VER) // All versions (as of July 2021).
// warning C4127: Conditional expression is constant
constexpr inline bool silence_msvc_c4127(bool cond) { return cond; }
# define PYBIND11_SILENCE_MSVC_C4127(...) ::pybind11::detail::silence_msvc_c4127(__VA_ARGS__)
#else
# define PYBIND11_SILENCE_MSVC_C4127(...) __VA_ARGS__
#endif
#if defined(__clang__) \
&& (defined(__apple_build_version__) /* AppleClang 13.0.0.13000029 was the only data point \
available. */ \

View File

@ -12,6 +12,9 @@
#include "class.h"
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
@ -115,7 +118,7 @@ template <typename Class>
void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
no_nullptr(ptr);
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) {
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
// We're going to try to construct an alias by moving the cpp type. Whether or not
// that succeeds, we still need to destroy the original cpp pointer (either the
// moved away leftover, if the alias construction works, or the value itself if we
@ -156,7 +159,7 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
auto *ptr = holder_helper<Holder<Class>>::get(holder);
no_nullptr(ptr);
// If we need an alias, check that the held pointer is actually an alias instance
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias && !is_alias<Class>(ptr)) {
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
"is not an alias instance");
}
@ -174,7 +177,7 @@ void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
static_assert(std::is_move_constructible<Cpp<Class>>::value,
"pybind11::init() return-by-value factory function requires a movable class");
if (PYBIND11_SILENCE_MSVC_C4127(Class::has_alias) && need_alias) {
if (Class::has_alias && need_alias) {
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
} else {
v_h.value_ptr() = new Cpp<Class>(std::move(result));
@ -206,10 +209,11 @@ struct constructor {
extra...);
}
template <typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value,
int> = 0>
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
@ -226,10 +230,11 @@ struct constructor {
extra...);
}
template <typename Class,
typename... Extra,
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value,
int> = 0>
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && !std::is_constructible<Cpp<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",
@ -245,10 +250,11 @@ struct constructor {
// Implementing class for py::init_alias<...>()
template <typename... Args>
struct alias_constructor {
template <typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value,
int> = 0>
template <
typename Class,
typename... Extra,
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int>
= 0>
static void execute(Class &cl, const Extra &...extra) {
cl.def(
"__init__",

View File

@ -43,6 +43,8 @@ using ExceptionTranslator = void (*)(std::exception_ptr);
PYBIND11_NAMESPACE_BEGIN(detail)
constexpr const char *internals_function_record_capsule_name = "pybind11_function_record_capsule";
// Forward declarations
inline PyTypeObject *make_static_property_type();
inline PyTypeObject *make_default_metaclass();
@ -182,6 +184,16 @@ struct internals {
# endif // PYBIND11_INTERNALS_VERSION > 4
// Unused if PYBIND11_SIMPLE_GIL_MANAGEMENT is defined:
PyInterpreterState *istate = nullptr;
# if PYBIND11_INTERNALS_VERSION > 4
// Note that we have to use a std::string to allocate memory to ensure a unique address
// We want unique addresses since we use pointer equality to compare function records
std::string function_record_capsule_name = internals_function_record_capsule_name;
# endif
internals() = default;
internals(const internals &other) = delete;
internals &operator=(const internals &other) = delete;
~internals() {
# if PYBIND11_INTERNALS_VERSION > 4
PYBIND11_TLS_FREE(loader_life_support_tls_key);
@ -456,9 +468,6 @@ PYBIND11_NOINLINE internals &get_internals() {
internals_ptr = new internals();
#if defined(WITH_THREAD)
# if PY_VERSION_HEX < 0x03090000
PyEval_InitThreads();
# endif
PyThreadState *tstate = PyThreadState_Get();
if (!PYBIND11_TLS_KEY_CREATE(internals_ptr->tstate)) {
pybind11_fail("get_internals: could not successfully initialize the tstate TSS key!");
@ -548,6 +557,25 @@ const char *c_str(Args &&...args) {
return strings.front().c_str();
}
inline const char *get_function_record_capsule_name() {
#if PYBIND11_INTERNALS_VERSION > 4
return get_internals().function_record_capsule_name.c_str();
#else
return nullptr;
#endif
}
// Determine whether or not the following capsule contains a pybind11 function record.
// Note that we use `internals` to make sure that only ABI compatible records are touched.
//
// This check is currently used in two places:
// - An important optimization in functional.h to avoid overhead in C++ -> Python -> C++
// - The sibling feature of cpp_function to allow overloads
inline bool is_function_record_capsule(const capsule &cap) {
// Pointer equality as we rely on internals() to ensure unique pointers
return cap.name() == get_function_record_capsule_name();
}
PYBIND11_NAMESPACE_END(detail)
/// Returns a named pointer that is shared among all extension modules (using the same

View File

@ -1006,5 +1006,14 @@ protected:
static Constructor make_move_constructor(...) { return nullptr; }
};
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
if (auto *type_data = get_type_info(ti)) {
handle th((PyObject *) type_data->type);
return th.attr("__module__").cast<std::string>() + '.'
+ th.attr("__qualname__").cast<std::string>();
}
return clean_type_id(ti.name());
}
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -9,705 +9,4 @@
#pragma once
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
#include "numpy.h"
// The C4127 suppression was introduced for Eigen 3.4.0. In theory we could
// make it version specific, or even remove it later, but considering that
// 1. C4127 is generally far more distracting than useful for modern template code, and
// 2. we definitely want to ignore any MSVC warnings originating from Eigen code,
// it is probably best to keep this around indefinitely.
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4127) // C4127: conditional expression is constant
# pragma warning(disable : 5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
#elif defined(__MINGW32__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include <Eigen/Core>
#include <Eigen/SparseCore>
#if defined(_MSC_VER)
# pragma warning(pop)
#elif defined(__MINGW32__)
# pragma GCC diagnostic pop
#endif
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType>
using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T>
using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category.
template <typename T>
using is_eigen_other
= all_of<is_template_base_of<Eigen::EigenBase, T>,
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false;
EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
: conformable{true}, rows{r}, cols{c},
// TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
: (cstride > 0 ? cstride : 0) /* outer stride */,
EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
negativestrides{rstride < 0 || cstride < 0} {}
// Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
if (negativestrides) {
return false;
}
if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; }
};
template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type
template <typename Type_>
struct EigenProps {
using Type = Type_;
using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime;
static constexpr bool row_major = Type::IsRowMajor,
vector
= Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero>
using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size
: row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim();
if (dims < 1 || dims > 2) {
return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && np_rows != rows)
|| (PYBIND11_SILENCE_MSVC_C4127(fixed_cols) && np_cols != cols)) {
return false;
}
return {np_rows, np_cols, np_rstride, np_cstride};
}
// Otherwise we're storing an n-vector. Only one of the strides will be used, but
// whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed) && size != n) {
return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
}
if (fixed) {
// The type has a fixed size, but is not a vector: abort
return false;
}
if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) {
return false;
}
return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (PYBIND11_SILENCE_MSVC_C4127(fixed_rows) && rows != n) {
return false;
}
return {n, 1, stride};
}
static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor
= const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[")
+ const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ")
+ const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]")
+
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
// be satisfied: writeable=True (for a mutable reference), and, depending on the map's
// stride options, possibly f_contiguous or c_contiguous. We include them in the
// descriptor output to provide some hint as to why a TypeError is occurring (otherwise
// it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and
// an error message that you *gave* a numpy.ndarray of the right type and dimensions.
const_name<show_writeable>(", flags.writeable", "")
+ const_name<show_c_contiguous>(", flags.c_contiguous", "")
+ const_name<show_f_contiguous>(", flags.f_contiguous", "") + const_name("]");
};
// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a;
if (props::vector) {
a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
} else {
a = array({src.rows(), src.cols()},
{elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release();
}
// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that
// reference the Eigen object's data with `base` as the python-registered base class (if omitted,
// the base will be set to None, and lifetime management is up to the caller). The numpy array is
// non-writeable if the given type is const.
template <typename props, typename Type>
handle eigen_ref_array(Type &src, handle parent = none()) {
// none here is to get past array's should-we-copy detection, which currently always
// copies when there is no base. Setting the base to None should be harmless.
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
}
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// numpy array that references the encapsulated data with a python-side reference to the capsule to
// tie its destruction to that of any dependent python objects. Const-ness is determined by
// whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src);
if (!buf) {
return false;
}
auto dims = buf.ndim();
if (dims < 1 || dims > 2) {
return false;
}
auto fits = props::conformable(buf);
if (!fits) {
return false;
}
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) {
ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
if (result < 0) { // Copy failed!
PyErr_Clear();
return false;
}
return true;
}
private:
// Cast implementation
template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::take_ownership:
case return_value_policy::automatic:
return eigen_encapsulate<props>(src);
case return_value_policy::move:
return eigen_encapsulate<props>(new CType(std::move(*src)));
case return_value_policy::copy:
return eigen_array_cast<props>(*src);
case return_value_policy::reference:
case return_value_policy::automatic_reference:
return eigen_ref_array<props>(*src);
case return_value_policy::reference_internal:
return eigen_ref_array<props>(*src, parent);
default:
throw cast_error("unhandled return_value_policy: should not happen!");
};
}
public:
// Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// If you return a non-reference const, we mark the numpy array readonly:
static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
// const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
// non-const pointer return
static handle cast(Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
// const pointer return
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &&() && { return std::move(value); }
template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private:
Type value;
};
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
private:
using props = EigenProps<MapType>;
public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing
// (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::copy:
return eigen_array_cast<props>(src);
case return_value_policy::reference_internal:
return eigen_array_cast<props>(src, parent, is_eigen_mutable_map<MapType>::value);
case return_value_policy::reference:
case return_value_policy::automatic:
case return_value_policy::automatic_reference:
return eigen_array_cast<props>(src, none(), is_eigen_mutable_map<MapType>::value);
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type");
}
}
static constexpr auto name = props::descriptor;
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator MapType() = delete;
template <typename>
using cast_op_type = MapType;
};
// We can return any map-like object (but can only load Refs, specialized next):
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType>
struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
: public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor)
std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// conversion and storage order conversion. (Note that we refuse to use this temporary copy
// when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref;
public:
bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits;
if (!need_copy) {
// We don't need a converting copy, but we also need to check whether the strides are
// compatible with the Ref's stride requirements
auto aref = reinterpret_borrow<Array>(src);
if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref);
if (!fits) {
return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true;
} else {
copy_or_ref = std::move(aref);
}
} else {
need_copy = true;
}
}
if (need_copy) {
// We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src);
if (!copy) {
return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref);
}
ref.reset();
map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map));
return true;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return *ref; }
template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor:
template <typename S>
using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it:
template <typename S>
using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic).
template <typename S>
using stride_ctor_outer
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::OuterStrideAtCompileTime == Eigen::Dynamic
&& S::InnerStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S>
using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
};
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout).
// load() is not supported, but we can cast them into the python domain by first copying to a
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>;
public:
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
handle h = eigen_encapsulate<props>(new Matrix(src));
return h;
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
static constexpr auto name = props::descriptor;
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator Type() = delete;
template <typename>
using cast_op_type = Type;
};
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
if (!src) {
return false;
}
auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) {
try {
obj = matrix_type(obj);
} catch (const error_already_set &) {
return false;
}
}
auto values = array_t<Scalar>((object) obj.attr("data"));
auto innerIndices = array_t<StorageIndex>((object) obj.attr("indices"));
auto outerIndices = array_t<StorageIndex>((object) obj.attr("indptr"));
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) {
return false;
}
value = EigenMapSparseMatrix<Scalar,
Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>(shape[0].cast<Index>(),
shape[1].cast<Index>(),
std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true;
}
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type &>(src).makeCompressed();
object matrix_type
= module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type(pybind11::make_tuple(
std::move(data), std::move(innerIndices), std::move(outerIndices)),
pybind11::make_tuple(src.rows(), src.cols()))
.release();
}
PYBIND11_TYPE_CASTER(Type,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#include "eigen/matrix.h"

View File

@ -0,0 +1,699 @@
/*
pybind11/eigen/matrix.h: Transparent conversion for dense and sparse Eigen matrices
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "../numpy.h"
/* HINT: To suppress warnings originating from the Eigen headers, use -isystem.
See also:
https://stackoverflow.com/questions/2579576/i-dir-vs-isystem-dir
https://stackoverflow.com/questions/1741816/isystem-for-ms-visual-studio-c-compiler
*/
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(5054) // https://github.com/pybind/pybind11/pull/3741
// C5054: operator '&': deprecated between enumerations of different types
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#include <Eigen/Core>
#include <Eigen/SparseCore>
PYBIND11_WARNING_POP
// Eigen prior to 3.2.7 doesn't have proper move constructors--but worse, some classes get implicit
// move constructors that break things. We could detect this an explicitly copy, but an extra copy
// of matrices seems highly undesirable.
static_assert(EIGEN_VERSION_AT_LEAST(3, 2, 7),
"Eigen matrix support in pybind11 requires Eigen >= 3.2.7");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
// Provide a convenience alias for easier pass-by-ref usage with fully dynamic strides:
using EigenDStride = Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic>;
template <typename MatrixType>
using EigenDRef = Eigen::Ref<MatrixType, 0, EigenDStride>;
template <typename MatrixType>
using EigenDMap = Eigen::Map<MatrixType, 0, EigenDStride>;
PYBIND11_NAMESPACE_BEGIN(detail)
#if EIGEN_VERSION_AT_LEAST(3, 3, 0)
using EigenIndex = Eigen::Index;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::Map<Eigen::SparseMatrix<Scalar, Flags, StorageIndex>>;
#else
using EigenIndex = EIGEN_DEFAULT_DENSE_INDEX_TYPE;
template <typename Scalar, int Flags, typename StorageIndex>
using EigenMapSparseMatrix = Eigen::MappedSparseMatrix<Scalar, Flags, StorageIndex>;
#endif
// Matches Eigen::Map, Eigen::Ref, blocks, etc:
template <typename T>
using is_eigen_dense_map = all_of<is_template_base_of<Eigen::DenseBase, T>,
std::is_base_of<Eigen::MapBase<T, Eigen::ReadOnlyAccessors>, T>>;
template <typename T>
using is_eigen_mutable_map = std::is_base_of<Eigen::MapBase<T, Eigen::WriteAccessors>, T>;
template <typename T>
using is_eigen_dense_plain
= all_of<negation<is_eigen_dense_map<T>>, is_template_base_of<Eigen::PlainObjectBase, T>>;
template <typename T>
using is_eigen_sparse = is_template_base_of<Eigen::SparseMatrixBase, T>;
// Test for objects inheriting from EigenBase<Derived> that aren't captured by the above. This
// basically covers anything that can be assigned to a dense matrix but that don't have a typical
// matrix data layout that can be copied from their .data(). For example, DiagonalMatrix and
// SelfAdjointView fall into this category.
template <typename T>
using is_eigen_other
= all_of<is_template_base_of<Eigen::EigenBase, T>,
negation<any_of<is_eigen_dense_map<T>, is_eigen_dense_plain<T>, is_eigen_sparse<T>>>>;
// Captures numpy/eigen conformability status (returned by EigenProps::conformable()):
template <bool EigenRowMajor>
struct EigenConformable {
bool conformable = false;
EigenIndex rows = 0, cols = 0;
EigenDStride stride{0, 0}; // Only valid if negativestrides is false!
bool negativestrides = false; // If true, do not use stride!
// NOLINTNEXTLINE(google-explicit-constructor)
EigenConformable(bool fits = false) : conformable{fits} {}
// Matrix type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex rstride, EigenIndex cstride)
: conformable{true}, rows{r}, cols{c},
// TODO: when Eigen bug #747 is fixed, remove the tests for non-negativity.
// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=747
stride{EigenRowMajor ? (rstride > 0 ? rstride : 0)
: (cstride > 0 ? cstride : 0) /* outer stride */,
EigenRowMajor ? (cstride > 0 ? cstride : 0)
: (rstride > 0 ? rstride : 0) /* inner stride */},
negativestrides{rstride < 0 || cstride < 0} {}
// Vector type:
EigenConformable(EigenIndex r, EigenIndex c, EigenIndex stride)
: EigenConformable(r, c, r == 1 ? c * stride : stride, c == 1 ? r : r * stride) {}
template <typename props>
bool stride_compatible() const {
// To have compatible strides, we need (on both dimensions) one of fully dynamic strides,
// matching strides, or a dimension size of 1 (in which case the stride value is
// irrelevant). Alternatively, if any dimension size is 0, the strides are not relevant
// (and numpy ≥ 1.23 sets the strides to 0 in that case, so we need to check explicitly).
if (negativestrides) {
return false;
}
if (rows == 0 || cols == 0) {
return true;
}
return (props::inner_stride == Eigen::Dynamic || props::inner_stride == stride.inner()
|| (EigenRowMajor ? cols : rows) == 1)
&& (props::outer_stride == Eigen::Dynamic || props::outer_stride == stride.outer()
|| (EigenRowMajor ? rows : cols) == 1);
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator bool() const { return conformable; }
};
template <typename Type>
struct eigen_extract_stride {
using type = Type;
};
template <typename PlainObjectType, int MapOptions, typename StrideType>
struct eigen_extract_stride<Eigen::Map<PlainObjectType, MapOptions, StrideType>> {
using type = StrideType;
};
template <typename PlainObjectType, int Options, typename StrideType>
struct eigen_extract_stride<Eigen::Ref<PlainObjectType, Options, StrideType>> {
using type = StrideType;
};
// Helper struct for extracting information from an Eigen type
template <typename Type_>
struct EigenProps {
using Type = Type_;
using Scalar = typename Type::Scalar;
using StrideType = typename eigen_extract_stride<Type>::type;
static constexpr EigenIndex rows = Type::RowsAtCompileTime, cols = Type::ColsAtCompileTime,
size = Type::SizeAtCompileTime;
static constexpr bool row_major = Type::IsRowMajor,
vector
= Type::IsVectorAtCompileTime, // At least one dimension has fixed size 1
fixed_rows = rows != Eigen::Dynamic, fixed_cols = cols != Eigen::Dynamic,
fixed = size != Eigen::Dynamic, // Fully-fixed size
dynamic = !fixed_rows && !fixed_cols; // Fully-dynamic size
template <EigenIndex i, EigenIndex ifzero>
using if_zero = std::integral_constant<EigenIndex, i == 0 ? ifzero : i>;
static constexpr EigenIndex inner_stride
= if_zero<StrideType::InnerStrideAtCompileTime, 1>::value,
outer_stride = if_zero < StrideType::OuterStrideAtCompileTime,
vector ? size
: row_major ? cols
: rows > ::value;
static constexpr bool dynamic_stride
= inner_stride == Eigen::Dynamic && outer_stride == Eigen::Dynamic;
static constexpr bool requires_row_major
= !dynamic_stride && !vector && (row_major ? inner_stride : outer_stride) == 1;
static constexpr bool requires_col_major
= !dynamic_stride && !vector && (row_major ? outer_stride : inner_stride) == 1;
// Takes an input array and determines whether we can make it fit into the Eigen type. If
// the array is a vector, we attempt to fit it into either an Eigen 1xN or Nx1 vector
// (preferring the latter if it will fit in either, i.e. for a fully dynamic matrix type).
static EigenConformable<row_major> conformable(const array &a) {
const auto dims = a.ndim();
if (dims < 1 || dims > 2) {
return false;
}
if (dims == 2) { // Matrix type: require exact match (or dynamic)
EigenIndex np_rows = a.shape(0), np_cols = a.shape(1),
np_rstride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar)),
np_cstride = a.strides(1) / static_cast<ssize_t>(sizeof(Scalar));
if ((fixed_rows && np_rows != rows) || (fixed_cols && np_cols != cols)) {
return false;
}
return {np_rows, np_cols, np_rstride, np_cstride};
}
// Otherwise we're storing an n-vector. Only one of the strides will be used, but
// whichever is used, we want the (single) numpy stride value.
const EigenIndex n = a.shape(0),
stride = a.strides(0) / static_cast<ssize_t>(sizeof(Scalar));
if (vector) { // Eigen type is a compile-time vector
if (fixed && size != n) {
return false; // Vector size mismatch
}
return {rows == 1 ? 1 : n, cols == 1 ? 1 : n, stride};
}
if (fixed) {
// The type has a fixed size, but is not a vector: abort
return false;
}
if (fixed_cols) {
// Since this isn't a vector, cols must be != 1. We allow this only if it exactly
// equals the number of elements (rows is Dynamic, and so 1 row is allowed).
if (cols != n) {
return false;
}
return {1, n, stride};
} // Otherwise it's either fully dynamic, or column dynamic; both become a column vector
if (fixed_rows && rows != n) {
return false;
}
return {n, 1, stride};
}
static constexpr bool show_writeable
= is_eigen_dense_map<Type>::value && is_eigen_mutable_map<Type>::value;
static constexpr bool show_order = is_eigen_dense_map<Type>::value;
static constexpr bool show_c_contiguous = show_order && requires_row_major;
static constexpr bool show_f_contiguous
= !show_c_contiguous && show_order && requires_col_major;
static constexpr auto descriptor
= const_name("numpy.ndarray[") + npy_format_descriptor<Scalar>::name + const_name("[")
+ const_name<fixed_rows>(const_name<(size_t) rows>(), const_name("m")) + const_name(", ")
+ const_name<fixed_cols>(const_name<(size_t) cols>(), const_name("n")) + const_name("]")
+
// For a reference type (e.g. Ref<MatrixXd>) we have other constraints that might need to
// be satisfied: writeable=True (for a mutable reference), and, depending on the map's
// stride options, possibly f_contiguous or c_contiguous. We include them in the
// descriptor output to provide some hint as to why a TypeError is occurring (otherwise
// it can be confusing to see that a function accepts a 'numpy.ndarray[float64[3,2]]' and
// an error message that you *gave* a numpy.ndarray of the right type and dimensions.
const_name<show_writeable>(", flags.writeable", "")
+ const_name<show_c_contiguous>(", flags.c_contiguous", "")
+ const_name<show_f_contiguous>(", flags.f_contiguous", "") + const_name("]");
};
// Casts an Eigen type to numpy array. If given a base, the numpy array references the src data,
// otherwise it'll make a copy. writeable lets you turn off the writeable flag for the array.
template <typename props>
handle
eigen_array_cast(typename props::Type const &src, handle base = handle(), bool writeable = true) {
constexpr ssize_t elem_size = sizeof(typename props::Scalar);
array a;
if (props::vector) {
a = array({src.size()}, {elem_size * src.innerStride()}, src.data(), base);
} else {
a = array({src.rows(), src.cols()},
{elem_size * src.rowStride(), elem_size * src.colStride()},
src.data(),
base);
}
if (!writeable) {
array_proxy(a.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return a.release();
}
// Takes an lvalue ref to some Eigen type and a (python) base object, creating a numpy array that
// reference the Eigen object's data with `base` as the python-registered base class (if omitted,
// the base will be set to None, and lifetime management is up to the caller). The numpy array is
// non-writeable if the given type is const.
template <typename props, typename Type>
handle eigen_ref_array(Type &src, handle parent = none()) {
// none here is to get past array's should-we-copy detection, which currently always
// copies when there is no base. Setting the base to None should be harmless.
return eigen_array_cast<props>(src, parent, !std::is_const<Type>::value);
}
// Takes a pointer to some dense, plain Eigen type, builds a capsule around it, then returns a
// numpy array that references the encapsulated data with a python-side reference to the capsule to
// tie its destruction to that of any dependent python objects. Const-ness is determined by
// whether or not the Type of the pointer given is const.
template <typename props, typename Type, typename = enable_if_t<is_eigen_dense_plain<Type>::value>>
handle eigen_encapsulate(Type *src) {
capsule base(src, [](void *o) { delete static_cast<Type *>(o); });
return eigen_ref_array<props>(*src, base);
}
// Type caster for regular, dense matrix types (e.g. MatrixXd), but not maps/refs/etc. of dense
// types.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
}
// Coerce into an array, but don't do type conversion yet; the copy below handles it.
auto buf = array::ensure(src);
if (!buf) {
return false;
}
auto dims = buf.ndim();
if (dims < 1 || dims > 2) {
return false;
}
auto fits = props::conformable(buf);
if (!fits) {
return false;
}
// Allocate the new type, then build a numpy reference into it
value = Type(fits.rows, fits.cols);
auto ref = reinterpret_steal<array>(eigen_ref_array<props>(value));
if (dims == 1) {
ref = ref.squeeze();
} else if (ref.ndim() == 1) {
buf = buf.squeeze();
}
int result = detail::npy_api::get().PyArray_CopyInto_(ref.ptr(), buf.ptr());
if (result < 0) { // Copy failed!
PyErr_Clear();
return false;
}
return true;
}
private:
// Cast implementation
template <typename CType>
static handle cast_impl(CType *src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::take_ownership:
case return_value_policy::automatic:
return eigen_encapsulate<props>(src);
case return_value_policy::move:
return eigen_encapsulate<props>(new CType(std::move(*src)));
case return_value_policy::copy:
return eigen_array_cast<props>(*src);
case return_value_policy::reference:
case return_value_policy::automatic_reference:
return eigen_ref_array<props>(*src);
case return_value_policy::reference_internal:
return eigen_ref_array<props>(*src, parent);
default:
throw cast_error("unhandled return_value_policy: should not happen!");
};
}
public:
// Normal returned non-reference, non-const value:
static handle cast(Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// If you return a non-reference const, we mark the numpy array readonly:
static handle cast(const Type &&src, return_value_policy /* policy */, handle parent) {
return cast_impl(&src, return_value_policy::move, parent);
}
// lvalue reference return; default (automatic) becomes copy
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
// const lvalue reference return; default (automatic) becomes copy
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
// non-const pointer return
static handle cast(Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
// const pointer return
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast_impl(src, policy, parent);
}
static constexpr auto name = props::descriptor;
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return &value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return value; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &&() && { return std::move(value); }
template <typename T>
using cast_op_type = movable_cast_op_type<T>;
private:
Type value;
};
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
private:
using props = EigenProps<MapType>;
public:
// Directly referencing a ref/map's data is a bit dangerous (whatever the map/ref points to has
// to stay around), but we'll allow it under the assumption that you know what you're doing
// (and have an appropriate keep_alive in place). We return a numpy array pointing directly at
// the ref's data (The numpy array ends up read-only if the ref was to a const matrix type.)
// Note that this means you need to ensure you don't destroy the object in some other way (e.g.
// with an appropriate keep_alive, or with a reference to a statically allocated matrix).
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
switch (policy) {
case return_value_policy::copy:
return eigen_array_cast<props>(src);
case return_value_policy::reference_internal:
return eigen_array_cast<props>(src, parent, is_eigen_mutable_map<MapType>::value);
case return_value_policy::reference:
case return_value_policy::automatic:
case return_value_policy::automatic_reference:
return eigen_array_cast<props>(src, none(), is_eigen_mutable_map<MapType>::value);
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map/Ref/Block type");
}
}
static constexpr auto name = props::descriptor;
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator MapType() = delete;
template <typename>
using cast_op_type = MapType;
};
// We can return any map-like object (but can only load Refs, specialized next):
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_map<Type>::value>> : eigen_map_caster<Type> {};
// Loader for Ref<...> arguments. See the documentation for info on how to make this work without
// copying (it requires some extra effort in many cases).
template <typename PlainObjectType, typename StrideType>
struct type_caster<
Eigen::Ref<PlainObjectType, 0, StrideType>,
enable_if_t<is_eigen_dense_map<Eigen::Ref<PlainObjectType, 0, StrideType>>::value>>
: public eigen_map_caster<Eigen::Ref<PlainObjectType, 0, StrideType>> {
private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
array::forcecast
| ((props::row_major ? props::inner_stride : props::outer_stride) == 1
? array::c_style
: (props::row_major ? props::outer_stride : props::inner_stride) == 1
? array::f_style
: 0)>;
static constexpr bool need_writeable = is_eigen_mutable_map<Type>::value;
// Delay construction (these have no default constructor)
std::unique_ptr<MapType> map;
std::unique_ptr<Type> ref;
// Our array. When possible, this is just a numpy array pointing to the source data, but
// sometimes we can't avoid copying (e.g. input is not a numpy array at all, has an
// incompatible layout, or is an array of a type that needs to be converted). Using a numpy
// temporary (rather than an Eigen temporary) saves an extra copy when we need both type
// conversion and storage order conversion. (Note that we refuse to use this temporary copy
// when loading an argument for a Ref<M> with M non-const, i.e. a read-write reference).
Array copy_or_ref;
public:
bool load(handle src, bool convert) {
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
EigenConformable<props::row_major> fits;
if (!need_copy) {
// We don't need a converting copy, but we also need to check whether the strides are
// compatible with the Ref's stride requirements
auto aref = reinterpret_borrow<Array>(src);
if (aref && (!need_writeable || aref.writeable())) {
fits = props::conformable(aref);
if (!fits) {
return false; // Incompatible dimensions
}
if (!fits.template stride_compatible<props>()) {
need_copy = true;
} else {
copy_or_ref = std::move(aref);
}
} else {
need_copy = true;
}
}
if (need_copy) {
// We need to copy: If we need a mutable reference, or we're not supposed to convert
// (either because we're in the no-convert overload pass, or because we're explicitly
// instructed not to copy (via `py::arg().noconvert()`) we have to fail loading.
if (!convert || need_writeable) {
return false;
}
Array copy = Array::ensure(src);
if (!copy) {
return false;
}
fits = props::conformable(copy);
if (!fits || !fits.template stride_compatible<props>()) {
return false;
}
copy_or_ref = std::move(copy);
loader_life_support::add_patient(copy_or_ref);
}
ref.reset();
map.reset(new MapType(data(copy_or_ref),
fits.rows,
fits.cols,
make_stride(fits.stride.outer(), fits.stride.inner())));
ref.reset(new Type(*map));
return true;
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type *() { return ref.get(); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator Type &() { return *ref; }
template <typename _T>
using cast_op_type = pybind11::detail::cast_op_type<_T>;
private:
template <typename T = Type, enable_if_t<is_eigen_mutable_map<T>::value, int> = 0>
Scalar *data(Array &a) {
return a.mutable_data();
}
template <typename T = Type, enable_if_t<!is_eigen_mutable_map<T>::value, int> = 0>
const Scalar *data(Array &a) {
return a.data();
}
// Attempt to figure out a constructor of `Stride` that will work.
// If both strides are fixed, use a default constructor:
template <typename S>
using stride_ctor_default = bool_constant<S::InnerStrideAtCompileTime != Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_default_constructible<S>::value>;
// Otherwise, if there is a two-index constructor, assume it is (outer,inner) like
// Eigen::Stride, and use it:
template <typename S>
using stride_ctor_dual
= bool_constant<!stride_ctor_default<S>::value
&& std::is_constructible<S, EigenIndex, EigenIndex>::value>;
// Otherwise, if there is a one-index constructor, and just one of the strides is dynamic, use
// it (passing whichever stride is dynamic).
template <typename S>
using stride_ctor_outer
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::OuterStrideAtCompileTime == Eigen::Dynamic
&& S::InnerStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S>
using stride_ctor_inner
= bool_constant<!any_of<stride_ctor_default<S>, stride_ctor_dual<S>>::value
&& S::InnerStrideAtCompileTime == Eigen::Dynamic
&& S::OuterStrideAtCompileTime != Eigen::Dynamic
&& std::is_constructible<S, EigenIndex>::value>;
template <typename S = StrideType, enable_if_t<stride_ctor_default<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex) {
return S();
}
template <typename S = StrideType, enable_if_t<stride_ctor_dual<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex inner) {
return S(outer, inner);
}
template <typename S = StrideType, enable_if_t<stride_ctor_outer<S>::value, int> = 0>
static S make_stride(EigenIndex outer, EigenIndex) {
return S(outer);
}
template <typename S = StrideType, enable_if_t<stride_ctor_inner<S>::value, int> = 0>
static S make_stride(EigenIndex, EigenIndex inner) {
return S(inner);
}
};
// type_caster for special matrix types (e.g. DiagonalMatrix), which are EigenBase, but not
// EigenDense (i.e. they don't have a data(), at least not with the usual matrix layout).
// load() is not supported, but we can cast them into the python domain by first copying to a
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
using props = EigenProps<Matrix>;
public:
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
handle h = eigen_encapsulate<props>(new Matrix(src));
return h;
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
return cast(*src, policy, parent);
}
static constexpr auto name = props::descriptor;
// Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return
// types but not bound arguments). We still provide them (with an explicitly delete) so that
// you end up here if you try anyway.
bool load(handle, bool) = delete;
operator Type() = delete;
template <typename>
using cast_op_type = Type;
};
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
if (!src) {
return false;
}
auto obj = reinterpret_borrow<object>(src);
object sparse_module = module_::import("scipy.sparse");
object matrix_type = sparse_module.attr(rowMajor ? "csr_matrix" : "csc_matrix");
if (!type::handle_of(obj).is(matrix_type)) {
try {
obj = matrix_type(obj);
} catch (const error_already_set &) {
return false;
}
}
auto values = array_t<Scalar>((object) obj.attr("data"));
auto innerIndices = array_t<StorageIndex>((object) obj.attr("indices"));
auto outerIndices = array_t<StorageIndex>((object) obj.attr("indptr"));
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
auto nnz = obj.attr("nnz").cast<Index>();
if (!values || !innerIndices || !outerIndices) {
return false;
}
value = EigenMapSparseMatrix<Scalar,
Type::Flags &(Eigen::RowMajor | Eigen::ColMajor),
StorageIndex>(shape[0].cast<Index>(),
shape[1].cast<Index>(),
std::move(nnz),
outerIndices.mutable_data(),
innerIndices.mutable_data(),
values.mutable_data());
return true;
}
static handle cast(const Type &src, return_value_policy /* policy */, handle /* parent */) {
const_cast<Type &>(src).makeCompressed();
object matrix_type
= module_::import("scipy.sparse").attr(rowMajor ? "csr_matrix" : "csc_matrix");
array data(src.nonZeros(), src.valuePtr());
array outerIndices((rowMajor ? src.rows() : src.cols()) + 1, src.outerIndexPtr());
array innerIndices(src.nonZeros(), src.innerIndexPtr());
return matrix_type(pybind11::make_tuple(
std::move(data), std::move(innerIndices), std::move(outerIndices)),
pybind11::make_tuple(src.rows(), src.cols()))
.release();
}
PYBIND11_TYPE_CASTER(Type,
const_name<(Type::IsRowMajor) != 0>("scipy.sparse.csr_matrix[",
"scipy.sparse.csc_matrix[")
+ npy_format_descriptor<Scalar>::name + const_name("]"));
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -0,0 +1,509 @@
/*
pybind11/eigen/tensor.h: Transparent conversion for Eigen tensors
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#pragma once
#include "../numpy.h"
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
static_assert(__GNUC__ > 5, "Eigen Tensor support in pybind11 requires GCC > 5.0");
#endif
// Disable warnings for Eigen
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4554)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
#include <unsupported/Eigen/CXX11/Tensor>
PYBIND11_WARNING_POP
static_assert(EIGEN_VERSION_AT_LEAST(3, 3, 0),
"Eigen Tensor support in pybind11 requires Eigen >= 3.3.0");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
inline bool is_tensor_aligned(const void *data) {
return (reinterpret_cast<std::size_t>(data) % EIGEN_DEFAULT_ALIGN_BYTES) == 0;
}
template <typename T>
constexpr int compute_array_flag_from_tensor() {
static_assert((static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor))
|| (static_cast<int>(T::Layout) == static_cast<int>(Eigen::ColMajor)),
"Layout must be row or column major");
return (static_cast<int>(T::Layout) == static_cast<int>(Eigen::RowMajor)) ? array::c_style
: array::f_style;
}
template <typename T>
struct eigen_tensor_helper {};
template <typename Scalar_, int NumIndices_, int Options_, typename IndexType>
struct eigen_tensor_helper<Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>> {
using Type = Eigen::Tensor<Scalar_, NumIndices_, Options_, IndexType>;
using ValidType = void;
static Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape(const Type &f) {
return f.dimensions();
}
static constexpr bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> & /*shape*/) {
return true;
}
template <typename T>
struct helper {};
template <size_t... Is>
struct helper<index_sequence<Is...>> {
static constexpr auto value = concat(const_name(((void) Is, "?"))...);
};
static constexpr auto dimensions_descriptor
= helper<decltype(make_index_sequence<Type::NumIndices>())>::value;
template <typename... Args>
static Type *alloc(Args &&...args) {
return new Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) { delete tensor; }
};
template <typename Scalar_, typename std::ptrdiff_t... Indices, int Options_, typename IndexType>
struct eigen_tensor_helper<
Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>> {
using Type = Eigen::TensorFixedSize<Scalar_, Eigen::Sizes<Indices...>, Options_, IndexType>;
using ValidType = void;
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices>
get_shape(const Type & /*f*/) {
return get_shape();
}
static constexpr Eigen::DSizes<typename Type::Index, Type::NumIndices> get_shape() {
return Eigen::DSizes<typename Type::Index, Type::NumIndices>(Indices...);
}
static bool
is_correct_shape(const Eigen::DSizes<typename Type::Index, Type::NumIndices> &shape) {
return get_shape() == shape;
}
static constexpr auto dimensions_descriptor = concat(const_name<Indices>()...);
template <typename... Args>
static Type *alloc(Args &&...args) {
Eigen::aligned_allocator<Type> allocator;
return ::new (allocator.allocate(1)) Type(std::forward<Args>(args)...);
}
static void free(Type *tensor) {
Eigen::aligned_allocator<Type> allocator;
tensor->~Type();
allocator.deallocate(tensor, 1);
}
};
template <typename Type, bool ShowDetails, bool NeedsWriteable = false>
struct get_tensor_descriptor {
static constexpr auto details
= const_name<NeedsWriteable>(", flags.writeable", "")
+ const_name<static_cast<int>(Type::Layout) == static_cast<int>(Eigen::RowMajor)>(
", flags.c_contiguous", ", flags.f_contiguous");
static constexpr auto value
= const_name("numpy.ndarray[") + npy_format_descriptor<typename Type::Scalar>::name
+ const_name("[") + eigen_tensor_helper<remove_cv_t<Type>>::dimensions_descriptor
+ const_name("]") + const_name<ShowDetails>(details, const_name("")) + const_name("]");
};
// When EIGEN_AVOID_STL_ARRAY is defined, Eigen::DSizes<T, 0> does not have the begin() member
// function. Falling back to a simple loop works around this issue.
//
// We need to disable the type-limits warning for the inner loop when size = 0.
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wtype-limits")
template <typename T, int size>
std::vector<T> convert_dsizes_to_vector(const Eigen::DSizes<T, size> &arr) {
std::vector<T> result(size);
for (size_t i = 0; i < size; i++) {
result[i] = arr[i];
}
return result;
}
template <typename T, int size>
Eigen::DSizes<T, size> get_shape_for_array(const array &arr) {
Eigen::DSizes<T, size> result;
const T *shape = arr.shape();
for (size_t i = 0; i < size; i++) {
result[i] = shape[i];
}
return result;
}
PYBIND11_WARNING_POP
template <typename Type>
struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> {
using Helper = eigen_tensor_helper<Type>;
static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value;
PYBIND11_TYPE_CASTER(Type, temp_name);
bool load(handle src, bool convert) {
if (!convert) {
if (!isinstance<array>(src)) {
return false;
}
array temp = array::ensure(src);
if (!temp) {
return false;
}
if (!convert && !temp.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
}
array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()> arr(
reinterpret_borrow<object>(src));
if (arr.ndim() != Type::NumIndices) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
auto data_pointer = arr.data();
#else
// Handle Eigen bug
auto data_pointer = const_cast<typename Type::Scalar *>(arr.data());
#endif
if (is_tensor_aligned(arr.data())) {
value = Eigen::TensorMap<const Type, Eigen::Aligned>(data_pointer, shape);
} else {
value = Eigen::TensorMap<const Type>(data_pointer, shape);
}
return true;
}
static handle cast(Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(const Type &&src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::reference
|| policy == return_value_policy::reference_internal) {
pybind11_fail("Cannot use a reference return value policy for an rvalue");
}
return cast_impl(&src, return_value_policy::move, parent);
}
static handle cast(Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const Type &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const Type *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
bool writeable = false;
switch (policy) {
case return_value_policy::move:
if (std::is_const<C>::value) {
pybind11_fail("Cannot move from a constant reference");
}
src = Helper::alloc(std::move(*src));
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::take_ownership:
if (std::is_const<C>::value) {
// This cast is ugly, and might be UB in some cases, but we don't have an
// alternative here as we must free that memory
Helper::free(const_cast<Type *>(src));
pybind11_fail("Cannot take ownership of a const reference");
}
parent_object
= capsule(src, [](void *ptr) { Helper::free(reinterpret_cast<Type *>(ptr)); });
writeable = true;
break;
case return_value_policy::copy:
writeable = true;
break;
case return_value_policy::reference:
parent_object = none();
writeable = !std::is_const<C>::value;
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
writeable = !std::is_const<C>::value;
break;
default:
pybind11_fail("pybind11 bug in eigen.h, please file a bug report");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)), src->data(), parent_object);
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
};
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<!needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
return reinterpret_cast<StoragePointerType>(arr.data());
#else
// Handle Eigen bug
return reinterpret_cast<StoragePointerType>(const_cast<void *>(arr.data()));
#endif
}
template <typename StoragePointerType,
bool needs_writeable,
enable_if_t<needs_writeable, bool> = true>
StoragePointerType get_array_data_for_type(array &arr) {
return reinterpret_cast<StoragePointerType>(arr.mutable_data());
}
template <typename T, typename = void>
struct get_storage_pointer_type;
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::StoragePointerType>> {
using SPT = typename MapType::StoragePointerType;
};
template <typename MapType>
struct get_storage_pointer_type<MapType, void_t<typename MapType::PointerArgType>> {
using SPT = typename MapType::PointerArgType;
};
template <typename Type, int Options>
struct type_caster<Eigen::TensorMap<Type, Options>,
typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> {
using MapType = Eigen::TensorMap<Type, Options>;
using Helper = eigen_tensor_helper<remove_cv_t<Type>>;
bool load(handle src, bool /*convert*/) {
// Note that we have a lot more checks here as we want to make sure to avoid copies
if (!isinstance<array>(src)) {
return false;
}
auto arr = reinterpret_borrow<array>(src);
if ((arr.flags() & compute_array_flag_from_tensor<Type>()) == 0) {
return false;
}
if (!arr.dtype().is(dtype::of<typename Type::Scalar>())) {
return false;
}
if (arr.ndim() != Type::NumIndices) {
return false;
}
constexpr bool is_aligned = (Options & Eigen::Aligned) != 0;
if (is_aligned && !is_tensor_aligned(arr.data())) {
return false;
}
auto shape = get_shape_for_array<typename Type::Index, Type::NumIndices>(arr);
if (!Helper::is_correct_shape(shape)) {
return false;
}
if (needs_writeable && !arr.writeable()) {
return false;
}
auto result = get_array_data_for_type<typename get_storage_pointer_type<MapType>::SPT,
needs_writeable>(arr);
value.reset(new MapType(std::move(result), std::move(shape)));
return true;
}
static handle cast(MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &&src, return_value_policy policy, handle parent) {
return cast_impl(&src, policy, parent);
}
static handle cast(MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast_impl(&src, policy, parent);
}
static handle cast(const MapType &src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic
|| policy == return_value_policy::automatic_reference) {
policy = return_value_policy::copy;
}
return cast(&src, policy, parent);
}
static handle cast(MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
static handle cast(const MapType *src, return_value_policy policy, handle parent) {
if (policy == return_value_policy::automatic) {
policy = return_value_policy::take_ownership;
} else if (policy == return_value_policy::automatic_reference) {
policy = return_value_policy::reference;
}
return cast_impl(src, policy, parent);
}
template <typename C>
static handle cast_impl(C *src, return_value_policy policy, handle parent) {
object parent_object;
constexpr bool writeable = !std::is_const<C>::value;
switch (policy) {
case return_value_policy::reference:
parent_object = none();
break;
case return_value_policy::reference_internal:
// Default should do the right thing
if (!parent) {
pybind11_fail("Cannot use reference internal when there is no parent");
}
parent_object = reinterpret_borrow<object>(parent);
break;
case return_value_policy::take_ownership:
delete src;
// fallthrough
default:
// move, take_ownership don't make any sense for a ref/map:
pybind11_fail("Invalid return_value_policy for Eigen Map type, must be either "
"reference or reference_internal");
}
auto result = array_t<typename Type::Scalar, compute_array_flag_from_tensor<Type>()>(
convert_dsizes_to_vector(Helper::get_shape(*src)),
src->data(),
std::move(parent_object));
if (!writeable) {
array_proxy(result.ptr())->flags &= ~detail::npy_api::NPY_ARRAY_WRITEABLE_;
}
return result.release();
}
#if EIGEN_VERSION_AT_LEAST(3, 4, 0)
static constexpr bool needs_writeable = !std::is_const<typename std::remove_pointer<
typename get_storage_pointer_type<MapType>::SPT>::type>::value;
#else
// Handle Eigen bug
static constexpr bool needs_writeable = !std::is_const<Type>::value;
#endif
protected:
// TODO: Move to std::optional once std::optional has more support
std::unique_ptr<MapType> value;
public:
static constexpr auto name = get_tensor_descriptor<Type, true, needs_writeable>::value;
explicit operator MapType *() { return value.get(); }
explicit operator MapType &() { return *value; }
explicit operator MapType &&() && { return std::move(*value); }
template <typename T_>
using cast_op_type = ::pybind11::detail::movable_cast_op_type<T_>;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@ -86,38 +86,26 @@ inline wchar_t *widen_chars(const char *safe_arg) {
return widened_arg;
}
PYBIND11_NAMESPACE_END(detail)
/** \rst
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
optional `init_signal_handlers` parameter can be used to skip the registration of
signal handlers (see the `Python documentation`_ for details). Calling this function
again after the interpreter has already been initialized is a fatal error.
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
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
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
inline void initialize_interpreter(bool init_signal_handlers = true,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
inline void precheck_interpreter() {
if (Py_IsInitialized() != 0) {
pybind11_fail("The interpreter is already running");
}
}
#if PY_VERSION_HEX < 0x030B0000
#if !defined(PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX)
# define PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX (0x03080000)
#endif
#if PY_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers,
int argc,
const char *const *argv,
bool add_program_dir_to_path) {
detail::precheck_interpreter();
Py_InitializeEx(init_signal_handlers ? 1 : 0);
# if defined(WITH_THREAD) && PY_VERSION_HEX < 0x03070000
PyEval_InitThreads();
# endif
// 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.
@ -147,26 +135,30 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
auto *pysys_argv = widened_argv.get();
PySys_SetArgvEx(argc, pysys_argv, static_cast<int>(add_program_dir_to_path));
#else
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
config.isolated = 0;
config.use_environment = 1;
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
}
#endif
PyStatus status = PyConfig_SetBytesArgv(&config, argc, const_cast<char *const *>(argv));
if (PyStatus_Exception(status)) {
PYBIND11_NAMESPACE_END(detail)
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
inline void initialize_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
detail::precheck_interpreter();
PyStatus status = PyConfig_SetBytesArgv(config, argc, const_cast<char *const *>(argv));
if (PyStatus_Exception(status) != 0) {
// A failure here indicates a character-encoding failure or the python
// interpreter out of memory. Give up.
PyConfig_Clear(&config);
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg
: "Failed to prepare CPython");
PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to prepare CPython");
}
status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
if (PyStatus_Exception(status)) {
throw std::runtime_error(PyStatus_IsError(status) ? status.err_msg
: "Failed to init CPython");
status = Py_InitializeFromConfig(config);
if (PyStatus_Exception(status) != 0) {
PyConfig_Clear(config);
throw std::runtime_error(PyStatus_IsError(status) != 0 ? status.err_msg
: "Failed to init CPython");
}
if (add_program_dir_to_path) {
PyRun_SimpleString("import sys, os.path; "
@ -174,6 +166,43 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
"os.path.abspath(os.path.dirname(sys.argv[0])) "
"if sys.argv and os.path.exists(sys.argv[0]) else '')");
}
PyConfig_Clear(config);
}
#endif
/** \rst
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
optional `init_signal_handlers` parameter can be used to skip the registration of
signal handlers (see the `Python documentation`_ for details). Calling this function
again after the interpreter has already been initialized is a fatal error.
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
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
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx
\endrst */
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_VERSION_HEX < PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
detail::initialize_interpreter_pre_pyconfig(
init_signal_handlers, argc, argv, add_program_dir_to_path);
#else
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
config.isolated = 0;
config.use_environment = 1;
config.install_signal_handlers = init_signal_handlers ? 1 : 0;
initialize_interpreter(&config, argc, argv, add_program_dir_to_path);
#endif
}
@ -261,6 +290,15 @@ public:
initialize_interpreter(init_signal_handlers, argc, argv, add_program_dir_to_path);
}
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
explicit scoped_interpreter(PyConfig *config,
int argc = 0,
const char *const *argv = nullptr,
bool add_program_dir_to_path = true) {
initialize_interpreter(config, argc, argv, add_program_dir_to_path);
}
#endif
scoped_interpreter(const scoped_interpreter &) = delete;
scoped_interpreter(scoped_interpreter &&other) noexcept { other.is_valid = false; }
scoped_interpreter &operator=(const scoped_interpreter &) = delete;

View File

@ -48,9 +48,16 @@ public:
*/
if (auto cfunc = func.cpp_function()) {
auto *cfunc_self = PyCFunction_GET_SELF(cfunc.ptr());
if (isinstance<capsule>(cfunc_self)) {
if (cfunc_self == nullptr) {
PyErr_Clear();
} else if (isinstance<capsule>(cfunc_self)) {
auto c = reinterpret_borrow<capsule>(cfunc_self);
auto *rec = (function_record *) c;
function_record *rec = nullptr;
// Check that we can safely reinterpret the capsule into a function_record
if (detail::is_function_record_capsule(c)) {
rec = c.get_pointer<function_record>();
}
while (rec != nullptr) {
if (rec->is_stateless

View File

@ -36,6 +36,8 @@ static_assert(std::is_signed<Py_intptr_t>::value, "Py_intptr_t must be signed");
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
class array; // Forward declaration
PYBIND11_NAMESPACE_BEGIN(detail)
@ -875,7 +877,7 @@ public:
*/
template <typename T, ssize_t Dims = -1>
detail::unchecked_mutable_reference<T, Dims> mutable_unchecked() & {
if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) {
if (Dims >= 0 && ndim() != Dims) {
throw std::domain_error("array has incorrect number of dimensions: "
+ std::to_string(ndim()) + "; expected "
+ std::to_string(Dims));
@ -893,7 +895,7 @@ public:
*/
template <typename T, ssize_t Dims = -1>
detail::unchecked_reference<T, Dims> unchecked() const & {
if (PYBIND11_SILENCE_MSVC_C4127(Dims >= 0) && ndim() != Dims) {
if (Dims >= 0 && ndim() != Dims) {
throw std::domain_error("array has incorrect number of dimensions: "
+ std::to_string(ndim()) + "; expected "
+ std::to_string(Dims));
@ -1469,7 +1471,7 @@ private:
}
// Extract name, offset and format descriptor for a struct field
# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, # Field)
# define PYBIND11_FIELD_DESCRIPTOR(T, Field) PYBIND11_FIELD_DESCRIPTOR_EX(T, Field, #Field)
// The main idea of this macro is borrowed from https://github.com/swansontec/map-macro
// (C) William Swanson, Paul Fultz
@ -1865,9 +1867,10 @@ private:
}
auto result = returned_array::create(trivial, shape);
PYBIND11_WARNING_PUSH
#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wreturn-std-move"
PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move")
#endif
if (size == 0) {
@ -1883,9 +1886,7 @@ private:
}
return result;
#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
# pragma clang diagnostic pop
#endif
PYBIND11_WARNING_POP
}
template <size_t... Index, size_t... VIndex, size_t... BIndex>

View File

@ -47,6 +47,16 @@ public:
return *this;
}
options &disable_enum_members_docstring() & {
global_state().show_enum_members_docstring = false;
return *this;
}
options &enable_enum_members_docstring() & {
global_state().show_enum_members_docstring = true;
return *this;
}
// Getter methods (return the global state):
static bool show_user_defined_docstrings() {
@ -55,6 +65,10 @@ public:
static bool show_function_signatures() { return global_state().show_function_signatures; }
static bool show_enum_members_docstring() {
return global_state().show_enum_members_docstring;
}
// This type is not meant to be allocated on the heap.
void *operator new(size_t) = delete;
@ -63,6 +77,8 @@ private:
bool show_user_defined_docstrings = true; //< Include user-supplied texts in docstrings.
bool show_function_signatures = true; //< Include auto-generated function signatures
// in docstrings.
bool show_enum_members_docstring = true; //< Include auto-generated member list in enum
// docstrings.
};
static state &global_state() {

View File

@ -35,6 +35,8 @@
# include <cxxabi.h>
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
This warning is about ABI compatibility, not code health.
It is only actually needed in a couple places, but apparently GCC 7 "generates this warning if
@ -43,11 +45,10 @@
No other GCC version generates this warning.
*/
#if defined(__GNUC__) && __GNUC__ == 7
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wnoexcept-type"
PYBIND11_WARNING_DISABLE_GCC("-Wnoexcept-type")
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
PYBIND11_NAMESPACE_BEGIN(detail)
@ -177,22 +178,22 @@ protected:
auto *rec = unique_rec.get();
/* Store the capture object directly in the function record if there is enough space */
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(capture) <= sizeof(rec->data))) {
if (sizeof(capture) <= sizeof(rec->data)) {
/* Without these pragmas, GCC warns that there might not be
enough space to use the placement new operator. However, the
'if' statement above ensures that this is the case. */
#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wplacement-new"
PYBIND11_WARNING_PUSH
#if defined(__GNUG__) && __GNUC__ >= 6
PYBIND11_WARNING_DISABLE_GCC("-Wplacement-new")
#endif
new ((capture *) &rec->data) capture{std::forward<Func>(f)};
#if defined(__GNUG__) && __GNUC__ >= 6 && !defined(__clang__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
#if !PYBIND11_HAS_STD_LAUNDER
PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
#endif
// UB without std::launder, but without breaking ABI and/or
// a significant refactoring it's "impossible" to solve.
if (!std::is_trivially_destructible<capture>::value) {
@ -202,9 +203,7 @@ protected:
data->~capture();
};
}
#if defined(__GNUG__) && !PYBIND11_HAS_STD_LAUNDER && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
PYBIND11_WARNING_POP
} else {
rec->data[0] = new capture{std::forward<Func>(f)};
rec->free_data = [](function_record *r) { delete ((capture *) r->data[0]); };
@ -468,13 +467,20 @@ protected:
if (rec->sibling) {
if (PyCFunction_Check(rec->sibling.ptr())) {
auto *self = PyCFunction_GET_SELF(rec->sibling.ptr());
capsule rec_capsule = isinstance<capsule>(self) ? reinterpret_borrow<capsule>(self)
: capsule(self);
chain = (detail::function_record *) rec_capsule;
/* Never append a method to an overload chain of a parent class;
instead, hide the parent's overloads in this case */
if (!chain->scope.is(rec->scope)) {
if (!isinstance<capsule>(self)) {
chain = nullptr;
} else {
auto rec_capsule = reinterpret_borrow<capsule>(self);
if (detail::is_function_record_capsule(rec_capsule)) {
chain = rec_capsule.get_pointer<detail::function_record>();
/* Never append a method to an overload chain of a parent class;
instead, hide the parent's overloads in this case */
if (!chain->scope.is(rec->scope)) {
chain = nullptr;
}
} else {
chain = nullptr;
}
}
}
// Don't trigger for things like the default __init__, which are wrapper_descriptors
@ -496,6 +502,7 @@ protected:
capsule rec_capsule(unique_rec.release(),
[](void *ptr) { destruct((detail::function_record *) ptr); });
rec_capsule.set_name(detail::get_function_record_capsule_name());
guarded_strdup.release();
object scope_module;
@ -661,10 +668,13 @@ protected:
/// Main dispatch logic for calls to functions bound using pybind11
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
using namespace detail;
assert(isinstance<capsule>(self));
/* Iterator over the list of potentially admissible overloads */
const function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr),
const function_record *overloads = reinterpret_cast<function_record *>(
PyCapsule_GetPointer(self, get_function_record_capsule_name())),
*it = overloads;
assert(overloads != nullptr);
/* Need to know how many arguments + keyword arguments there are to pick the right
overload */
@ -1416,9 +1426,9 @@ template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
void call_operator_delete(T *p, size_t, size_t) {
T::operator delete(p);
}
template <
typename T,
enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0>
template <typename T,
enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int>
= 0>
void call_operator_delete(T *p, size_t s, size_t) {
T::operator delete(p, s);
}
@ -1830,8 +1840,7 @@ private:
if (holder_ptr) {
init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible<holder_type>());
v_h.set_holder_constructed();
} else if (PYBIND11_SILENCE_MSVC_C4127(detail::always_construct_holder<holder_type>::value)
|| inst->owned) {
} else if (detail::always_construct_holder<holder_type>::value || inst->owned) {
new (std::addressof(v_h.holder<holder_type>())) holder_type(v_h.value_ptr<type>());
v_h.set_holder_constructed();
}
@ -1871,9 +1880,22 @@ private:
static detail::function_record *get_function_record(handle h) {
h = detail::get_function(h);
return h ? (detail::function_record *) reinterpret_borrow<capsule>(
PyCFunction_GET_SELF(h.ptr()))
: nullptr;
if (!h) {
return nullptr;
}
handle func_self = PyCFunction_GET_SELF(h.ptr());
if (!func_self) {
throw error_already_set();
}
if (!isinstance<capsule>(func_self)) {
return nullptr;
}
auto cap = reinterpret_borrow<capsule>(func_self);
if (!detail::is_function_record_capsule(cap)) {
return nullptr;
}
return cap.get_pointer<detail::function_record>();
}
};
@ -1950,29 +1972,35 @@ struct enum_base {
name("name"),
is_method(m_base));
m_base.attr("__doc__") = static_property(
cpp_function(
[](handle arg) -> std::string {
std::string docstring;
dict entries = arg.attr("__entries");
if (((PyTypeObject *) arg.ptr())->tp_doc) {
docstring += std::string(((PyTypeObject *) arg.ptr())->tp_doc) + "\n\n";
}
docstring += "Members:";
for (auto kv : entries) {
auto key = std::string(pybind11::str(kv.first));
auto comment = kv.second[int_(1)];
docstring += "\n\n " + key;
if (!comment.is_none()) {
docstring += " : " + (std::string) pybind11::str(comment);
if (options::show_enum_members_docstring()) {
m_base.attr("__doc__") = static_property(
cpp_function(
[](handle arg) -> std::string {
std::string docstring;
dict entries = arg.attr("__entries");
if (((PyTypeObject *) arg.ptr())->tp_doc) {
docstring += std::string(
reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
docstring += "\n\n";
}
}
return docstring;
},
name("__doc__")),
none(),
none(),
"");
docstring += "Members:";
for (auto kv : entries) {
auto key = std::string(pybind11::str(kv.first));
auto comment = kv.second[int_(1)];
docstring += "\n\n ";
docstring += key;
if (!comment.is_none()) {
docstring += " : ";
docstring += pybind11::str(comment).cast<std::string>();
}
}
return docstring;
},
name("__doc__")),
none(),
none(),
"");
}
m_base.attr("__members__") = static_property(cpp_function(
[](handle arg) -> dict {
@ -2852,7 +2880,3 @@ inline function get_overload(const T *this_ptr, const char *name) {
PYBIND11_OVERRIDE_PURE(PYBIND11_TYPE(ret_type), PYBIND11_TYPE(cname), fn, __VA_ARGS__);
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#if defined(__GNUC__) && __GNUC__ == 7
# pragma GCC diagnostic pop // -Wnoexcept-type
#endif

View File

@ -33,6 +33,8 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_WARNING_DISABLE_MSVC(4127)
/* A few forward declarations */
class handle;
class object;
@ -230,7 +232,8 @@ public:
detail::enable_if_t<detail::all_of<detail::none_of<std::is_base_of<handle, T>,
detail::is_pyobj_ptr_or_nullptr_t<T>>,
std::is_convertible<T, PyObject *>>::value,
int> = 0>
int>
= 0>
// NOLINTNEXTLINE(google-explicit-constructor)
handle(T &obj) : m_ptr(obj) {}
@ -246,6 +249,11 @@ public:
const handle &inc_ref() const & {
#ifdef PYBIND11_HANDLE_REF_DEBUG
inc_ref_counter(1);
#endif
#if defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
if (m_ptr != nullptr && !PyGILState_Check()) {
throw std::runtime_error("pybind11::handle::inc_ref() PyGILState_Check() failure.");
}
#endif
Py_XINCREF(m_ptr);
return *this;
@ -257,6 +265,11 @@ public:
this function automatically. Returns a reference to itself.
\endrst */
const handle &dec_ref() const & {
#if defined(PYBIND11_ASSERT_GIL_HELD_INCREF_DECREF)
if (m_ptr != nullptr && !PyGILState_Check()) {
throw std::runtime_error("pybind11::handle::dec_ref() PyGILState_Check() failure.");
}
#endif
Py_XDECREF(m_ptr);
return *this;
}
@ -623,12 +636,6 @@ inline std::string error_string() {
PYBIND11_NAMESPACE_END(detail)
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4275 4251)
// warning C4275: An exported class was derived from a class that wasn't exported.
// Can be ignored when derived from a STL class.
#endif
/// Fetch and hold an error which was already set in Python. An instance of this is typically
/// thrown to propagate python-side errors back through C++ which can either be caught manually or
/// else falls back to the function dispatcher (which then raises the captured error back to
@ -688,9 +695,6 @@ private:
/// crashes (undefined behavior) if the Python interpreter is finalizing.
static void m_fetched_error_deleter(detail::error_fetch_and_normalize *raw_ptr);
};
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
/// 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.
@ -893,10 +897,8 @@ object object_or_cast(T &&o);
// Match a PyObject*, which we want to convert directly to handle via its converting constructor
inline handle object_or_cast(PyObject *ptr) { return ptr; }
#if defined(_MSC_VER) && _MSC_VER < 1920
# pragma warning(push)
# pragma warning(disable : 4522) // warning C4522: multiple assignment operators specified
#endif
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4522) // warning C4522: multiple assignment operators specified
template <typename Policy>
class accessor : public object_api<accessor<Policy>> {
using key_type = typename Policy::key_type;
@ -960,9 +962,7 @@ private:
key_type key;
mutable object cache;
};
#if defined(_MSC_VER) && _MSC_VER < 1920
# pragma warning(pop)
#endif
PYBIND11_WARNING_POP
PYBIND11_NAMESPACE_BEGIN(accessor_policies)
struct obj_attr {
@ -1702,7 +1702,7 @@ PYBIND11_NAMESPACE_BEGIN(detail)
// unsigned type: (A)-1 != (B)-1 when A and B are unsigned types of different sizes).
template <typename Unsigned>
Unsigned as_unsigned(PyObject *o) {
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(Unsigned) <= sizeof(unsigned long))) {
if (sizeof(Unsigned) <= sizeof(unsigned long)) {
unsigned long v = PyLong_AsUnsignedLong(o);
return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
}
@ -1719,7 +1719,7 @@ public:
template <typename T, detail::enable_if_t<std::is_integral<T>::value, int> = 0>
// NOLINTNEXTLINE(google-explicit-constructor)
int_(T value) {
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(T) <= sizeof(long))) {
if (sizeof(T) <= sizeof(long)) {
if (std::is_signed<T>::value) {
m_ptr = PyLong_FromLong((long) value);
} else {

View File

@ -10,10 +10,13 @@
#pragma once
#include "detail/common.h"
#include "detail/type_caster_base.h"
#include "cast.h"
#include "operators.h"
#include <algorithm>
#include <sstream>
#include <type_traits>
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
@ -636,18 +639,52 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name)
"Return the canonical string representation of this map.");
}
template <typename Map>
template <typename KeyType>
struct keys_view {
Map &map;
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual bool contains(const KeyType &k) = 0;
virtual bool contains(const object &k) = 0;
virtual ~keys_view() = default;
};
template <typename Map>
template <typename MappedType>
struct values_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~values_view() = default;
};
template <typename KeyType, typename MappedType>
struct items_view {
virtual size_t len() = 0;
virtual iterator iter() = 0;
virtual ~items_view() = default;
};
template <typename Map, typename KeysView>
struct KeysViewImpl : public KeysView {
explicit KeysViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_key_iterator(map.begin(), map.end()); }
bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); }
bool contains(const object &) override { return false; }
Map &map;
};
template <typename Map>
struct items_view {
template <typename Map, typename ValuesView>
struct ValuesViewImpl : public ValuesView {
explicit ValuesViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_value_iterator(map.begin(), map.end()); }
Map &map;
};
template <typename Map, typename ItemsView>
struct ItemsViewImpl : public ItemsView {
explicit ItemsViewImpl(Map &map) : map(map) {}
size_t len() override { return map.size(); }
iterator iter() override { return make_iterator(map.begin(), map.end()); }
Map &map;
};
@ -657,9 +694,11 @@ template <typename Map, typename holder_type = std::unique_ptr<Map>, typename...
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using KeysView = detail::keys_view<Map>;
using ValuesView = detail::values_view<Map>;
using ItemsView = detail::items_view<Map>;
using StrippedKeyType = detail::remove_cvref_t<KeyType>;
using StrippedMappedType = detail::remove_cvref_t<MappedType>;
using KeysView = detail::keys_view<StrippedKeyType>;
using ValuesView = detail::values_view<StrippedMappedType>;
using ItemsView = detail::items_view<StrippedKeyType, StrippedMappedType>;
using Class_ = class_<Map, holder_type>;
// If either type is a non-module-local bound type then make the map binding non-local as well;
@ -673,12 +712,57 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
}
Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward<Args>(args)...);
class_<KeysView> keys_view(
scope, ("KeysView[" + name + "]").c_str(), pybind11::module_local(local));
class_<ValuesView> values_view(
scope, ("ValuesView[" + name + "]").c_str(), pybind11::module_local(local));
class_<ItemsView> items_view(
scope, ("ItemsView[" + name + "]").c_str(), pybind11::module_local(local));
static constexpr auto key_type_descr = detail::make_caster<KeyType>::name;
static constexpr auto mapped_type_descr = detail::make_caster<MappedType>::name;
std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text);
// If key type isn't properly wrapped, fall back to C++ names
if (key_type_name == "%") {
key_type_name = detail::type_info_description(typeid(KeyType));
}
// Similarly for value type:
if (mapped_type_name == "%") {
mapped_type_name = detail::type_info_description(typeid(MappedType));
}
// Wrap KeysView[KeyType] if it wasn't already wrapped
if (!detail::get_type_info(typeid(KeysView))) {
class_<KeysView> keys_view(
scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local));
keys_view.def("__len__", &KeysView::len);
keys_view.def("__iter__",
&KeysView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const KeyType &)>(&KeysView::contains));
// Fallback for when the object is not of the key type
keys_view.def("__contains__",
static_cast<bool (KeysView::*)(const object &)>(&KeysView::contains));
}
// Similarly for ValuesView:
if (!detail::get_type_info(typeid(ValuesView))) {
class_<ValuesView> values_view(scope,
("ValuesView[" + mapped_type_name + "]").c_str(),
pybind11::module_local(local));
values_view.def("__len__", &ValuesView::len);
values_view.def("__iter__",
&ValuesView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
// Similarly for ItemsView:
if (!detail::get_type_info(typeid(ItemsView))) {
class_<ItemsView> items_view(
scope,
("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(),
pybind11::module_local(local));
items_view.def("__len__", &ItemsView::len);
items_view.def("__iter__",
&ItemsView::iter,
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
}
cl.def(init<>());
@ -698,19 +782,25 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
cl.def(
"keys",
[](Map &m) { return KeysView{m}; },
[](Map &m) {
return std::unique_ptr<KeysView>(new detail::KeysViewImpl<Map, KeysView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def(
"values",
[](Map &m) { return ValuesView{m}; },
[](Map &m) {
return std::unique_ptr<ValuesView>(new detail::ValuesViewImpl<Map, ValuesView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
cl.def(
"items",
[](Map &m) { return ItemsView{m}; },
[](Map &m) {
return std::unique_ptr<ItemsView>(new detail::ItemsViewImpl<Map, ItemsView>(m));
},
keep_alive<0, 1>() /* Essential: keep map alive while view exists */
);
@ -749,36 +839,6 @@ class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&
cl.def("__len__", &Map::size);
keys_view.def("__len__", [](KeysView &view) { return view.map.size(); });
keys_view.def(
"__iter__",
[](KeysView &view) { return make_key_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
keys_view.def("__contains__", [](KeysView &view, const KeyType &k) -> bool {
auto it = view.map.find(k);
if (it == view.map.end()) {
return false;
}
return true;
});
// Fallback for when the object is not of the key type
keys_view.def("__contains__", [](KeysView &, const object &) -> bool { return false; });
values_view.def("__len__", [](ValuesView &view) { return view.map.size(); });
values_view.def(
"__iter__",
[](ValuesView &view) { return make_value_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
items_view.def("__len__", [](ItemsView &view) { return view.map.size(); });
items_view.def(
"__iter__",
[](ItemsView &view) { return make_iterator(view.map.begin(), view.map.end()); },
keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */
);
return cl;
}

View File

@ -8,5 +8,5 @@ def _to_int(s: str) -> Union[int, str]:
return s
__version__ = "2.10.1"
__version__ = "2.10.2"
version_info = tuple(_to_int(s) for s in __version__.split("."))

View File

@ -46,5 +46,5 @@ zip_safe = False
max-line-length = 120
show_source = True
exclude = .git, __pycache__, build, dist, docs, tools, venv
extend-ignore = E203, E722, B903, B950
extend-select = B9
extend-ignore = E203, E722
extend-select = B902, B904

View File

@ -128,7 +128,8 @@ set(PYBIND11_TEST_FILES
test_custom_type_casters
test_custom_type_setup
test_docstring_options
test_eigen
test_eigen_matrix
test_eigen_tensor
test_enum
test_eval
test_exceptions
@ -233,7 +234,10 @@ list(GET PYBIND11_EIGEN_VERSION_AND_HASH 1 PYBIND11_EIGEN_VERSION_HASH)
# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
# keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed"
# skip message).
list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I)
list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I EQUAL -1)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
endif()
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
# Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake).
# Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also
@ -288,13 +292,34 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING})
endif()
message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}")
if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
tests_extra_targets("test_eigen_tensor.py" "eigen_tensor_avoid_stl_array")
endif()
else()
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
message(
STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download")
endif()
endif()
# Some code doesn't support gcc 4
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
endif()
# Optional dependency for some tests (boost::variant is only supported with version >= 1.56)
find_package(Boost 1.56)

View File

@ -7,6 +7,8 @@ Adds docstring and exceptions message sanitizers.
import contextlib
import difflib
import gc
import multiprocessing
import os
import re
import textwrap
@ -15,6 +17,22 @@ import pytest
# Early diagnostic for failed imports
import pybind11_tests
@pytest.fixture(scope="session", autouse=True)
def always_forkserver_on_unix():
if os.name == "nt":
return
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
# In a nutshell: fork() after starting threads == flakiness in the form of deadlocks.
# It is actually a well-known pitfall, unfortunately without guard rails.
# "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py,
# visit the issuecomment link above for details).
# Windows does not have fork() and the associated pitfall, therefore it is best left
# running with defaults.
multiprocessing.set_start_method("forkserver")
_long_marker = re.compile(r"([0-9])L")
_hexadecimal = re.compile(r"0x[0-9a-fA-F]+")

View File

@ -0,0 +1,14 @@
/*
tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#ifndef EIGEN_AVOID_STL_ARRAY
# define EIGEN_AVOID_STL_ARRAY
#endif
#include "test_eigen_tensor.inl"
PYBIND11_MODULE(eigen_tensor_avoid_stl_array, m) { eigen_tensor_test::test_module(m); }

View File

@ -55,6 +55,11 @@ detail_headers = {
"include/pybind11/detail/typeid.h",
}
eigen_headers = {
"include/pybind11/eigen/matrix.h",
"include/pybind11/eigen/tensor.h",
}
stl_headers = {
"include/pybind11/stl/filesystem.h",
}
@ -82,7 +87,7 @@ py_files = {
"setup_helpers.py",
}
headers = main_headers | detail_headers | stl_headers
headers = main_headers | detail_headers | eigen_headers | stl_headers
src_files = headers | cmake_files | pkgconfig_files
all_files = src_files | py_files
@ -92,6 +97,7 @@ sdist_files = {
"pybind11/include",
"pybind11/include/pybind11",
"pybind11/include/pybind11/detail",
"pybind11/include/pybind11/eigen",
"pybind11/include/pybind11/stl",
"pybind11/share",
"pybind11/share/cmake",

View File

@ -73,6 +73,9 @@ PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11)
TEST_SUBMODULE(builtin_casters, m) {
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_MSVC(4127)
// test_simple_string
m.def("string_roundtrip", [](const char *s) { return s; });
@ -86,7 +89,7 @@ TEST_SUBMODULE(builtin_casters, m) {
std::wstring wstr;
wstr.push_back(0x61); // a
wstr.push_back(0x2e18); // ⸘
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
if (sizeof(wchar_t) == 2) {
wstr.push_back(mathbfA16_1);
wstr.push_back(mathbfA16_2);
} // 𝐀, utf16
@ -113,7 +116,7 @@ TEST_SUBMODULE(builtin_casters, m) {
// Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger
// UnicodeDecodeError
m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
if (sizeof(wchar_t) == 2) {
m.def("bad_wchar_string", [=]() {
return std::wstring({wchar_t(0x61), wchar_t(0xd800)});
});
@ -384,4 +387,6 @@ TEST_SUBMODULE(builtin_casters, m) {
m.def("takes_const_ref", [](const ConstRefCasted &x) { return x.tag; });
m.def("takes_const_ref_wrap",
[](std::reference_wrapper<const ConstRefCasted> x) { return x.get().tag; });
PYBIND11_WARNING_POP
}

View File

@ -240,4 +240,41 @@ TEST_SUBMODULE(callbacks, m) {
f();
}
});
auto *custom_def = []() {
static PyMethodDef def;
def.ml_name = "example_name";
def.ml_doc = "Example doc";
def.ml_meth = [](PyObject *, PyObject *args) -> PyObject * {
if (PyTuple_Size(args) != 1) {
throw std::runtime_error("Invalid number of arguments for example_name");
}
PyObject *first = PyTuple_GetItem(args, 0);
if (!PyLong_Check(first)) {
throw std::runtime_error("Invalid argument to example_name");
}
auto result = py::cast(PyLong_AsLong(first) * 9);
return result.release().ptr();
};
def.ml_flags = METH_VARARGS;
return &def;
}();
// rec_capsule with name that has the same value (but not pointer) as our internal one
// This capsule should be detected by our code as foreign and not inspected as the pointers
// shouldn't match
constexpr const char *rec_capsule_name
= pybind11::detail::internals_function_record_capsule_name;
py::capsule rec_capsule(std::malloc(1), [](void *data) { std::free(data); });
rec_capsule.set_name(rec_capsule_name);
m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));
// This test requires a new ABI version to pass
#if PYBIND11_INTERNALS_VERSION > 4
// rec_capsule with nullptr name
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));
#else
m.add_object("custom_function2", py::none());
#endif
}

View File

@ -193,3 +193,16 @@ def test_callback_num_times():
if len(rates) > 1:
print("Min Mean Max")
print(f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}")
def test_custom_func():
assert m.custom_function(4) == 36
assert m.roundtrip(m.custom_function)(4) == 36
@pytest.mark.skipif(
m.custom_function2 is None, reason="Current PYBIND11_INTERNALS_VERSION too low"
)
def test_custom_func2():
assert m.custom_function2(3) == 27
assert m.roundtrip(m.custom_function2)(3) == 27

View File

@ -22,10 +22,8 @@
#include <utility>
#if defined(_MSC_VER)
# pragma warning(disable : 4324)
PYBIND11_WARNING_DISABLE_MSVC(4324)
// warning C4324: structure was padded due to alignment specifier
#endif
// test_brace_initialization
struct NoBraceInitialization {

View File

@ -52,15 +52,12 @@ int f1(int x) noexcept { return x + 1; }
#endif
int f2(int x) noexcept(true) { return x + 2; }
int f3(int x) noexcept(false) { return x + 3; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated"
#endif
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated")
PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated")
// NOLINTNEXTLINE(modernize-use-noexcept)
int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true)
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
PYBIND11_WARNING_POP
struct C {
int m1(int x) noexcept { return x - 1; }
int m2(int x) const noexcept { return x - 2; }
@ -68,17 +65,14 @@ struct C {
int m4(int x) const noexcept(true) { return x - 4; }
int m5(int x) noexcept(false) { return x - 5; }
int m6(int x) const noexcept(false) { return x - 6; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated"
#endif
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated")
PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated")
// NOLINTNEXTLINE(modernize-use-noexcept)
int m7(int x) throw() { return x - 7; }
// NOLINTNEXTLINE(modernize-use-noexcept)
int m8(int x) const throw() { return x - 8; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
PYBIND11_WARNING_POP
};
} // namespace test_exc_sp
@ -126,14 +120,12 @@ TEST_SUBMODULE(constants_and_functions, m) {
.def("m8", &C::m8);
m.def("f1", f1);
m.def("f2", f2);
#if defined(__INTEL_COMPILER)
# pragma warning push
# pragma warning disable 878 // incompatible exception specifications
#endif
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_INTEL(878) // incompatible exception specifications
m.def("f3", f3);
#if defined(__INTEL_COMPILER)
# pragma warning pop
#endif
PYBIND11_WARNING_POP
m.def("f4", f4);
// test_function_record_leaks

View File

@ -85,4 +85,57 @@ TEST_SUBMODULE(docstring_options, m) {
&DocstringTestFoo::setValue,
"This is a property docstring");
}
{
enum class DocstringTestEnum1 { Member1, Member2 };
py::enum_<DocstringTestEnum1>(m, "DocstringTestEnum1", "Enum docstring")
.value("Member1", DocstringTestEnum1::Member1)
.value("Member2", DocstringTestEnum1::Member2);
}
{
py::options options;
options.enable_enum_members_docstring();
enum class DocstringTestEnum2 { Member1, Member2 };
py::enum_<DocstringTestEnum2>(m, "DocstringTestEnum2", "Enum docstring")
.value("Member1", DocstringTestEnum2::Member1)
.value("Member2", DocstringTestEnum2::Member2);
}
{
py::options options;
options.disable_enum_members_docstring();
enum class DocstringTestEnum3 { Member1, Member2 };
py::enum_<DocstringTestEnum3>(m, "DocstringTestEnum3", "Enum docstring")
.value("Member1", DocstringTestEnum3::Member1)
.value("Member2", DocstringTestEnum3::Member2);
}
{
py::options options;
options.disable_user_defined_docstrings();
enum class DocstringTestEnum4 { Member1, Member2 };
py::enum_<DocstringTestEnum4>(m, "DocstringTestEnum4", "Enum docstring")
.value("Member1", DocstringTestEnum4::Member1)
.value("Member2", DocstringTestEnum4::Member2);
}
{
py::options options;
options.disable_user_defined_docstrings();
options.disable_enum_members_docstring();
enum class DocstringTestEnum5 { Member1, Member2 };
py::enum_<DocstringTestEnum5>(m, "DocstringTestEnum5", "Enum docstring")
.value("Member1", DocstringTestEnum5::Member1)
.value("Member2", DocstringTestEnum5::Member2);
}
}

View File

@ -39,3 +39,26 @@ def test_docstring_options():
# Suppression of user-defined docstrings for non-function objects
assert not m.DocstringTestFoo.__doc__
assert not m.DocstringTestFoo.value_prop.__doc__
# Check existig behaviour of enum docstings
assert (
m.DocstringTestEnum1.__doc__
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"
)
# options.enable_enum_members_docstring()
assert (
m.DocstringTestEnum2.__doc__
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"
)
# options.disable_enum_members_docstring()
assert m.DocstringTestEnum3.__doc__ == "Enum docstring"
# options.disable_user_defined_docstrings()
assert m.DocstringTestEnum4.__doc__ == "Members:\n\n Member1\n\n Member2"
# options.disable_user_defined_docstrings()
# options.disable_enum_members_docstring()
# When all options are disabled, no docstring (instead of an empty one) should be generated
assert m.DocstringTestEnum5.__doc__ is None

View File

@ -7,15 +7,13 @@
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/eigen.h>
#include <pybind11/eigen/matrix.h>
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#if defined(_MSC_VER)
# pragma warning(disable : 4996) // C4996: std::unary_negation is deprecated
#endif
PYBIND11_WARNING_DISABLE_MSVC(4996)
#include <Eigen/Cholesky>
@ -81,7 +79,7 @@ struct CustomOperatorNew {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
};
TEST_SUBMODULE(eigen, m) {
TEST_SUBMODULE(eigen_matrix, m) {
using FixedMatrixR = Eigen::Matrix<float, 5, 6, Eigen::RowMajor>;
using FixedMatrixC = Eigen::Matrix<float, 5, 6>;
using DenseMatrixR = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

View File

@ -3,7 +3,7 @@ import pytest
from pybind11_tests import ConstructorStats
np = pytest.importorskip("numpy")
m = pytest.importorskip("pybind11_tests.eigen")
m = pytest.importorskip("pybind11_tests.eigen_matrix")
ref = np.array(

View File

@ -0,0 +1,18 @@
/*
tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#define PYBIND11_TEST_EIGEN_TENSOR_NAMESPACE eigen_tensor
#ifdef EIGEN_AVOID_STL_ARRAY
# undef EIGEN_AVOID_STL_ARRAY
#endif
#include "test_eigen_tensor.inl"
#include "pybind11_tests.h"
test_initializer egien_tensor("eigen_tensor", eigen_tensor_test::test_module);

333
tests/test_eigen_tensor.inl Normal file
View File

@ -0,0 +1,333 @@
/*
tests/eigen_tensor.cpp -- automatic conversion of Eigen Tensor
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/eigen/tensor.h>
PYBIND11_NAMESPACE_BEGIN(eigen_tensor_test)
namespace py = pybind11;
PYBIND11_WARNING_DISABLE_MSVC(4127)
template <typename M>
void reset_tensor(M &x) {
for (int i = 0; i < x.dimension(0); i++) {
for (int j = 0; j < x.dimension(1); j++) {
for (int k = 0; k < x.dimension(2); k++) {
x(i, j, k) = i * (5 * 2) + j * 2 + k;
}
}
}
}
template <typename M>
bool check_tensor(M &x) {
for (int i = 0; i < x.dimension(0); i++) {
for (int j = 0; j < x.dimension(1); j++) {
for (int k = 0; k < x.dimension(2); k++) {
if (x(i, j, k) != (i * (5 * 2) + j * 2 + k)) {
return false;
}
}
}
}
return true;
}
template <int Options>
Eigen::Tensor<double, 3, Options> &get_tensor() {
static Eigen::Tensor<double, 3, Options> *x;
if (!x) {
x = new Eigen::Tensor<double, 3, Options>(3, 5, 2);
reset_tensor(*x);
}
return *x;
}
template <int Options>
Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> &get_tensor_map() {
static Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> *x;
if (!x) {
x = new Eigen::TensorMap<Eigen::Tensor<double, 3, Options>>(get_tensor<Options>());
}
return *x;
}
template <int Options>
Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options> &get_fixed_tensor() {
static Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options> *x;
if (!x) {
Eigen::aligned_allocator<Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options>>
allocator;
x = new (allocator.allocate(1))
Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options>();
reset_tensor(*x);
}
return *x;
}
template <int Options>
const Eigen::Tensor<double, 3, Options> &get_const_tensor() {
return get_tensor<Options>();
}
template <int Options>
struct CustomExample {
CustomExample() : member(get_tensor<Options>()), view_member(member) {}
Eigen::Tensor<double, 3, Options> member;
Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> view_member;
};
template <int Options>
void init_tensor_module(pybind11::module &m) {
const char *needed_options = "";
if (Options == Eigen::ColMajor) {
needed_options = "F";
} else {
needed_options = "C";
}
m.attr("needed_options") = needed_options;
m.def("setup", []() {
reset_tensor(get_tensor<Options>());
reset_tensor(get_fixed_tensor<Options>());
});
m.def("is_ok", []() {
return check_tensor(get_tensor<Options>()) && check_tensor(get_fixed_tensor<Options>());
});
py::class_<CustomExample<Options>>(m, "CustomExample", py::module_local())
.def(py::init<>())
.def_readonly(
"member", &CustomExample<Options>::member, py::return_value_policy::reference_internal)
.def_readonly("member_view",
&CustomExample<Options>::view_member,
py::return_value_policy::reference_internal);
m.def(
"copy_fixed_tensor",
[]() { return &get_fixed_tensor<Options>(); },
py::return_value_policy::copy);
m.def(
"copy_tensor", []() { return &get_tensor<Options>(); }, py::return_value_policy::copy);
m.def(
"copy_const_tensor",
[]() { return &get_const_tensor<Options>(); },
py::return_value_policy::copy);
m.def(
"move_fixed_tensor_copy",
[]() -> Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options> {
return get_fixed_tensor<Options>();
},
py::return_value_policy::move);
m.def(
"move_tensor_copy",
[]() -> Eigen::Tensor<double, 3, Options> { return get_tensor<Options>(); },
py::return_value_policy::move);
m.def(
"move_const_tensor",
[]() -> const Eigen::Tensor<double, 3, Options> & { return get_const_tensor<Options>(); },
py::return_value_policy::move);
m.def(
"take_fixed_tensor",
[]() {
Eigen::aligned_allocator<
Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options>>
allocator;
return new (allocator.allocate(1))
Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options>(
get_fixed_tensor<Options>());
},
py::return_value_policy::take_ownership);
m.def(
"take_tensor",
[]() { return new Eigen::Tensor<double, 3, Options>(get_tensor<Options>()); },
py::return_value_policy::take_ownership);
m.def(
"take_const_tensor",
[]() -> const Eigen::Tensor<double, 3, Options> * {
return new Eigen::Tensor<double, 3, Options>(get_tensor<Options>());
},
py::return_value_policy::take_ownership);
m.def(
"take_view_tensor",
[]() -> const Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> * {
return new Eigen::TensorMap<Eigen::Tensor<double, 3, Options>>(get_tensor<Options>());
},
py::return_value_policy::take_ownership);
m.def(
"reference_tensor",
[]() { return &get_tensor<Options>(); },
py::return_value_policy::reference);
m.def(
"reference_tensor_v2",
[]() -> Eigen::Tensor<double, 3, Options> & { return get_tensor<Options>(); },
py::return_value_policy::reference);
m.def(
"reference_tensor_internal",
[]() { return &get_tensor<Options>(); },
py::return_value_policy::reference_internal);
m.def(
"reference_fixed_tensor",
[]() { return &get_tensor<Options>(); },
py::return_value_policy::reference);
m.def(
"reference_const_tensor",
[]() { return &get_const_tensor<Options>(); },
py::return_value_policy::reference);
m.def(
"reference_const_tensor_v2",
[]() -> const Eigen::Tensor<double, 3, Options> & { return get_const_tensor<Options>(); },
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor",
[]() -> Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> {
return get_tensor_map<Options>();
},
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor_v2",
// NOLINTNEXTLINE(readability-const-return-type)
[]() -> const Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> {
return get_tensor_map<Options>(); // NOLINT(readability-const-return-type)
}, // NOLINT(readability-const-return-type)
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor_v3",
[]() -> Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> * {
return &get_tensor_map<Options>();
},
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor_v4",
[]() -> const Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> * {
return &get_tensor_map<Options>();
},
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor_v5",
[]() -> Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> & {
return get_tensor_map<Options>();
},
py::return_value_policy::reference);
m.def(
"reference_view_of_tensor_v6",
[]() -> const Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> & {
return get_tensor_map<Options>();
},
py::return_value_policy::reference);
m.def(
"reference_view_of_fixed_tensor",
[]() {
return Eigen::TensorMap<
Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options>>(
get_fixed_tensor<Options>());
},
py::return_value_policy::reference);
m.def("round_trip_tensor",
[](const Eigen::Tensor<double, 3, Options> &tensor) { return tensor; });
m.def(
"round_trip_tensor_noconvert",
[](const Eigen::Tensor<double, 3, Options> &tensor) { return tensor; },
py::arg("tensor").noconvert());
m.def("round_trip_tensor2",
[](const Eigen::Tensor<int32_t, 3, Options> &tensor) { return tensor; });
m.def("round_trip_fixed_tensor",
[](const Eigen::TensorFixedSize<double, Eigen::Sizes<3, 5, 2>, Options> &tensor) {
return tensor;
});
m.def(
"round_trip_view_tensor",
[](Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> view) { return view; },
py::return_value_policy::reference);
m.def(
"round_trip_view_tensor_ref",
[](Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> &view) { return view; },
py::return_value_policy::reference);
m.def(
"round_trip_view_tensor_ptr",
[](Eigen::TensorMap<Eigen::Tensor<double, 3, Options>> *view) { return view; },
py::return_value_policy::reference);
m.def(
"round_trip_aligned_view_tensor",
[](Eigen::TensorMap<Eigen::Tensor<double, 3, Options>, Eigen::Aligned> view) {
return view;
},
py::return_value_policy::reference);
m.def(
"round_trip_const_view_tensor",
[](Eigen::TensorMap<const Eigen::Tensor<double, 3, Options>> view) {
return Eigen::Tensor<double, 3, Options>(view);
},
py::return_value_policy::move);
m.def(
"round_trip_rank_0",
[](const Eigen::Tensor<double, 0, Options> &tensor) { return tensor; },
py::return_value_policy::move);
m.def(
"round_trip_rank_0_noconvert",
[](const Eigen::Tensor<double, 0, Options> &tensor) { return tensor; },
py::arg("tensor").noconvert(),
py::return_value_policy::move);
m.def(
"round_trip_rank_0_view",
[](Eigen::TensorMap<Eigen::Tensor<double, 0, Options>> &tensor) { return tensor; },
py::return_value_policy::reference);
}
void test_module(py::module_ &m) {
auto f_style = m.def_submodule("f_style");
auto c_style = m.def_submodule("c_style");
init_tensor_module<Eigen::ColMajor>(f_style);
init_tensor_module<Eigen::RowMajor>(c_style);
}
PYBIND11_NAMESPACE_END(eigen_tensor_test)

296
tests/test_eigen_tensor.py Normal file
View File

@ -0,0 +1,296 @@
import sys
import pytest
np = pytest.importorskip("numpy")
eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor")
submodules = [eigen_tensor.c_style, eigen_tensor.f_style]
try:
import eigen_tensor_avoid_stl_array as avoid
submodules += [avoid.c_style, avoid.f_style]
except ImportError as e:
# Ensure config, build, toolchain, etc. issues are not masked here:
raise RuntimeError(
"import eigen_tensor_avoid_stl_array FAILED, while "
"import pybind11_tests.eigen_tensor succeeded. "
"Please ensure that "
"test_eigen_tensor.cpp & "
"eigen_tensor_avoid_stl_array.cpp "
"are built together (or both are not built if Eigen is not available)."
) from e
tensor_ref = np.empty((3, 5, 2), dtype=np.int64)
for i in range(tensor_ref.shape[0]):
for j in range(tensor_ref.shape[1]):
for k in range(tensor_ref.shape[2]):
tensor_ref[i, j, k] = i * (5 * 2) + j * 2 + k
indices = (2, 3, 1)
@pytest.fixture(autouse=True)
def cleanup():
for module in submodules:
module.setup()
yield
for module in submodules:
assert module.is_ok()
def test_import_avoid_stl_array():
pytest.importorskip("eigen_tensor_avoid_stl_array")
assert len(submodules) == 4
def assert_equal_tensor_ref(mat, writeable=True, modified=None):
assert mat.flags.writeable == writeable
copy = np.array(tensor_ref)
if modified is not None:
copy[indices] = modified
np.testing.assert_array_equal(mat, copy)
@pytest.mark.parametrize("m", submodules)
@pytest.mark.parametrize("member_name", ["member", "member_view"])
def test_reference_internal(m, member_name):
if not hasattr(sys, "getrefcount"):
pytest.skip("No reference counting")
foo = m.CustomExample()
counts = sys.getrefcount(foo)
mem = getattr(foo, member_name)
assert_equal_tensor_ref(mem, writeable=False)
new_counts = sys.getrefcount(foo)
assert new_counts == counts + 1
assert_equal_tensor_ref(mem, writeable=False)
del mem
assert sys.getrefcount(foo) == counts
assert_equal_funcs = [
"copy_tensor",
"copy_fixed_tensor",
"copy_const_tensor",
"move_tensor_copy",
"move_fixed_tensor_copy",
"take_tensor",
"take_fixed_tensor",
"reference_tensor",
"reference_tensor_v2",
"reference_fixed_tensor",
"reference_view_of_tensor",
"reference_view_of_tensor_v3",
"reference_view_of_tensor_v5",
"reference_view_of_fixed_tensor",
]
assert_equal_const_funcs = [
"reference_view_of_tensor_v2",
"reference_view_of_tensor_v4",
"reference_view_of_tensor_v6",
"reference_const_tensor",
"reference_const_tensor_v2",
]
@pytest.mark.parametrize("m", submodules)
@pytest.mark.parametrize("func_name", assert_equal_funcs + assert_equal_const_funcs)
def test_convert_tensor_to_py(m, func_name):
writeable = func_name in assert_equal_funcs
assert_equal_tensor_ref(getattr(m, func_name)(), writeable=writeable)
@pytest.mark.parametrize("m", submodules)
def test_bad_cpp_to_python_casts(m):
with pytest.raises(
RuntimeError, match="Cannot use reference internal when there is no parent"
):
m.reference_tensor_internal()
with pytest.raises(RuntimeError, match="Cannot move from a constant reference"):
m.move_const_tensor()
with pytest.raises(
RuntimeError, match="Cannot take ownership of a const reference"
):
m.take_const_tensor()
with pytest.raises(
RuntimeError,
match="Invalid return_value_policy for Eigen Map type, must be either reference or reference_internal",
):
m.take_view_tensor()
@pytest.mark.parametrize("m", submodules)
def test_bad_python_to_cpp_casts(m):
with pytest.raises(
TypeError, match=r"^round_trip_tensor\(\): incompatible function arguments"
):
m.round_trip_tensor(np.zeros((2, 3)))
with pytest.raises(TypeError, match=r"^Cannot cast array data from dtype"):
m.round_trip_tensor(np.zeros(dtype=np.str_, shape=(2, 3, 1)))
with pytest.raises(
TypeError,
match=r"^round_trip_tensor_noconvert\(\): incompatible function arguments",
):
m.round_trip_tensor_noconvert(tensor_ref)
assert_equal_tensor_ref(
m.round_trip_tensor_noconvert(tensor_ref.astype(np.float64))
)
if m.needed_options == "F":
bad_options = "C"
else:
bad_options = "F"
# Shape, dtype and the order need to be correct for a TensorMap cast
with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
):
m.round_trip_view_tensor(
np.zeros((3, 5, 2), dtype=np.float64, order=bad_options)
)
with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
):
m.round_trip_view_tensor(
np.zeros((3, 5, 2), dtype=np.float32, order=m.needed_options)
)
with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
):
m.round_trip_view_tensor(
np.zeros((3, 5), dtype=np.float64, order=m.needed_options)
)
with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
):
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
m.round_trip_view_tensor(
temp[:, ::-1, :],
)
with pytest.raises(
TypeError, match=r"^round_trip_view_tensor\(\): incompatible function arguments"
):
temp = np.zeros((3, 5, 2), dtype=np.float64, order=m.needed_options)
temp.setflags(write=False)
m.round_trip_view_tensor(temp)
@pytest.mark.parametrize("m", submodules)
def test_references_actually_refer(m):
a = m.reference_tensor()
temp = a[indices]
a[indices] = 100
assert_equal_tensor_ref(m.copy_const_tensor(), modified=100)
a[indices] = temp
assert_equal_tensor_ref(m.copy_const_tensor())
a = m.reference_view_of_tensor()
a[indices] = 100
assert_equal_tensor_ref(m.copy_const_tensor(), modified=100)
a[indices] = temp
assert_equal_tensor_ref(m.copy_const_tensor())
@pytest.mark.parametrize("m", submodules)
def test_round_trip(m):
assert_equal_tensor_ref(m.round_trip_tensor(tensor_ref))
with pytest.raises(TypeError, match="^Cannot cast array data from"):
assert_equal_tensor_ref(m.round_trip_tensor2(tensor_ref))
assert_equal_tensor_ref(m.round_trip_tensor2(np.array(tensor_ref, dtype=np.int32)))
assert_equal_tensor_ref(m.round_trip_fixed_tensor(tensor_ref))
assert_equal_tensor_ref(m.round_trip_aligned_view_tensor(m.reference_tensor()))
copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options)
assert_equal_tensor_ref(m.round_trip_view_tensor(copy))
assert_equal_tensor_ref(m.round_trip_view_tensor_ref(copy))
assert_equal_tensor_ref(m.round_trip_view_tensor_ptr(copy))
copy.setflags(write=False)
assert_equal_tensor_ref(m.round_trip_const_view_tensor(copy))
np.testing.assert_array_equal(
tensor_ref[:, ::-1, :], m.round_trip_tensor(tensor_ref[:, ::-1, :])
)
assert m.round_trip_rank_0(np.float64(3.5)) == 3.5
assert m.round_trip_rank_0(3.5) == 3.5
with pytest.raises(
TypeError,
match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments",
):
m.round_trip_rank_0_noconvert(np.float64(3.5))
with pytest.raises(
TypeError,
match=r"^round_trip_rank_0_noconvert\(\): incompatible function arguments",
):
m.round_trip_rank_0_noconvert(3.5)
with pytest.raises(
TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments"
):
m.round_trip_rank_0_view(np.float64(3.5))
with pytest.raises(
TypeError, match=r"^round_trip_rank_0_view\(\): incompatible function arguments"
):
m.round_trip_rank_0_view(3.5)
@pytest.mark.parametrize("m", submodules)
def test_round_trip_references_actually_refer(m):
# Need to create a copy that matches the type on the C side
copy = np.array(tensor_ref, dtype=np.float64, order=m.needed_options)
a = m.round_trip_view_tensor(copy)
temp = a[indices]
a[indices] = 100
assert_equal_tensor_ref(copy, modified=100)
a[indices] = temp
assert_equal_tensor_ref(copy)
@pytest.mark.parametrize("m", submodules)
def test_doc_string(m, doc):
assert (
doc(m.copy_tensor) == "copy_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]"
)
assert (
doc(m.copy_fixed_tensor)
== "copy_fixed_tensor() -> numpy.ndarray[numpy.float64[3, 5, 2]]"
)
assert (
doc(m.reference_const_tensor)
== "reference_const_tensor() -> numpy.ndarray[numpy.float64[?, ?, ?]]"
)
order_flag = f"flags.{m.needed_options.lower()}_contiguous"
assert doc(m.round_trip_view_tensor) == (
f"round_trip_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}])"
+ f" -> numpy.ndarray[numpy.float64[?, ?, ?], flags.writeable, {order_flag}]"
)
assert doc(m.round_trip_const_view_tensor) == (
f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])"
+ " -> numpy.ndarray[numpy.float64[?, ?, ?]]"
)

View File

@ -3,11 +3,9 @@
#include <pybind11/embed.h>
#ifdef _MSC_VER
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
# pragma warning(disable : 4996)
#endif
PYBIND11_WARNING_DISABLE_MSVC(4996)
// Catch uses _ internally, which breaks gettext style defines
#ifdef _

View File

@ -1,10 +1,8 @@
#include <pybind11/embed.h>
#ifdef _MSC_VER
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
# pragma warning(disable : 4996)
#endif
PYBIND11_WARNING_DISABLE_MSVC(4996)
#include <catch.hpp>
#include <cstdlib>
@ -16,6 +14,11 @@
namespace py = pybind11;
using namespace py::literals;
size_t get_sys_path_size() {
auto sys_path = py::module::import("sys").attr("path");
return py::len(sys_path);
}
class Widget {
public:
explicit Widget(std::string message) : message(std::move(message)) {}
@ -168,6 +171,70 @@ TEST_CASE("There can be only one interpreter") {
py::initialize_interpreter();
}
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
TEST_CASE("Custom PyConfig") {
py::finalize_interpreter();
PyConfig config;
PyConfig_InitPythonConfig(&config);
REQUIRE_NOTHROW(py::scoped_interpreter{&config});
{
py::scoped_interpreter p{&config};
REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast<int>() == 42);
}
py::initialize_interpreter();
}
TEST_CASE("Custom PyConfig with argv") {
py::finalize_interpreter();
{
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
char *argv[] = {strdup("a.out")};
py::scoped_interpreter argv_scope{&config, 1, argv};
std::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();
}
#endif
TEST_CASE("Add program dir to path pre-PyConfig") {
py::finalize_interpreter();
size_t path_size_add_program_dir_to_path_false = 0;
{
py::scoped_interpreter scoped_interp{true, 0, nullptr, false};
path_size_add_program_dir_to_path_false = get_sys_path_size();
}
{
py::scoped_interpreter scoped_interp{};
REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
}
py::initialize_interpreter();
}
#if PY_VERSION_HEX >= PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX
TEST_CASE("Add program dir to path using PyConfig") {
py::finalize_interpreter();
size_t path_size_add_program_dir_to_path_false = 0;
{
PyConfig config;
PyConfig_InitPythonConfig(&config);
py::scoped_interpreter scoped_interp{&config, 0, nullptr, false};
path_size_add_program_dir_to_path_false = get_sys_path_size();
}
{
PyConfig config;
PyConfig_InitPythonConfig(&config);
py::scoped_interpreter scoped_interp{&config};
REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1);
}
py::initialize_interpreter();
}
#endif
bool has_pybind11_internals_builtin() {
auto builtins = py::handle(PyEval_GetBuiltins());
return builtins.contains(PYBIND11_INTERNALS_ID);

View File

@ -4,6 +4,7 @@ import pytest
import env
import pybind11_cross_module_tests as cm
import pybind11_tests # noqa: F401
from pybind11_tests import exceptions as m
@ -72,9 +73,9 @@ def test_cross_module_exceptions(msg):
# TODO: FIXME
@pytest.mark.xfail(
"env.PYPY and env.MACOS",
"env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))",
raises=RuntimeError,
reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)",
reason="See Issue #2847, PR #2999, PR #4324",
)
def test_cross_module_exception_translator():
with pytest.raises(KeyError):

View File

@ -5,6 +5,7 @@ import time
import pytest
import env
from pybind11_tests import gil_scoped as m
@ -144,7 +145,6 @@ def _intentional_deadlock():
ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK = ALL_BASIC_TESTS + (_intentional_deadlock,)
SKIP_IF_DEADLOCK = True # See PR #4216
def _run_in_process(target, *args, **kwargs):
@ -181,7 +181,7 @@ def _run_in_process(target, *args, **kwargs):
elif process.exitcode is None:
assert t_delta > 0.9 * timeout
msg = "DEADLOCK, most likely, exactly what this test is meant to detect."
if SKIP_IF_DEADLOCK:
if env.PYPY and env.WIN:
pytest.skip(msg)
raise RuntimeError(msg)
return process.exitcode

View File

@ -44,14 +44,13 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
// test_args_and_kwargs
m.def("args_function", [](py::args args) -> py::tuple {
PYBIND11_WARNING_PUSH
#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wreturn-std-move"
PYBIND11_WARNING_DISABLE_CLANG("-Wreturn-std-move")
#endif
return args;
#ifdef PYBIND11_DETECTED_CLANG_WITH_MISLEADING_CALL_STD_MOVE_EXPLICITLY_WARNING
# pragma clang diagnostic pop
#endif
PYBIND11_WARNING_POP
});
m.def("args_kwargs_function", [](const py::args &args, const py::kwargs &kwargs) {
return py::make_tuple(args, kwargs);

View File

@ -521,4 +521,6 @@ TEST_SUBMODULE(numpy_array, sm) {
sm.def("test_fmt_desc_double", [](const py::array_t<double> &) {});
sm.def("test_fmt_desc_const_float", [](const py::array_t<const float> &) {});
sm.def("test_fmt_desc_const_double", [](const py::array_t<const double> &) {});
sm.def("round_trip_float", [](double d) { return d; });
}

View File

@ -585,3 +585,9 @@ def test_dtype_refcount_leak():
m.ndim(a)
after = getrefcount(dtype)
assert after == before
def test_round_trip_float():
arr = np.zeros((), np.float64)
arr[()] = 37.2
assert m.round_trip_float(arr) == 37.2

View File

@ -132,22 +132,18 @@ struct hash<HashMe> {
// Not a good abs function, but easy to test.
std::string abs(const Vector2 &) { return "abs(Vector2)"; }
// MSVC & Intel warns about unknown pragmas, and warnings are errors.
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
// clang 7.0.0 and Apple LLVM 10.0.1 introduce `-Wself-assign-overloaded` to
// `-Wall`, which is used here for overloading (e.g. `py::self += py::self `).
// Here, we suppress the warning using `#pragma diagnostic`.
// Here, we suppress the warning
// Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46
// TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`).
# if defined(__APPLE__) && defined(__clang__)
# if (__clang_major__ >= 10)
# pragma GCC diagnostic ignored "-Wself-assign-overloaded"
# endif
# elif defined(__clang__)
# if (__clang_major__ >= 7)
# pragma GCC diagnostic ignored "-Wself-assign-overloaded"
# endif
#if defined(__APPLE__) && defined(__clang__)
# if (__clang_major__ >= 10)
PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded")
# endif
#elif defined(__clang__)
# if (__clang_major__ >= 7)
PYBIND11_WARNING_DISABLE_CLANG("-Wself-assign-overloaded")
# endif
#endif
@ -283,6 +279,3 @@ TEST_SUBMODULE(operators, m) {
m.def("get_unhashable_HashMe_set", []() { return std::unordered_set<HashMe>{{"one"}}; });
}
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif

View File

@ -309,3 +309,29 @@ def test_map_delitem():
del um["ua"]
assert sorted(list(um)) == ["ub"]
assert sorted(list(um.items())) == [("ub", 2.6)]
def test_map_view_types():
map_string_double = m.MapStringDouble()
unordered_map_string_double = m.UnorderedMapStringDouble()
map_string_double_const = m.MapStringDoubleConst()
unordered_map_string_double_const = m.UnorderedMapStringDoubleConst()
assert map_string_double.keys().__class__.__name__ == "KeysView[str]"
assert map_string_double.values().__class__.__name__ == "ValuesView[float]"
assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]"
keys_type = type(map_string_double.keys())
assert type(unordered_map_string_double.keys()) is keys_type
assert type(map_string_double_const.keys()) is keys_type
assert type(unordered_map_string_double_const.keys()) is keys_type
values_type = type(map_string_double.values())
assert type(unordered_map_string_double.values()) is values_type
assert type(map_string_double_const.values()) is values_type
assert type(unordered_map_string_double_const.values()) is values_type
items_type = type(map_string_double.items())
assert type(unordered_map_string_double.items()) is items_type
assert type(map_string_double_const.items()) is items_type
assert type(unordered_map_string_double_const.items()) is items_type

View File

@ -173,7 +173,8 @@ struct AdderBase {
using DataVisitor = std::function<void(const Data &)>;
virtual void
operator()(const Data &first, const Data &second, const DataVisitor &visitor) const = 0;
operator()(const Data &first, const Data &second, const DataVisitor &visitor) const
= 0;
virtual ~AdderBase() = default;
AdderBase() = default;
AdderBase(const AdderBase &) = delete;

View File

@ -208,7 +208,9 @@ string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}")
string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}")
string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES "${PYTHON_SITE_PACKAGES}")
if(CMAKE_HOST_WIN32)
if(DEFINED PYTHON_LIBRARY)
# Don't write to PYTHON_LIBRARY if it's already set
elseif(CMAKE_HOST_WIN32)
set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/python${PYTHON_LIBRARY_SUFFIX}.lib")
# when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the
@ -274,7 +276,7 @@ if(NOT PYTHON_DEBUG_LIBRARY)
endif()
set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}")
find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARY}"
find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARIES}"
"${PYTHON_EXECUTABLE}${PYTHON_VERSION_STRING}")
set(PYTHONLIBS_FOUND TRUE)

View File

@ -311,6 +311,16 @@ function(_pybind11_generate_lto target prefer_thin_lto)
HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS
PYBIND11_LTO_LINKER_FLAGS)
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM")
# IntelLLVM equivalent to LTO is called IPO; also IntelLLVM is WIN32/UNIX
# WARNING/HELP WANTED: This block of code is currently not covered by pybind11 GitHub Actions!
if(WIN32)
_pybind11_return_if_cxx_and_linker_flags_work(
HAS_INTEL_IPO "-Qipo" "-Qipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS)
else()
_pybind11_return_if_cxx_and_linker_flags_work(
HAS_INTEL_IPO "-ipo" "-ipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS)
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel")
# Intel equivalent to LTO is called IPO
_pybind11_return_if_cxx_and_linker_flags_work(HAS_INTEL_IPO "-ipo" "-ipo"

View File

@ -9,7 +9,7 @@ if(CMAKE_VERSION VERSION_LESS 3.12)
message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12")
endif()
include_guard(GLOBAL)
include_guard(DIRECTORY)
get_property(
is_config
@ -235,7 +235,7 @@ function(pybind11_add_module target_name)
# Use case-insensitive comparison to match the result of $<CONFIG:cfgs>
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
if(NOT MSVC AND NOT ${uppercase_CMAKE_BUILD_TYPE} MATCHES DEBUG|RELWITHDEBINFO)
if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO)
# Strip unnecessary sections of the binary on Linux/macOS
pybind11_strip(${target_name})
endif()

View File

@ -214,7 +214,7 @@ function(pybind11_add_module target_name)
# Use case-insensitive comparison to match the result of $<CONFIG:cfgs>
string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE)
if(NOT MSVC AND NOT ${uppercase_CMAKE_BUILD_TYPE} MATCHES DEBUG|RELWITHDEBINFO)
if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO)
pybind11_strip(${target_name})
endif()

View File

@ -27,10 +27,11 @@ class InstallHeadersNested(install_headers):
main_headers = glob.glob("pybind11/include/pybind11/*.h")
detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h")
eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h")
stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h")
cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake")
pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc")
headers = main_headers + detail_headers + stl_headers
headers = main_headers + detail_headers + stl_headers + eigen_headers
cmdclass = {"install_headers": InstallHeadersNested}
$extra_cmd
@ -55,6 +56,7 @@ setup(
(base + "share/pkgconfig", pkgconfig_files),
(base + "include/pybind11", main_headers),
(base + "include/pybind11/detail", detail_headers),
(base + "include/pybind11/eigen", eigen_headers),
(base + "include/pybind11/stl", stl_headers),
],
cmdclass=cmdclass,

View File

@ -15,6 +15,7 @@ setup(
"pybind11",
"pybind11.include.pybind11",
"pybind11.include.pybind11.detail",
"pybind11.include.pybind11.eigen",
"pybind11.include.pybind11.stl",
"pybind11.share.cmake.pybind11",
"pybind11.share.pkgconfig",
@ -23,6 +24,7 @@ setup(
"pybind11": ["py.typed"],
"pybind11.include.pybind11": ["*.h"],
"pybind11.include.pybind11.detail": ["*.h"],
"pybind11.include.pybind11.eigen": ["*.h"],
"pybind11.include.pybind11.stl": ["*.h"],
"pybind11.share.cmake.pybind11": ["*.cmake"],
"pybind11.share.pkgconfig": ["*.pc"],