From 3f5bc19cfb14e9b42b3b3ac56b1fa11bbc2f44a3 Mon Sep 17 00:00:00 2001 From: bwehlin Date: Wed, 25 Feb 2026 11:35:47 +0100 Subject: [PATCH 1/6] string overloads and some basic tests --- include/pybind11/pybind11.h | 36 ++++++++++++++++++++++++++++++++++++ tests/test_modules.cpp | 4 ++++ tests/test_modules.py | 8 ++++++++ 3 files changed, 48 insertions(+) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 76d998a3ba..6a2b2c9a80 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1541,6 +1541,14 @@ class module_ : public object { return *this; } + /** \rst + Overload of `def` taking the function name as a ``std::string``. + \endrst */ + template + module_ &def(const std::string& name_, Func &&f, const Extra &...extra) { + return def(name_.c_str(), f, std::forward(extra)...); + } + /** \rst Create and return a new Python submodule with the given name and docstring. This also works recursively, i.e. @@ -1587,6 +1595,20 @@ class module_ : public object { return result; } + /** \rst + Overload of `def_submodule` taking the function name and documentation as ``std::string``. + \endrst */ + module_ def_submodule(const std::string& name, const std::string& doc) { + return def_submodule(name.c_str(), doc.c_str()); + } + + /** \rst + Overload of `def_submodule` taking the function name as a ``std::string``. + \endrst */ + module_ def_submodule(const std::string& name) { + return def_submodule(name.c_str(), nullptr); + } + /// Import and return a module or throws `error_already_set`. static module_ import(const char *name) { PyObject *obj = PyImport_ImportModule(name); @@ -1596,6 +1618,13 @@ class module_ : public object { return reinterpret_steal(obj); } + /** \rst + Overload of `import` taking the function name as a ``std::string``. + \endrst */ + static module_ import(const std::string& name) { + return import(name.c_str()); + } + /// Reload the module or throws `error_already_set`. void reload() { PyObject *obj = PyImport_ReloadModule(ptr()); @@ -1622,6 +1651,13 @@ class module_ : public object { PyModule_AddObject(ptr(), name, obj.inc_ref().ptr() /* steals a reference */); } + /** \rst + Overload of `add_object` taking the function name and documentation as a ``std::string``. + \endrst */ + PYBIND11_NOINLINE void add_object(const std::string& name, handle obj, bool overwrite = false) { + return add_object(name.c_str(), obj, overwrite); + } + // DEPRECATED (since PR #5688): Use PyModuleDef directly instead. using module_def = PyModuleDef; diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp index 842a3bc4b8..5bb48f8e7c 100644 --- a/tests/test_modules.cpp +++ b/tests/test_modules.cpp @@ -121,4 +121,8 @@ TEST_SUBMODULE(modules, m) { }); m.def("def_submodule", [](py::module_ m, const char *name) { return m.def_submodule(name); }); + + // Test std::string versions of def_submodule and def + py::module m_sub_string = m.def_submodule(std::string("subsubmodule_string")); + m_sub_string.def(std::string("submodule_string_func"), []() { return "submodule_string_func()"; }); } diff --git a/tests/test_modules.py b/tests/test_modules.py index ea8ca7e5e4..4978919dd7 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -8,6 +8,7 @@ from pybind11_tests import ConstructorStats from pybind11_tests import modules as m from pybind11_tests.modules import subsubmodule as ms +from pybind11_tests.modules import submodule_string as mstr def test_nested_modules(): @@ -25,6 +26,13 @@ def test_nested_modules(): assert ms.submodule_func() == "submodule_func()" + assert ( + pybind11_tests.modules.submodule_string.__name__ + == "pybind11_tests.modules.submodule_string" + ) + + assert mstr.submodule_string_func() == "submodule_string_func()" + def test_reference_internal(): b = ms.B() From c1684bb0875be5297e2008782b4e7796e614730f Mon Sep 17 00:00:00 2001 From: bwehlin Date: Wed, 25 Feb 2026 11:45:39 +0100 Subject: [PATCH 2/6] Fixed typo --- tests/test_modules.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp index 5bb48f8e7c..1da1b8a746 100644 --- a/tests/test_modules.cpp +++ b/tests/test_modules.cpp @@ -123,6 +123,6 @@ TEST_SUBMODULE(modules, m) { m.def("def_submodule", [](py::module_ m, const char *name) { return m.def_submodule(name); }); // Test std::string versions of def_submodule and def - py::module m_sub_string = m.def_submodule(std::string("subsubmodule_string")); + py::module m_sub_string = m.def_submodule(std::string("submodule_string")); m_sub_string.def(std::string("submodule_string_func"), []() { return "submodule_string_func()"; }); } From 69a14e0a7ce91eb2a994fef2147c27d3c4ae46e3 Mon Sep 17 00:00:00 2001 From: bwehlin Date: Wed, 25 Feb 2026 12:00:56 +0100 Subject: [PATCH 3/6] More string functions and tests --- include/pybind11/pybind11.h | 13 ++++++++++++- tests/test_exceptions.cpp | 13 +++++++++++++ tests/test_exceptions.py | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 6a2b2c9a80..122287b040 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1652,7 +1652,7 @@ class module_ : public object { } /** \rst - Overload of `add_object` taking the function name and documentation as a ``std::string``. + Overload of `add_object` taking the function name as a ``std::string``. \endrst */ PYBIND11_NOINLINE void add_object(const std::string& name, handle obj, bool overwrite = false) { return add_object(name.c_str(), obj, overwrite); @@ -1698,6 +1698,17 @@ class module_ : public object { // For Python 2, reinterpret_borrow was correct. return reinterpret_borrow(m); } + + /** \rst + Overload of `create_extension_module` taking the module name and documentation as ``std::string``. + \endrst */ + static module_ create_extension_module(const std::string& name, + const std::string& doc, + PyModuleDef *def, + mod_gil_not_used gil_not_used + = mod_gil_not_used(false)) { + return create_extension_module(name.c_str(), doc.c_str(), def, gil_not_used); + } }; PYBIND11_NAMESPACE_BEGIN(detail) diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index 0a970065b4..9d5970260c 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -268,6 +268,19 @@ TEST_SUBMODULE(exceptions, m) { return false; }); + m.def("modulenotfound_exception_matches_base_string", []() { + try { + // On Python >= 3.6, this raises a ModuleNotFoundError, a subclass of ImportError + py::module_::import(std::string("nonexistent")); + } catch (py::error_already_set &ex) { + if (!ex.matches(PyExc_ImportError)) { + throw; + } + return true; + } + return false; + }); + m.def("throw_already_set", [](bool err) { if (err) { py::set_error(PyExc_ValueError, "foo"); diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 59845b441b..505ccb6178 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -133,6 +133,7 @@ def test_exception_matches(): assert m.exception_matches() assert m.exception_matches_base() assert m.modulenotfound_exception_matches_base() + assert m.modulenotfound_exception_matches_base_string() def test_custom(msg): From fff3c61410b4abc519c81d67eba6838736db7a8c Mon Sep 17 00:00:00 2001 From: bwehlin Date: Wed, 25 Feb 2026 12:15:35 +0100 Subject: [PATCH 4/6] Fix unsorted-imports --- tests/test_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_modules.py b/tests/test_modules.py index 4978919dd7..c29f76ed5d 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -7,8 +7,8 @@ import env from pybind11_tests import ConstructorStats from pybind11_tests import modules as m -from pybind11_tests.modules import subsubmodule as ms from pybind11_tests.modules import submodule_string as mstr +from pybind11_tests.modules import subsubmodule as ms def test_nested_modules(): From 891c0ecbbf5dec8e16a4df2bbe08518ee2cc6cdc Mon Sep 17 00:00:00 2001 From: Bjorn Wehlin Date: Wed, 25 Feb 2026 12:19:53 +0100 Subject: [PATCH 5/6] Linting --- include/pybind11/pybind11.h | 22 ++++++++++------------ tests/test_modules.cpp | 3 ++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 122287b040..8647d76cf1 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1545,7 +1545,7 @@ class module_ : public object { Overload of `def` taking the function name as a ``std::string``. \endrst */ template - module_ &def(const std::string& name_, Func &&f, const Extra &...extra) { + module_ &def(const std::string &name_, Func &&f, const Extra &...extra) { return def(name_.c_str(), f, std::forward(extra)...); } @@ -1598,16 +1598,14 @@ class module_ : public object { /** \rst Overload of `def_submodule` taking the function name and documentation as ``std::string``. \endrst */ - module_ def_submodule(const std::string& name, const std::string& doc) { + module_ def_submodule(const std::string &name, const std::string &doc) { return def_submodule(name.c_str(), doc.c_str()); } /** \rst Overload of `def_submodule` taking the function name as a ``std::string``. \endrst */ - module_ def_submodule(const std::string& name) { - return def_submodule(name.c_str(), nullptr); - } + module_ def_submodule(const std::string &name) { return def_submodule(name.c_str(), nullptr); } /// Import and return a module or throws `error_already_set`. static module_ import(const char *name) { @@ -1621,9 +1619,7 @@ class module_ : public object { /** \rst Overload of `import` taking the function name as a ``std::string``. \endrst */ - static module_ import(const std::string& name) { - return import(name.c_str()); - } + static module_ import(const std::string &name) { return import(name.c_str()); } /// Reload the module or throws `error_already_set`. void reload() { @@ -1654,7 +1650,8 @@ class module_ : public object { /** \rst Overload of `add_object` taking the function name as a ``std::string``. \endrst */ - PYBIND11_NOINLINE void add_object(const std::string& name, handle obj, bool overwrite = false) { + PYBIND11_NOINLINE void + add_object(const std::string &name, handle obj, bool overwrite = false) { return add_object(name.c_str(), obj, overwrite); } @@ -1700,10 +1697,11 @@ class module_ : public object { } /** \rst - Overload of `create_extension_module` taking the module name and documentation as ``std::string``. + Overload of `create_extension_module` taking the module name and documentation as + ``std::string``. \endrst */ - static module_ create_extension_module(const std::string& name, - const std::string& doc, + static module_ create_extension_module(const std::string &name, + const std::string &doc, PyModuleDef *def, mod_gil_not_used gil_not_used = mod_gil_not_used(false)) { diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp index 1da1b8a746..dc22907156 100644 --- a/tests/test_modules.cpp +++ b/tests/test_modules.cpp @@ -124,5 +124,6 @@ TEST_SUBMODULE(modules, m) { // Test std::string versions of def_submodule and def py::module m_sub_string = m.def_submodule(std::string("submodule_string")); - m_sub_string.def(std::string("submodule_string_func"), []() { return "submodule_string_func()"; }); + m_sub_string.def(std::string("submodule_string_func"), + []() { return "submodule_string_func()"; }); } From 9b35745859196cd8d390fd1510eb4e5a3533d854 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Mon, 2 Mar 2026 13:54:38 -0800 Subject: [PATCH 6/6] Add test that exposes breakage: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` g++ -o pybind11/tests/test_modules.os -c -std=c++20 -fPIC -fvisibility=hidden -O0 -g -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated -Wundef -Wnon-virtual-dtor -Wunused-result -Werror -funsigned-char -Wpedantic -isystem /wrk/cpython_installs/v3.14.2_df793163d58_default/include/python3.14 -isystem /usr/include/eigen3 -DPYBIND11_SMART_HOLDER_PADDING_ON -DPYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX -DPYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD_IF_AVAILABLE -DPYBIND11_TEST_BOOST -Ipybind11/include -I/wrk/forked/pybind11/include -I/wrk/clone/pybind11/include /wrk/forked/pybind11/tests/test_modules.cpp In file included from /wrk/forked/pybind11/include/pybind11/eval.h:14, from /wrk/forked/pybind11/tests/pybind11_tests.h:3, from /wrk/forked/pybind11/tests/constructor_stats.h:68, from /wrk/forked/pybind11/tests/test_modules.cpp:11: /wrk/forked/pybind11/include/pybind11/pybind11.h: In instantiation of ‘pybind11::module_& pybind11::module_::def(const std::string&, Func&&, const Extra& ...) [with Func = test_submodule_modules(pybind11::module_&)::; Extra = {pybind11::arg}; std::string = std::__cxx11::basic_string]’: /wrk/forked/pybind11/tests/test_modules.cpp:129:21: required from here /wrk/forked/pybind11/include/pybind11/pybind11.h:1549:57: error: binding reference of type ‘std::remove_reference::type&’ {aka ‘pybind11::arg&’} to ‘const pybind11::arg’ discards qualifiers 1549 | return def(name_.c_str(), f, std::forward(extra)...); | ~~~~~~~~~~~~~~~~~~~^~~~~~~ In file included from /usr/include/c++/13/bits/stl_pair.h:61, from /usr/include/c++/13/bits/stl_algobase.h:64, from /usr/include/c++/13/bits/specfun.h:43, from /usr/include/c++/13/cmath:3699, from /usr/include/c++/13/math.h:36, from /wrk/cpython_installs/v3.14.2_df793163d58_default/include/python3.14/Python.h:23, from /wrk/forked/pybind11/include/pybind11/conduit/wrap_include_python_h.h:44, from /wrk/forked/pybind11/include/pybind11/detail/common.h:12, from /wrk/forked/pybind11/include/pybind11/attr.h:13, from /wrk/forked/pybind11/include/pybind11/detail/class.h:12, from /wrk/forked/pybind11/include/pybind11/pybind11.h:12: /usr/include/c++/13/bits/move.h:70:56: note: initializing argument 1 of ‘constexpr _Tp&& std::forward(typename remove_reference<_Functor>::type&) [with _Tp = pybind11::arg; typename remove_reference<_Functor>::type = pybind11::arg]’ 70 | forward(typename std::remove_reference<_Tp>::type& __t) noexcept | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~ ``` --- tests/test_modules.cpp | 2 ++ tests/test_modules.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/test_modules.cpp b/tests/test_modules.cpp index dc22907156..889f5275e5 100644 --- a/tests/test_modules.cpp +++ b/tests/test_modules.cpp @@ -126,4 +126,6 @@ TEST_SUBMODULE(modules, m) { py::module m_sub_string = m.def_submodule(std::string("submodule_string")); m_sub_string.def(std::string("submodule_string_func"), []() { return "submodule_string_func()"; }); + m_sub_string.def( + std::string("submodule_string_func_with_arg"), [](int x) { return x + 1; }, py::arg("x")); } diff --git a/tests/test_modules.py b/tests/test_modules.py index c29f76ed5d..1cee311017 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -32,6 +32,7 @@ def test_nested_modules(): ) assert mstr.submodule_string_func() == "submodule_string_func()" + assert mstr.submodule_string_func_with_arg(x=2) == 3 def test_reference_internal():