Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/pybind11/detail/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,10 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_VisitManagedDict(self, visit, arg);
int ret = PyObject_VisitManagedDict(self, visit, arg);
if (ret) {
return ret;
}
#else
PyObject *&dict = *_PyObject_GetDictPtr(self);
Py_VISIT(dict);
Expand Down
6 changes: 6 additions & 0 deletions tests/test_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ TEST_SUBMODULE(class_, m) {
~NoConstructorNew() { print_destroyed(this); }
};

struct DynamicAttr {
DynamicAttr() = default;
};

py::class_<NoConstructor>(m, "NoConstructor")
.def_static("new_instance", &NoConstructor::new_instance, "Return an instance");

Expand All @@ -112,6 +116,8 @@ TEST_SUBMODULE(class_, m) {
.def_static("__new__",
[](const py::object &) { return NoConstructorNew::new_instance(); });

py::class_<DynamicAttr>(m, "DynamicAttr", py::dynamic_attr()).def(py::init<>());

// test_pass_unique_ptr
struct ToBeHeldByUniquePtr {};
py::class_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr")
Expand Down
18 changes: 18 additions & 0 deletions tests/test_class.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import gc
import sys
from unittest import mock

Expand All @@ -18,6 +19,13 @@ def refcount_immortal(ob: object) -> int:
return sys.getrefcount(ob)


MANAGED_DICT_GET_REFERRERS_SUPPORTED = (
env.CPYTHON
and sys.version_info >= (3, 13, 13)
and (sys.version_info < (3, 14) or sys.version_info >= (3, 14, 4))
)


def test_obj_class_name():
expected_name = "UserType" if env.PYPY else "pybind11_tests.UserType"
assert m.obj_class_name(UserType(1)) == expected_name
Expand Down Expand Up @@ -45,6 +53,16 @@ def test_instance(msg):
assert cstats.alive() == 0


@pytest.mark.skipif(
not MANAGED_DICT_GET_REFERRERS_SUPPORTED,
reason="Requires CPython 3.13.13+ or 3.14.4+ managed dict traversal support",
)
def test_get_referrers():
instance = m.DynamicAttr()
instance.a = "test"
assert instance in gc.get_referrers(instance.__dict__)


def test_instance_new():
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)

Expand Down
Loading