Convert test_multiple_inheritance to new style

Significant rearrangement, but no new tests added.
This commit is contained in:
Jason Rhinelander 2017-07-25 00:05:15 -04:00
parent 793726014d
commit 44a17e1f3d
2 changed files with 149 additions and 173 deletions

View File

@ -11,47 +11,74 @@
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include "constructor_stats.h" #include "constructor_stats.h"
struct Base1 { // Many bases for testing that multiple inheritance from many classes (i.e. requiring extra
Base1(int i) : i(i) { } // space for holder constructed flags) works.
int foo() { return i; }
int i;
};
struct Base2 {
Base2(int i) : i(i) { }
int bar() { return i; }
int i;
};
template <int N> struct BaseN { template <int N> struct BaseN {
BaseN(int i) : i(i) { } BaseN(int i) : i(i) { }
int i; int i;
}; };
struct Base12 : Base1, Base2 { // test_mi_static_properties
Base12(int i, int j) : Base1(i), Base2(j) { } struct Vanilla {
std::string vanilla() { return "Vanilla"; };
}; };
struct WithStatic1 {
struct MIType : Base12 { static std::string static_func1() { return "WithStatic1"; };
MIType(int i, int j) : Base12(i, j) { } static int static_value1;
}; };
struct WithStatic2 {
static std::string static_func2() { return "WithStatic2"; };
static int static_value2;
};
struct VanillaStaticMix1 : Vanilla, WithStatic1, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix1"; }
static int static_value;
};
struct VanillaStaticMix2 : WithStatic1, Vanilla, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix2"; }
static int static_value;
};
int WithStatic1::static_value1 = 1;
int WithStatic2::static_value2 = 2;
int VanillaStaticMix1::static_value = 12;
int VanillaStaticMix2::static_value = 12;
test_initializer multiple_inheritance([](py::module &m) { TEST_SUBMODULE(multiple_inheritance, m) {
// test_multiple_inheritance_mix1
// test_multiple_inheritance_mix2
struct Base1 {
Base1(int i) : i(i) { }
int foo() { return i; }
int i;
};
py::class_<Base1> b1(m, "Base1"); py::class_<Base1> b1(m, "Base1");
b1.def(py::init<int>()) b1.def(py::init<int>())
.def("foo", &Base1::foo); .def("foo", &Base1::foo);
struct Base2 {
Base2(int i) : i(i) { }
int bar() { return i; }
int i;
};
py::class_<Base2> b2(m, "Base2"); py::class_<Base2> b2(m, "Base2");
b2.def(py::init<int>()) b2.def(py::init<int>())
.def("bar", &Base2::bar); .def("bar", &Base2::bar);
py::class_<Base12, Base1, Base2>(m, "Base12");
// test_multiple_inheritance_cpp
struct Base12 : Base1, Base2 {
Base12(int i, int j) : Base1(i), Base2(j) { }
};
struct MIType : Base12 {
MIType(int i, int j) : Base12(i, j) { }
};
py::class_<Base12, Base1, Base2>(m, "Base12");
py::class_<MIType, Base12>(m, "MIType") py::class_<MIType, Base12>(m, "MIType")
.def(py::init<int, int>()); .def(py::init<int, int>());
// Many bases for testing that multiple inheritance from many classes (i.e. requiring extra
// space for holder constructed flags) works. // test_multiple_inheritance_python_many_bases
#define PYBIND11_BASEN(N) py::class_<BaseN<N>>(m, "BaseN" #N).def(py::init<int>()).def("f" #N, [](BaseN<N> &b) { return b.i + N; }) #define PYBIND11_BASEN(N) py::class_<BaseN<N>>(m, "BaseN" #N).def(py::init<int>()).def("f" #N, [](BaseN<N> &b) { return b.i + N; })
PYBIND11_BASEN( 1); PYBIND11_BASEN( 2); PYBIND11_BASEN( 3); PYBIND11_BASEN( 4); PYBIND11_BASEN( 1); PYBIND11_BASEN( 2); PYBIND11_BASEN( 3); PYBIND11_BASEN( 4);
PYBIND11_BASEN( 5); PYBIND11_BASEN( 6); PYBIND11_BASEN( 7); PYBIND11_BASEN( 8); PYBIND11_BASEN( 5); PYBIND11_BASEN( 6); PYBIND11_BASEN( 7); PYBIND11_BASEN( 8);
@ -67,55 +94,50 @@ test_initializer multiple_inheritance([](py::module &m) {
// }; // };
// py::class_<Base12v2>(m, "Base12v2", b1, b2) // py::class_<Base12v2>(m, "Base12v2", b1, b2)
// .def(py::init<int, int>()); // .def(py::init<int, int>());
});
/* Test the case where not all base classes are specified,
and where pybind11 requires the py::multiple_inheritance
flag to perform proper casting between types */
struct Base1a { // test_multiple_inheritance_virtbase
Base1a(int i) : i(i) { } // Test the case where not all base classes are specified, and where pybind11 requires the
int foo() { return i; } // py::multiple_inheritance flag to perform proper casting between types.
int i; struct Base1a {
}; Base1a(int i) : i(i) { }
int foo() { return i; }
struct Base2a { int i;
Base2a(int i) : i(i) { } };
int bar() { return i; }
int i;
};
struct Base12a : Base1a, Base2a {
Base12a(int i, int j) : Base1a(i), Base2a(j) { }
};
test_initializer multiple_inheritance_nonexplicit([](py::module &m) {
py::class_<Base1a, std::shared_ptr<Base1a>>(m, "Base1a") py::class_<Base1a, std::shared_ptr<Base1a>>(m, "Base1a")
.def(py::init<int>()) .def(py::init<int>())
.def("foo", &Base1a::foo); .def("foo", &Base1a::foo);
struct Base2a {
Base2a(int i) : i(i) { }
int bar() { return i; }
int i;
};
py::class_<Base2a, std::shared_ptr<Base2a>>(m, "Base2a") py::class_<Base2a, std::shared_ptr<Base2a>>(m, "Base2a")
.def(py::init<int>()) .def(py::init<int>())
.def("bar", &Base2a::bar); .def("bar", &Base2a::bar);
struct Base12a : Base1a, Base2a {
Base12a(int i, int j) : Base1a(i), Base2a(j) { }
};
py::class_<Base12a, /* Base1 missing */ Base2a, py::class_<Base12a, /* Base1 missing */ Base2a,
std::shared_ptr<Base12a>>(m, "Base12a", py::multiple_inheritance()) std::shared_ptr<Base12a>>(m, "Base12a", py::multiple_inheritance())
.def(py::init<int, int>()); .def(py::init<int, int>());
m.def("bar_base2a", [](Base2a *b) { return b->bar(); }); m.def("bar_base2a", [](Base2a *b) { return b->bar(); });
m.def("bar_base2a_sharedptr", [](std::shared_ptr<Base2a> b) { return b->bar(); }); m.def("bar_base2a_sharedptr", [](std::shared_ptr<Base2a> b) { return b->bar(); });
});
// Issue #801: invalid casting to derived type with MI bases // test_mi_unaligned_base
struct I801B1 { int a = 1; virtual ~I801B1() = default; }; // test_mi_base_return
struct I801B2 { int b = 2; virtual ~I801B2() = default; }; // Issue #801: invalid casting to derived type with MI bases
struct I801C : I801B1, I801B2 {}; struct I801B1 { int a = 1; virtual ~I801B1() = default; };
struct I801D : I801C {}; // Indirect MI struct I801B2 { int b = 2; virtual ~I801B2() = default; };
// Unregistered classes: struct I801C : I801B1, I801B2 {};
struct I801B3 { int c = 3; virtual ~I801B3() = default; }; struct I801D : I801C {}; // Indirect MI
struct I801E : I801B3, I801D {}; // Unregistered classes:
struct I801B3 { int c = 3; virtual ~I801B3() = default; };
struct I801E : I801B3, I801D {};
test_initializer multiple_inheritance_casting([](py::module &m) {
py::class_<I801B1, std::shared_ptr<I801B1>>(m, "I801B1").def(py::init<>()).def_readonly("a", &I801B1::a); py::class_<I801B1, std::shared_ptr<I801B1>>(m, "I801B1").def(py::init<>()).def_readonly("a", &I801B1::a);
py::class_<I801B2, std::shared_ptr<I801B2>>(m, "I801B2").def(py::init<>()).def_readonly("b", &I801B2::b); py::class_<I801B2, std::shared_ptr<I801B2>>(m, "I801B2").def(py::init<>()).def_readonly("b", &I801B2::b);
py::class_<I801C, I801B1, I801B2, std::shared_ptr<I801C>>(m, "I801C").def(py::init<>()); py::class_<I801C, I801B1, I801B2, std::shared_ptr<I801C>>(m, "I801C").def(py::init<>());
@ -141,46 +163,9 @@ test_initializer multiple_inheritance_casting([](py::module &m) {
// isn't pybind-registered (and uses multiple-inheritance to offset the pybind base) // isn't pybind-registered (and uses multiple-inheritance to offset the pybind base)
m.def("i801e_c", []() -> I801C * { return new I801E(); }); m.def("i801e_c", []() -> I801C * { return new I801E(); });
m.def("i801e_b2", []() -> I801B2 * { return new I801E(); }); m.def("i801e_b2", []() -> I801B2 * { return new I801E(); });
});
struct Vanilla { // test_mi_static_properties
std::string vanilla() { return "Vanilla"; };
};
struct WithStatic1 {
static std::string static_func1() { return "WithStatic1"; };
static int static_value1;
};
struct WithStatic2 {
static std::string static_func2() { return "WithStatic2"; };
static int static_value2;
};
struct WithDict { };
struct VanillaStaticMix1 : Vanilla, WithStatic1, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix1"; }
static int static_value;
};
struct VanillaStaticMix2 : WithStatic1, Vanilla, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix2"; }
static int static_value;
};
struct VanillaDictMix1 : Vanilla, WithDict { };
struct VanillaDictMix2 : WithDict, Vanilla { };
int WithStatic1::static_value1 = 1;
int WithStatic2::static_value2 = 2;
int VanillaStaticMix1::static_value = 12;
int VanillaStaticMix2::static_value = 12;
test_initializer mi_static_properties([](py::module &pm) {
auto m = pm.def_submodule("mi");
py::class_<Vanilla>(m, "Vanilla") py::class_<Vanilla>(m, "Vanilla")
.def(py::init<>()) .def(py::init<>())
.def("vanilla", &Vanilla::vanilla); .def("vanilla", &Vanilla::vanilla);
@ -207,9 +192,13 @@ test_initializer mi_static_properties([](py::module &pm) {
.def_static("static_func", &VanillaStaticMix2::static_func) .def_static("static_func", &VanillaStaticMix2::static_func)
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value); .def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION)
struct WithDict { };
struct VanillaDictMix1 : Vanilla, WithDict { };
struct VanillaDictMix2 : WithDict, Vanilla { };
py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>()); py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>());
py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>()); py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>());
py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>()); py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>());
#endif #endif
}); }

