diff --git a/.codespell-ignore-lines b/.codespell-ignore-lines new file mode 100644 index 000000000..e8cbf3144 --- /dev/null +++ b/.codespell-ignore-lines @@ -0,0 +1,35 @@ +template + template + auto &this_ = static_cast(*this); + if (load_impl(temp, false)) { + ssize_t nd = 0; + auto trivial = broadcast(buffers, nd, shape); + auto ndim = (size_t) nd; + int nd; + ssize_t ndim() const { return detail::array_proxy(m_ptr)->nd; } + using op = op_impl; +template + template + class_ &def(const detail::op_ &op, const Extra &...extra) { + class_ &def_cast(const detail::op_ &op, const Extra &...extra) { + int valu; + explicit movable_int(int v) : valu{v} {} + movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; } + explicit indestructible_int(int v) : valu{v} {} + REQUIRE(hld.as_raw_ptr_unowned()->valu == 19); + REQUIRE(othr.valu == 19); + REQUIRE(orig.valu == 91); + (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"), +atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; } + assert m.atyp_valu().get_mtxt() == "Valu" +// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const, +@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) +struct IntStruct { + explicit IntStruct(int v) : value(v){}; + ~IntStruct() { value = -value; } + IntStruct(const IntStruct &) = default; + IntStruct &operator=(const IntStruct &) = default; + py::class_(m, "IntStruct").def(py::init([](const int i) { return IntStruct(i); })); + py::implicitly_convertible(); + m.def("test", [](int expected, const IntStruct &in) { + [](int expected, const IntStruct &in) { diff --git a/.codespell-ignorelines b/.codespell-ignorelines deleted file mode 100644 index a8ce4d434..000000000 --- a/.codespell-ignorelines +++ /dev/null @@ -1,13 +0,0 @@ - assert m.atyp_valu().get_mtxt() == "Valu" -atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; } - explicit indestructible_int(int v) : valu{v} {} - explicit movable_int(int v) : valu{v} {} - int valu; - (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"), - REQUIRE(hld.as_raw_ptr_unowned()->valu == 19); - REQUIRE(orig.valu == 91); - REQUIRE(othr.valu == 19); - with pytest.raises(ValueError) as excinfo: - with pytest.raises(ValueError) as exc_info: -// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const, - movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 621a4f99d..d512d8866 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -755,7 +755,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.12 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.11.0 with: arch: x86 @@ -808,7 +808,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.12 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.10.0 + uses: ilammy/msvc-dev-cmd@v1.11.0 with: arch: x86 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40388af1b..47426da4d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: # Black, the code formatter, natively supports pre-commit - repo: https://github.com/psf/black - rev: "22.6.0" # Keep in sync with blacken-docs + rev: "22.8.0" # Keep in sync with blacken-docs hooks: - id: black @@ -59,17 +59,17 @@ repos: hooks: - id: blacken-docs additional_dependencies: - - black==22.6.0 # keep in sync with black hook + - black==22.8.0 # keep in sync with black hook # Changes tabs to spaces - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: "v1.3.0" + rev: "v1.3.1" hooks: - id: remove-tabs exclude: (^docs/.*|\.patch)?$ - repo: https://github.com/sirosen/texthooks - rev: "0.3.1" + rev: "0.4.0" hooks: - id: fix-ligatures - id: fix-smartquotes @@ -113,7 +113,7 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v2.14.5" + rev: "v2.15.2" hooks: - id: pylint files: ^pybind11 @@ -146,12 +146,14 @@ repos: additional_dependencies: [cmake, ninja] # Check for spelling +# Use tools/codespell_ignore_lines_from_errors.py +# to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.1.0" + rev: "v2.2.1" hooks: - id: codespell exclude: ".supp$" - args: ["-L", "nd,ot,thist", "--exclude-file", ".codespell-ignorelines"] + args: ["-x", ".codespell-ignore-lines"] # Check for common shell mistakes - repo: https://github.com/shellcheck-py/shellcheck-py diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index edab15fcb..71960b803 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -39,15 +39,42 @@ The ``PYBIND11_MAKE_OPAQUE`` macro does *not* require the above workarounds. Global Interpreter Lock (GIL) ============================= -When calling a C++ function from Python, the GIL is always held. +The Python C API dictates that the Global Interpreter Lock (GIL) must always +be held by the current thread to safely access Python objects. As a result, +when Python calls into C++ via pybind11 the GIL must be held, and pybind11 +will never implicitly release the GIL. + +.. code-block:: cpp + + void my_function() { + /* GIL is held when this function is called from Python */ + } + + PYBIND11_MODULE(example, m) { + m.def("my_function", &my_function); + } + +pybind11 will ensure that the GIL is held when it knows that it is calling +Python code. For example, if a Python callback is passed to C++ code via +``std::function``, when C++ code calls the function the built-in wrapper +will acquire the GIL before calling the Python callback. Similarly, the +``PYBIND11_OVERRIDE`` family of macros will acquire the GIL before calling +back into Python. + +When writing C++ code that is called from other C++ code, if that code accesses +Python state, it must explicitly acquire and release the GIL. + The classes :class:`gil_scoped_release` and :class:`gil_scoped_acquire` can be used to acquire and release the global interpreter lock in the body of a C++ function call. In this way, long-running C++ code can be parallelized using -multiple Python threads. Taking :ref:`overriding_virtuals` as an example, this +multiple Python threads, **but great care must be taken** when any +:class:`gil_scoped_release` appear: if there is any way that the C++ code +can access Python objects, :class:`gil_scoped_acquire` should be used to +reacquire the GIL. Taking :ref:`overriding_virtuals` as an example, this could be realized as follows (important changes highlighted): .. code-block:: cpp - :emphasize-lines: 8,9,31,32 + :emphasize-lines: 8,30,31 class PyAnimal : public Animal { public: @@ -56,9 +83,7 @@ could be realized as follows (important changes highlighted): /* Trampoline (need one for each virtual function) */ std::string go(int n_times) { - /* Acquire GIL before calling Python code */ - py::gil_scoped_acquire acquire; - + /* PYBIND11_OVERRIDE_PURE will acquire the GIL before accessing Python state */ PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ @@ -78,7 +103,8 @@ could be realized as follows (important changes highlighted): .def(py::init<>()); m.def("call_go", [](Animal *animal) -> std::string { - /* Release GIL before calling into (potentially long-running) C++ code */ + // GIL is held when called from Python code. Release GIL before + // calling into (potentially long-running) C++ code py::gil_scoped_release release; return call_go(animal); }); diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 0d6b4c9b0..2339b7b78 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -55,6 +55,9 @@ extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObjec return PyProperty_Type.tp_descr_set(self, cls, value); } +// Forward declaration to use in `make_static_property_type()` +inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type); + /** A `static_property` is the same as a `property` but the `__get__()` and `__set__()` methods are modified to always use the object type instead of a concrete instance. Return value: New reference. */ @@ -87,6 +90,13 @@ inline PyTypeObject *make_static_property_type() { pybind11_fail("make_static_property_type(): failure in PyType_Ready()!"); } +# if PY_VERSION_HEX >= 0x030C0000 + // PRE 3.12 FEATURE FREEZE. PLEASE REVIEW AFTER FREEZE. + // Since Python-3.12 property-derived types are required to + // have dynamic attributes (to set `__doc__`) + enable_dynamic_attributes(heap_type); +# endif + setattr((PyObject *) type, "__module__", str("pybind11_builtins")); PYBIND11_SET_OLDPY_QUALNAME(type, name_obj); diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 56e5edc5a..369e18649 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -1401,7 +1401,7 @@ PYBIND11_NOINLINE void register_structured_dtype(any_container oss << '}'; auto format_str = oss.str(); - // Sanity check: verify that NumPy properly parses our buffer format string + // Smoke test: verify that NumPy properly parses our buffer format string auto &api = npy_api::get(); auto arr = array(buffer_info(nullptr, itemsize, format_str, 1)); if (!api.PyArray_EquivTypes_(dtype_ptr, arr.dtype().ptr())) { diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6ba1f5f20..f4ba459f2 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -473,7 +473,7 @@ struct error_fetch_and_normalize { + " failed to obtain the name " "of the normalized active exception type."); } -#if defined(PYPY_VERSION) +#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x07030a00 // This behavior runs the risk of masking errors in the error handling, but avoids a // conflict with PyPy, which relies on the normalization here to change OSError to // FileNotFoundError (https://github.com/pybind/pybind11/issues/4075). diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index a3835c21e..937f6c233 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -115,7 +115,7 @@ public: #if defined(PYPY_VERSION) PyObject *globals = PyEval_GetGlobals(); PyObject *result = PyRun_String("import gc\n" - "for i in range(2):" + "for i in range(2):\n" " gc.collect()\n", Py_file_input, globals, diff --git a/tests/test_eigen.py b/tests/test_eigen.py index a1c114aed..9c6485de3 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -251,14 +251,14 @@ def array_copy_but_one(a, r, c, v): def test_eigen_return_references(): """Tests various ways of returning references and non-referencing copies""" - master = np.ones((10, 10)) + primary = np.ones((10, 10)) a = m.ReturnTester() a_get1 = a.get() assert not a_get1.flags.owndata and a_get1.flags.writeable - assign_both(a_get1, master, 3, 3, 5) + assign_both(a_get1, primary, 3, 3, 5) a_get2 = a.get_ptr() assert not a_get2.flags.owndata and a_get2.flags.writeable - assign_both(a_get1, master, 2, 3, 6) + assign_both(a_get1, primary, 2, 3, 6) a_view1 = a.view() assert not a_view1.flags.owndata and not a_view1.flags.writeable @@ -271,25 +271,25 @@ def test_eigen_return_references(): a_copy1 = a.copy_get() assert a_copy1.flags.owndata and a_copy1.flags.writeable - np.testing.assert_array_equal(a_copy1, master) + np.testing.assert_array_equal(a_copy1, primary) a_copy1[7, 7] = -44 # Shouldn't affect anything else - c1want = array_copy_but_one(master, 7, 7, -44) + c1want = array_copy_but_one(primary, 7, 7, -44) a_copy2 = a.copy_view() assert a_copy2.flags.owndata and a_copy2.flags.writeable - np.testing.assert_array_equal(a_copy2, master) + np.testing.assert_array_equal(a_copy2, primary) a_copy2[4, 4] = -22 # Shouldn't affect anything else - c2want = array_copy_but_one(master, 4, 4, -22) + c2want = array_copy_but_one(primary, 4, 4, -22) a_ref1 = a.ref() assert not a_ref1.flags.owndata and a_ref1.flags.writeable - assign_both(a_ref1, master, 1, 1, 15) + assign_both(a_ref1, primary, 1, 1, 15) a_ref2 = a.ref_const() assert not a_ref2.flags.owndata and not a_ref2.flags.writeable with pytest.raises(ValueError): a_ref2[5, 5] = 33 a_ref3 = a.ref_safe() assert not a_ref3.flags.owndata and a_ref3.flags.writeable - assign_both(a_ref3, master, 0, 7, 99) + assign_both(a_ref3, primary, 0, 7, 99) a_ref4 = a.ref_const_safe() assert not a_ref4.flags.owndata and not a_ref4.flags.writeable with pytest.raises(ValueError): @@ -297,23 +297,23 @@ def test_eigen_return_references(): a_copy3 = a.copy_ref() assert a_copy3.flags.owndata and a_copy3.flags.writeable - np.testing.assert_array_equal(a_copy3, master) + np.testing.assert_array_equal(a_copy3, primary) a_copy3[8, 1] = 11 - c3want = array_copy_but_one(master, 8, 1, 11) + c3want = array_copy_but_one(primary, 8, 1, 11) a_copy4 = a.copy_ref_const() assert a_copy4.flags.owndata and a_copy4.flags.writeable - np.testing.assert_array_equal(a_copy4, master) + np.testing.assert_array_equal(a_copy4, primary) a_copy4[8, 4] = 88 - c4want = array_copy_but_one(master, 8, 4, 88) + c4want = array_copy_but_one(primary, 8, 4, 88) a_block1 = a.block(3, 3, 2, 2) assert not a_block1.flags.owndata and a_block1.flags.writeable a_block1[0, 0] = 55 - master[3, 3] = 55 + primary[3, 3] = 55 a_block2 = a.block_safe(2, 2, 3, 2) assert not a_block2.flags.owndata and a_block2.flags.writeable a_block2[2, 1] = -123 - master[4, 3] = -123 + primary[4, 3] = -123 a_block3 = a.block_const(6, 7, 4, 3) assert not a_block3.flags.owndata and not a_block3.flags.writeable with pytest.raises(ValueError): @@ -321,18 +321,18 @@ def test_eigen_return_references(): a_copy5 = a.copy_block(2, 2, 2, 3) assert a_copy5.flags.owndata and a_copy5.flags.writeable - np.testing.assert_array_equal(a_copy5, master[2:4, 2:5]) + np.testing.assert_array_equal(a_copy5, primary[2:4, 2:5]) a_copy5[1, 1] = 777 - c5want = array_copy_but_one(master[2:4, 2:5], 1, 1, 777) + c5want = array_copy_but_one(primary[2:4, 2:5], 1, 1, 777) a_corn1 = a.corners() assert not a_corn1.flags.owndata and a_corn1.flags.writeable a_corn1 *= 50 a_corn1[1, 1] = 999 - master[0, 0] = 50 - master[0, 9] = 50 - master[9, 0] = 50 - master[9, 9] = 999 + primary[0, 0] = 50 + primary[0, 9] = 50 + primary[9, 0] = 50 + primary[9, 9] = 999 a_corn2 = a.corners_const() assert not a_corn2.flags.owndata and not a_corn2.flags.writeable with pytest.raises(ValueError): @@ -340,22 +340,22 @@ def test_eigen_return_references(): # All of the changes made all the way along should be visible everywhere # now (except for the copies, of course) - np.testing.assert_array_equal(a_get1, master) - np.testing.assert_array_equal(a_get2, master) - np.testing.assert_array_equal(a_view1, master) - np.testing.assert_array_equal(a_view2, master) - np.testing.assert_array_equal(a_ref1, master) - np.testing.assert_array_equal(a_ref2, master) - np.testing.assert_array_equal(a_ref3, master) - np.testing.assert_array_equal(a_ref4, master) - np.testing.assert_array_equal(a_block1, master[3:5, 3:5]) - np.testing.assert_array_equal(a_block2, master[2:5, 2:4]) - np.testing.assert_array_equal(a_block3, master[6:10, 7:10]) + np.testing.assert_array_equal(a_get1, primary) + np.testing.assert_array_equal(a_get2, primary) + np.testing.assert_array_equal(a_view1, primary) + np.testing.assert_array_equal(a_view2, primary) + np.testing.assert_array_equal(a_ref1, primary) + np.testing.assert_array_equal(a_ref2, primary) + np.testing.assert_array_equal(a_ref3, primary) + np.testing.assert_array_equal(a_ref4, primary) + np.testing.assert_array_equal(a_block1, primary[3:5, 3:5]) + np.testing.assert_array_equal(a_block2, primary[2:5, 2:4]) + np.testing.assert_array_equal(a_block3, primary[6:10, 7:10]) np.testing.assert_array_equal( - a_corn1, master[0 :: master.shape[0] - 1, 0 :: master.shape[1] - 1] + a_corn1, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] ) np.testing.assert_array_equal( - a_corn2, master[0 :: master.shape[0] - 1, 0 :: master.shape[1] - 1] + a_corn2, primary[0 :: primary.shape[0] - 1, 0 :: primary.shape[1] - 1] ) np.testing.assert_array_equal(a_copy1, c1want) diff --git a/tools/codespell_ignore_lines_from_errors.py b/tools/codespell_ignore_lines_from_errors.py new file mode 100644 index 000000000..5403ec3ad --- /dev/null +++ b/tools/codespell_ignore_lines_from_errors.py @@ -0,0 +1,35 @@ +"""Simple script for rebuilding .codespell-ignore-lines + +Usage: + +cat < /dev/null > .codespell-ignore-lines +pre-commit run --all-files codespell >& /tmp/codespell_errors.txt +python3 tools/codespell_ignore_lines_from_errors.py /tmp/codespell_errors.txt > .codespell-ignore-lines + +git diff to review changes, then commit, push. +""" + +import sys +from typing import List + + +def run(args: List[str]) -> None: + assert len(args) == 1, "codespell_errors.txt" + cache = {} + done = set() + for line in sorted(open(args[0]).read().splitlines()): + i = line.find(" ==> ") + if i > 0: + flds = line[:i].split(":") + if len(flds) >= 2: + filename, line_num = flds[:2] + if filename not in cache: + cache[filename] = open(filename).read().splitlines() + supp = cache[filename][int(line_num) - 1] + if supp not in done: + print(supp) + done.add(supp) + + +if __name__ == "__main__": + run(args=sys.argv[1:])