-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: a long-standing bug in the handling of Python multiple inheritance #4762
Changes from 25 commits
c9a56ac
bdd938a
61f7529
1fb7dc3
b9f3380
8ea1b8c
1d4f9ff
775aab5
d37b540
7f8b208
eb09c6c
a77c1c6
eec2d81
9c7d267
7c7d78d
d708836
cf5958d
c89561f
a2f95e1
4f90d85
0a0debd
52b7993
33805e2
adb5bad
c516151
f3bb31e
0a9599f
86ca4e1
9ae6cba
5f5fd6a
df27188
52de174
1b157fc
7a6c436
6ab605b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from unittest import mock | ||
|
||
import pytest | ||
|
||
import env | ||
|
@@ -203,6 +205,18 @@ def __init__(self): | |
assert msg(exc_info.value) == expected | ||
|
||
|
||
@pytest.mark.parametrize( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a new feature enabled by this PR, or a bug fixed along with the PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a bug fixed along with this PR. |
||
"mock_return_value", [None, (1, 2, 3), m.Pet("Polly", "parrot"), m.Dog("Molly")] | ||
) | ||
def test_mock_new(mock_return_value): | ||
with mock.patch.object( | ||
m.Pet, "__new__", return_value=mock_return_value | ||
) as mock_new: | ||
obj = m.Pet("Noname", "Nospecies") | ||
assert obj is mock_return_value | ||
mock_new.assert_called_once_with(m.Pet, "Noname", "Nospecies") | ||
|
||
|
||
def test_automatic_upcasting(): | ||
assert type(m.return_class_1()).__name__ == "DerivedClass1" | ||
assert type(m.return_class_2()).__name__ == "DerivedClass2" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#include "pybind11_tests.h" | ||
|
||
namespace test_python_multiple_inheritance { | ||
|
||
// Copied from: | ||
// https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python_multiple_inheritance.h | ||
|
||
struct CppBase { | ||
explicit CppBase(int value) : base_value(value) {} | ||
int get_base_value() const { return base_value; } | ||
void reset_base_value(int new_value) { base_value = new_value; } | ||
|
||
private: | ||
int base_value; | ||
}; | ||
|
||
struct CppDrvd : CppBase { | ||
explicit CppDrvd(int value) : CppBase(value), drvd_value(value * 3) {} | ||
int get_drvd_value() const { return drvd_value; } | ||
void reset_drvd_value(int new_value) { drvd_value = new_value; } | ||
|
||
int get_base_value_from_drvd() const { return get_base_value(); } | ||
void reset_base_value_from_drvd(int new_value) { reset_base_value(new_value); } | ||
|
||
private: | ||
int drvd_value; | ||
}; | ||
|
||
} // namespace test_python_multiple_inheritance | ||
|
||
TEST_SUBMODULE(python_multiple_inheritance, m) { | ||
using namespace test_python_multiple_inheritance; | ||
|
||
py::class_<CppBase>(m, "CppBase") | ||
.def(py::init<int>()) | ||
.def("get_base_value", &CppBase::get_base_value) | ||
.def("reset_base_value", &CppBase::reset_base_value); | ||
|
||
py::class_<CppDrvd, CppBase>(m, "CppDrvd") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By convention, do we require users to list all of the base classes in the template arg list? I thought the usual way is py::class_, but maybe just doing that Python won't be aware the Py object should have a base class of Py.CppBase? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes the convention is to list all of the base classes if we want the base class methods to surface to the Pybind class. |
||
.def(py::init<int>()) | ||
.def("get_drvd_value", &CppDrvd::get_drvd_value) | ||
.def("reset_drvd_value", &CppDrvd::reset_drvd_value) | ||
.def("get_base_value_from_drvd", &CppDrvd::get_base_value_from_drvd) | ||
.def("reset_base_value_from_drvd", &CppDrvd::reset_base_value_from_drvd); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Adapted from: | ||
# https://github.com/google/clif/blob/5718e4d0807fd3b6a8187dde140069120b81ecef/clif/testing/python/python_multiple_inheritance_test.py | ||
|
||
from pybind11_tests import python_multiple_inheritance as m | ||
|
||
|
||
class PC(m.CppBase): | ||
pass | ||
|
||
|
||
class PPCC(PC, m.CppDrvd): | ||
pass | ||
|
||
|
||
def test_PC(): | ||
d = PC(11) | ||
assert d.get_base_value() == 11 | ||
d.reset_base_value(13) | ||
assert d.get_base_value() == 13 | ||
|
||
|
||
def test_PPCC(): | ||
d = PPCC(11) | ||
assert d.get_drvd_value() == 33 | ||
d.reset_drvd_value(55) | ||
assert d.get_drvd_value() == 55 | ||
|
||
assert d.get_base_value() == 11 | ||
assert d.get_base_value_from_drvd() == 11 | ||
d.reset_base_value(20) | ||
assert d.get_base_value() == 20 | ||
assert d.get_base_value_from_drvd() == 20 | ||
d.reset_base_value_from_drvd(30) | ||
assert d.get_base_value() == 30 | ||
assert d.get_base_value_from_drvd() == 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks dangerous. No initializer guarantees types is not nullptr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved because inst check above.