Skip to content

Commit 6cf6bf2

Browse files
Fix confusing weakref constructor overload (#2832)
* Demonstrate issue with weakref constructor overloads * Fix weakref constructor to convert on being passed a non-weakref object * Improve on nonlocal-scoped variable in test_weakref * Keep backwards-compatibility by introducing PYBIND11_OBJECT_CVT_DEFAULT macro * Simplify test_weakref
1 parent 932769b commit 6cf6bf2

File tree

3 files changed

+54
-1
lines changed

3 files changed

+54
-1
lines changed

include/pybind11/pytypes.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,10 @@ PYBIND11_NAMESPACE_END(detail)
819819
: Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \
820820
{ if (!m_ptr) throw error_already_set(); }
821821

822+
#define PYBIND11_OBJECT_CVT_DEFAULT(Name, Parent, CheckFun, ConvertFun) \
823+
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \
824+
Name() : Parent() { }
825+
822826
#define PYBIND11_OBJECT_CHECK_FAILED(Name, o_ptr) \
823827
::pybind11::type_error("Object of type '" + \
824828
::pybind11::detail::get_fully_qualified_tp_name(Py_TYPE(o_ptr)) + \
@@ -1168,11 +1172,16 @@ class float_ : public object {
11681172

11691173
class weakref : public object {
11701174
public:
1171-
PYBIND11_OBJECT_DEFAULT(weakref, object, PyWeakref_Check)
1175+
PYBIND11_OBJECT_CVT_DEFAULT(weakref, object, PyWeakref_Check, raw_weakref)
11721176
explicit weakref(handle obj, handle callback = {})
11731177
: object(PyWeakref_NewRef(obj.ptr(), callback.ptr()), stolen_t{}) {
11741178
if (!m_ptr) pybind11_fail("Could not allocate weak reference!");
11751179
}
1180+
1181+
private:
1182+
static PyObject *raw_weakref(PyObject *o) {
1183+
return PyWeakref_NewRef(o, nullptr);
1184+
}
11761185
};
11771186

11781187
class slice : public object {

tests/test_pytypes.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -424,4 +424,14 @@ TEST_SUBMODULE(pytypes, m) {
424424
m.def("pass_to_pybind11_bytes", [](py::bytes b) { return py::len(b); });
425425
m.def("pass_to_pybind11_str", [](py::str s) { return py::len(s); });
426426
m.def("pass_to_std_string", [](std::string s) { return s.size(); });
427+
428+
// test_weakref
429+
m.def("weakref_from_handle",
430+
[](py::handle h) { return py::weakref(h); });
431+
m.def("weakref_from_handle_and_function",
432+
[](py::handle h, py::function f) { return py::weakref(h, f); });
433+
m.def("weakref_from_object",
434+
[](py::object o) { return py::weakref(o); });
435+
m.def("weakref_from_object_and_function",
436+
[](py::object o, py::function f) { return py::weakref(o, f); });
427437
}

tests/test_pytypes.py

+34
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,37 @@ def test_pass_bytes_or_unicode_to_string_types():
541541
else:
542542
with pytest.raises(TypeError):
543543
m.pass_to_pybind11_str(malformed_utf8)
544+
545+
546+
@pytest.mark.parametrize(
547+
"create_weakref, create_weakref_with_callback",
548+
[
549+
(m.weakref_from_handle, m.weakref_from_handle_and_function),
550+
(m.weakref_from_object, m.weakref_from_object_and_function),
551+
],
552+
)
553+
def test_weakref(create_weakref, create_weakref_with_callback):
554+
from weakref import getweakrefcount
555+
556+
# Apparently, you cannot weakly reference an object()
557+
class WeaklyReferenced(object):
558+
pass
559+
560+
def callback(wr):
561+
# No `nonlocal` in Python 2
562+
callback.called = True
563+
564+
obj = WeaklyReferenced()
565+
assert getweakrefcount(obj) == 0
566+
wr = create_weakref(obj) # noqa: F841
567+
assert getweakrefcount(obj) == 1
568+
569+
obj = WeaklyReferenced()
570+
assert getweakrefcount(obj) == 0
571+
callback.called = False
572+
wr = create_weakref_with_callback(obj, callback) # noqa: F841
573+
assert getweakrefcount(obj) == 1
574+
assert not callback.called
575+
del obj
576+
pytest.gc_collect()
577+
assert callback.called

0 commit comments

Comments
 (0)