View File

@ -1,19 +1,16 @@
import pytest import pytest
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import multiple_inheritance as m
def test_multiple_inheritance_cpp(): def test_multiple_inheritance_cpp():
from pybind11_tests import MIType mt = m.MIType(3, 4)
mt = MIType(3, 4)
assert mt.foo() == 3 assert mt.foo() == 3
assert mt.bar() == 4 assert mt.bar() == 4
def test_multiple_inheritance_mix1(): def test_multiple_inheritance_mix1():
from pybind11_tests import Base2
class Base1: class Base1:
def __init__(self, i): def __init__(self, i):
self.i = i self.i = i
@ -21,10 +18,10 @@ def test_multiple_inheritance_mix1():
def foo(self): def foo(self):
return self.i return self.i
class MITypePy(Base1, Base2): class MITypePy(Base1, m.Base2):
def __init__(self, i, j): def __init__(self, i, j):
Base1.__init__(self, i) Base1.__init__(self, i)
Base2.__init__(self, j) m.Base2.__init__(self, j)
mt = MITypePy(3, 4) mt = MITypePy(3, 4)
@ -33,7 +30,6 @@ def test_multiple_inheritance_mix1():
def test_multiple_inheritance_mix2(): def test_multiple_inheritance_mix2():
from pybind11_tests import Base1
class Base2: class Base2:
def __init__(self, i): def __init__(self, i):
@ -42,9 +38,9 @@ def test_multiple_inheritance_mix2():
def bar(self): def bar(self):
return self.i return self.i
class MITypePy(Base1, Base2): class MITypePy(m.Base1, Base2):
def __init__(self, i, j): def __init__(self, i, j):
Base1.__init__(self, i) m.Base1.__init__(self, i)
Base2.__init__(self, j) Base2.__init__(self, j)
mt = MITypePy(3, 4) mt = MITypePy(3, 4)
@ -54,41 +50,40 @@ def test_multiple_inheritance_mix2():
def test_multiple_inheritance_python(): def test_multiple_inheritance_python():
from pybind11_tests import Base1, Base2
class MI1(Base1, Base2): class MI1(m.Base1, m.Base2):
def __init__(self, i, j): def __init__(self, i, j):
Base1.__init__(self, i) m.Base1.__init__(self, i)
Base2.__init__(self, j) m.Base2.__init__(self, j)
class B1(object): class B1(object):
def v(self): def v(self):
return 1 return 1
class MI2(B1, Base1, Base2): class MI2(B1, m.Base1, m.Base2):
def __init__(self, i, j): def __init__(self, i, j):
B1.__init__(self) B1.__init__(self)
Base1.__init__(self, i) m.Base1.__init__(self, i)
Base2.__init__(self, j) m.Base2.__init__(self, j)
class MI3(MI2): class MI3(MI2):
def __init__(self, i, j): def __init__(self, i, j):
MI2.__init__(self, i, j) MI2.__init__(self, i, j)
class MI4(MI3, Base2): class MI4(MI3, m.Base2):
def __init__(self, i, j, k): def __init__(self, i, j, k):
MI2.__init__(self, j, k) MI2.__init__(self, j, k)
Base2.__init__(self, i) m.Base2.__init__(self, i)
class MI5(Base2, B1, Base1): class MI5(m.Base2, B1, m.Base1):
def __init__(self, i, j): def __init__(self, i, j):
B1.__init__(self) B1.__init__(self)
Base1.__init__(self, i) m.Base1.__init__(self, i)
Base2.__init__(self, j) m.Base2.__init__(self, j)
class MI6(Base2, B1): class MI6(m.Base2, B1):
def __init__(self, i): def __init__(self, i):
Base2.__init__(self, i) m.Base2.__init__(self, i)
B1.__init__(self) B1.__init__(self)
class B2(B1): class B2(B1):
@ -160,47 +155,45 @@ def test_multiple_inheritance_python():
def test_multiple_inheritance_python_many_bases(): def test_multiple_inheritance_python_many_bases():
from pybind11_tests import (BaseN1, BaseN2, BaseN3, BaseN4, BaseN5, BaseN6, BaseN7,
BaseN8, BaseN9, BaseN10, BaseN11, BaseN12, BaseN13, BaseN14,
BaseN15, BaseN16, BaseN17)
class MIMany14(BaseN1, BaseN2, BaseN3, BaseN4): class MIMany14(m.BaseN1, m.BaseN2, m.BaseN3, m.BaseN4):
def __init__(self): def __init__(self):
BaseN1.__init__(self, 1) m.BaseN1.__init__(self, 1)
BaseN2.__init__(self, 2) m.BaseN2.__init__(self, 2)
BaseN3.__init__(self, 3) m.BaseN3.__init__(self, 3)
BaseN4.__init__(self, 4) m.BaseN4.__init__(self, 4)
class MIMany58(BaseN5, BaseN6, BaseN7, BaseN8): class MIMany58(m.BaseN5, m.BaseN6, m.BaseN7, m.BaseN8):
def __init__(self): def __init__(self):
BaseN5.__init__(self, 5) m.BaseN5.__init__(self, 5)
BaseN6.__init__(self, 6) m.BaseN6.__init__(self, 6)
BaseN7.__init__(self, 7) m.BaseN7.__init__(self, 7)
BaseN8.__init__(self, 8) m.BaseN8.__init__(self, 8)
class MIMany916(BaseN9, BaseN10, BaseN11, BaseN12, BaseN13, BaseN14, BaseN15, BaseN16): class MIMany916(m.BaseN9, m.BaseN10, m.BaseN11, m.BaseN12, m.BaseN13, m.BaseN14, m.BaseN15,
m.BaseN16):
def __init__(self): def __init__(self):
BaseN9.__init__(self, 9) m.BaseN9.__init__(self, 9)
BaseN10.__init__(self, 10) m.BaseN10.__init__(self, 10)
BaseN11.__init__(self, 11) m.BaseN11.__init__(self, 11)
BaseN12.__init__(self, 12) m.BaseN12.__init__(self, 12)
BaseN13.__init__(self, 13) m.BaseN13.__init__(self, 13)
BaseN14.__init__(self, 14) m.BaseN14.__init__(self, 14)
BaseN15.__init__(self, 15) m.BaseN15.__init__(self, 15)
BaseN16.__init__(self, 16) m.BaseN16.__init__(self, 16)
class MIMany19(MIMany14, MIMany58, BaseN9): class MIMany19(MIMany14, MIMany58, m.BaseN9):
def __init__(self): def __init__(self):
MIMany14.__init__(self) MIMany14.__init__(self)
MIMany58.__init__(self) MIMany58.__init__(self)
BaseN9.__init__(self, 9) m.BaseN9.__init__(self, 9)
class MIMany117(MIMany14, MIMany58, MIMany916, BaseN17): class MIMany117(MIMany14, MIMany58, MIMany916, m.BaseN17):
def __init__(self): def __init__(self):
MIMany14.__init__(self) MIMany14.__init__(self)
MIMany58.__init__(self) MIMany58.__init__(self)
MIMany916.__init__(self) MIMany916.__init__(self)
BaseN17.__init__(self, 17) m.BaseN17.__init__(self, 17)
# Inherits from 4 registered C++ classes: can fit in one pointer on any modern arch: # Inherits from 4 registered C++ classes: can fit in one pointer on any modern arch:
a = MIMany14() a = MIMany14()
@ -224,31 +217,29 @@ def test_multiple_inheritance_python_many_bases():
def test_multiple_inheritance_virtbase(): def test_multiple_inheritance_virtbase():
from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr
class MITypePy(Base12a): class MITypePy(m.Base12a):
def __init__(self, i, j): def __init__(self, i, j):
Base12a.__init__(self, i, j) m.Base12a.__init__(self, i, j)
mt = MITypePy(3, 4) mt = MITypePy(3, 4)
assert mt.bar() == 4 assert mt.bar() == 4
assert bar_base2a(mt) == 4 assert m.bar_base2a(mt) == 4
assert bar_base2a_sharedptr(mt) == 4 assert m.bar_base2a_sharedptr(mt) == 4
def test_mi_static_properties(): def test_mi_static_properties():
"""Mixing bases with and without static properties should be possible """Mixing bases with and without static properties should be possible
and the result should be independent of base definition order""" and the result should be independent of base definition order"""
from pybind11_tests import mi
for d in (mi.VanillaStaticMix1(), mi.VanillaStaticMix2()): for d in (m.VanillaStaticMix1(), m.VanillaStaticMix2()):
assert d.vanilla() == "Vanilla" assert d.vanilla() == "Vanilla"
assert d.static_func1() == "WithStatic1" assert d.static_func1() == "WithStatic1"
assert d.static_func2() == "WithStatic2" assert d.static_func2() == "WithStatic2"
assert d.static_func() == d.__class__.__name__ assert d.static_func() == d.__class__.__name__
mi.WithStatic1.static_value1 = 1 m.WithStatic1.static_value1 = 1
mi.WithStatic2.static_value2 = 2 m.WithStatic2.static_value2 = 2
assert d.static_value1 == 1 assert d.static_value1 == 1
assert d.static_value2 == 2 assert d.static_value2 == 2
assert d.static_value == 12 assert d.static_value == 12
@ -264,30 +255,28 @@ def test_mi_static_properties():
@pytest.unsupported_on_pypy @pytest.unsupported_on_pypy
def test_mi_dynamic_attributes(): def test_mi_dynamic_attributes():
"""Mixing bases with and without dynamic attribute support""" """Mixing bases with and without dynamic attribute support"""
from pybind11_tests import mi
for d in (mi.VanillaDictMix1(), mi.VanillaDictMix2()): for d in (m.VanillaDictMix1(), m.VanillaDictMix2()):
d.dynamic = 1 d.dynamic = 1
assert d.dynamic == 1 assert d.dynamic == 1
def test_mi_unaligned_base(): def test_mi_unaligned_base():
"""Returning an offset (non-first MI) base class pointer should recognize the instance""" """Returning an offset (non-first MI) base class pointer should recognize the instance"""
from pybind11_tests import I801C, I801D, i801b1_c, i801b2_c, i801b1_d, i801b2_d
n_inst = ConstructorStats.detail_reg_inst() n_inst = ConstructorStats.detail_reg_inst()
c = I801C() c = m.I801C()
d = I801D() d = m.I801D()
# + 4 below because we have the two instances, and each instance has offset base I801B2 # + 4 below because we have the two instances, and each instance has offset base I801B2
assert ConstructorStats.detail_reg_inst() == n_inst + 4 assert ConstructorStats.detail_reg_inst() == n_inst + 4
b1c = i801b1_c(c) b1c = m.i801b1_c(c)
assert b1c is c assert b1c is c
b2c = i801b2_c(c) b2c = m.i801b2_c(c)
assert b2c is c assert b2c is c
b1d = i801b1_d(d) b1d = m.i801b1_d(d)
assert b1d is d assert b1d is d
b2d = i801b2_d(d) b2d = m.i801b2_d(d)
assert b2d is d assert b2d is d
assert ConstructorStats.detail_reg_inst() == n_inst + 4 # no extra instances assert ConstructorStats.detail_reg_inst() == n_inst + 4 # no extra instances
@ -299,30 +288,28 @@ def test_mi_unaligned_base():
def test_mi_base_return(): def test_mi_base_return():
"""Tests returning an offset (non-first MI) base class pointer to a derived instance""" """Tests returning an offset (non-first MI) base class pointer to a derived instance"""
from pybind11_tests import (I801B2, I801C, I801D, i801c_b1, i801c_b2, i801d_b1, i801d_b2,
i801e_c, i801e_b2)
n_inst = ConstructorStats.detail_reg_inst() n_inst = ConstructorStats.detail_reg_inst()
c1 = i801c_b1() c1 = m.i801c_b1()
assert type(c1) is I801C assert type(c1) is m.I801C
assert c1.a == 1 assert c1.a == 1
assert c1.b == 2 assert c1.b == 2
d1 = i801d_b1() d1 = m.i801d_b1()
assert type(d1) is I801D assert type(d1) is m.I801D
assert d1.a == 1 assert d1.a == 1
assert d1.b == 2 assert d1.b == 2
assert ConstructorStats.detail_reg_inst() == n_inst + 4 assert ConstructorStats.detail_reg_inst() == n_inst + 4
c2 = i801c_b2() c2 = m.i801c_b2()
assert type(c2) is I801C assert type(c2) is m.I801C
assert c2.a == 1 assert c2.a == 1
assert c2.b == 2 assert c2.b == 2
d2 = i801d_b2() d2 = m.i801d_b2()
assert type(d2) is I801D assert type(d2) is m.I801D
assert d2.a == 1 assert d2.a == 1
assert d2.b == 2 assert d2.b == 2
@ -336,11 +323,11 @@ def test_mi_base_return():
# Returning an unregistered derived type with a registered base; we won't # Returning an unregistered derived type with a registered base; we won't
# pick up the derived type, obviously, but should still work (as an object # pick up the derived type, obviously, but should still work (as an object
# of whatever type was returned). # of whatever type was returned).
e1 = i801e_c() e1 = m.i801e_c()
assert type(e1) is I801C assert type(e1) is m.I801C
assert e1.a == 1 assert e1.a == 1
assert e1.b == 2 assert e1.b == 2
e2 = i801e_b2() e2 = m.i801e_b2()
assert type(e2) is I801B2 assert type(e2) is m.I801B2
assert e2.b == 2 assert e2.b == 2