Skip to content

Commit a5f5dc2

Browse files
rickardraysearchfilmor
authored andcommitted
Another shot at #495 (#503)
* Add tests of subclassing with __namespace__ * Remove __init__ call from ClassDerived.InvokeCtor * Trying out __init__ * Cleanup * Add tests constructing python type from CLR and calling __init__ * Revert borked changelog update * Don't leak init reference * Rename tests * Remove unused internal Runtime.GetBoundArgTuple * Reenable skipped tests in test_subclass.py
1 parent c8ae358 commit a5f5dc2

File tree

5 files changed

+74
-86
lines changed

5 files changed

+74
-86
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2222
attribute (#481)
2323
- Fixed conversion of 'float' and 'double' values (#486)
2424
- Fixed 'clrmethod' for python 2 (#492)
25+
- Fixed double calling of constructor when deriving from .NET class (#495)
2526

2627

2728
## [2.3.0][] - 2017-03-11

src/runtime/classderived.cs

+1-35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
@@ -808,7 +808,6 @@ public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, objec
808808
obj,
809809
args);
810810

811-
var disposeList = new List<PyObject>();
812811
CLRObject self = null;
813812
IntPtr gs = Runtime.PyGILState_Ensure();
814813
try
@@ -821,42 +820,9 @@ public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, objec
821820
// object to be collected.
822821
FieldInfo fi = obj.GetType().GetField("__pyobj__");
823822
fi.SetValue(obj, self);
824-
825-
Runtime.XIncref(self.pyHandle);
826-
var pyself = new PyObject(self.pyHandle);
827-
disposeList.Add(pyself);
828-
829-
Runtime.XIncref(Runtime.PyNone);
830-
var pynone = new PyObject(Runtime.PyNone);
831-
disposeList.Add(pynone);
832-
833-
// call __init__
834-
PyObject init = pyself.GetAttr("__init__", pynone);
835-
disposeList.Add(init);
836-
if (init.Handle != Runtime.PyNone)
837-
{
838-
// if __init__ hasn't been overridden then it will be a managed object
839-
ManagedType managedMethod = ManagedType.GetManagedObject(init.Handle);
840-
if (null == managedMethod)
841-
{
842-
var pyargs = new PyObject[args.Length];
843-
for (var i = 0; i < args.Length; ++i)
844-
{
845-
pyargs[i] = new PyObject(Converter.ToPython(args[i], args[i]?.GetType()));
846-
disposeList.Add(pyargs[i]);
847-
}
848-
849-
disposeList.Add(init.Invoke(pyargs));
850-
}
851-
}
852823
}
853824
finally
854825
{
855-
foreach (PyObject x in disposeList)
856-
{
857-
x?.Dispose();
858-
}
859-
860826
// Decrement the python object's reference count.
861827
// This doesn't actually destroy the object, it just sets the reference to this object
862828
// to be a weak reference and it will be destroyed when the C# object is destroyed.

src/runtime/metatype.cs

+4-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Runtime.InteropServices;
33

44
namespace Python.Runtime
@@ -157,23 +157,13 @@ public static IntPtr tp_call(IntPtr tp, IntPtr args, IntPtr kw)
157157
return IntPtr.Zero;
158158
}
159159

160-
IntPtr py__init__ = Runtime.PyString_FromString("__init__");
161-
IntPtr type = Runtime.PyObject_TYPE(obj);
162-
IntPtr init = Runtime._PyType_Lookup(type, py__init__);
163-
Runtime.XDecref(py__init__);
160+
var init = Runtime.PyObject_GetAttrString(obj, "__init__");
164161
Runtime.PyErr_Clear();
165162

166163
if (init != IntPtr.Zero)
167164
{
168-
IntPtr bound = Runtime.GetBoundArgTuple(obj, args);
169-
if (bound == IntPtr.Zero)
170-
{
171-
Runtime.XDecref(obj);
172-
return IntPtr.Zero;
173-
}
174-
175-
IntPtr result = Runtime.PyObject_Call(init, bound, kw);
176-
Runtime.XDecref(bound);
165+
IntPtr result = Runtime.PyObject_Call(init, args, kw);
166+
Runtime.XDecref(init);
177167

178168
if (result == IntPtr.Zero)
179169
{

src/runtime/runtime.cs

-23
Original file line numberDiff line numberDiff line change
@@ -378,29 +378,6 @@ internal static void CheckExceptionOccurred()
378378
}
379379
}
380380

381-
internal static IntPtr GetBoundArgTuple(IntPtr obj, IntPtr args)
382-
{
383-
if (PyObject_TYPE(args) != PyTupleType)
384-
{
385-
Exceptions.SetError(Exceptions.TypeError, "tuple expected");
386-
return IntPtr.Zero;
387-
}
388-
int size = PyTuple_Size(args);
389-
IntPtr items = PyTuple_New(size + 1);
390-
PyTuple_SetItem(items, 0, obj);
391-
XIncref(obj);
392-
393-
for (var i = 0; i < size; i++)
394-
{
395-
IntPtr item = PyTuple_GetItem(args, i);
396-
XIncref(item);
397-
PyTuple_SetItem(items, i + 1, item);
398-
}
399-
400-
return items;
401-
}
402-
403-
404381
internal static IntPtr ExtendTuple(IntPtr t, params IntPtr[] args)
405382
{
406383
int size = PyTuple_Size(t);

src/tests/test_subclass.py

+68-14
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
from ._compat import range
1616

1717

18-
def interface_test_class_fixture():
18+
def interface_test_class_fixture(subnamespace):
1919
"""Delay creation of class until test starts."""
2020

2121
class InterfaceTestClass(IInterfaceTest):
2222
"""class that implements the test interface"""
23-
__namespace__ = "Python.Test"
23+
__namespace__ = "Python.Test." + subnamespace
2424

2525
def foo(self):
2626
return "InterfaceTestClass"
@@ -31,12 +31,12 @@ def bar(self, x, i):
3131
return InterfaceTestClass
3232

3333

34-
def derived_class_fixture():
34+
def derived_class_fixture(subnamespace):
3535
"""Delay creation of class until test starts."""
3636

3737
class DerivedClass(SubClassTest):
3838
"""class that derives from a class deriving from IInterfaceTest"""
39-
__namespace__ = "Python.Test"
39+
__namespace__ = "Python.Test." + subnamespace
4040

4141
def foo(self):
4242
return "DerivedClass"
@@ -60,12 +60,12 @@ def return_list(self):
6060
return DerivedClass
6161

6262

63-
def derived_event_test_class_fixture():
63+
def derived_event_test_class_fixture(subnamespace):
6464
"""Delay creation of class until test starts."""
6565

6666
class DerivedEventTest(IInterfaceTest):
6767
"""class that implements IInterfaceTest.TestEvent"""
68-
__namespace__ = "Python.Test"
68+
__namespace__ = "Python.Test." + subnamespace
6969

7070
def __init__(self):
7171
self.event_handlers = []
@@ -99,7 +99,7 @@ def test_base_class():
9999

100100
def test_interface():
101101
"""Test python classes can derive from C# interfaces"""
102-
InterfaceTestClass = interface_test_class_fixture()
102+
InterfaceTestClass = interface_test_class_fixture(test_interface.__name__)
103103
ob = InterfaceTestClass()
104104
assert ob.foo() == "InterfaceTestClass"
105105
assert FunctionsTest.test_foo(ob) == "InterfaceTestClass"
@@ -112,7 +112,7 @@ def test_interface():
112112

113113
def test_derived_class():
114114
"""Test python class derived from managed type"""
115-
DerivedClass = derived_class_fixture()
115+
DerivedClass = derived_class_fixture(test_derived_class.__name__)
116116
ob = DerivedClass()
117117
assert ob.foo() == "DerivedClass"
118118
assert ob.base_foo() == "foo"
@@ -128,10 +128,9 @@ def test_derived_class():
128128
assert id(x) == id(ob)
129129

130130

131-
@pytest.mark.skip(reason="FIXME: test randomly pass/fails")
132131
def test_create_instance():
133132
"""Test derived instances can be created from managed code"""
134-
DerivedClass = derived_class_fixture()
133+
DerivedClass = derived_class_fixture(test_create_instance.__name__)
135134
ob = FunctionsTest.create_instance(DerivedClass)
136135
assert ob.foo() == "DerivedClass"
137136
assert FunctionsTest.test_foo(ob) == "DerivedClass"
@@ -142,7 +141,7 @@ def test_create_instance():
142141
x = FunctionsTest.pass_through(ob)
143142
assert id(x) == id(ob)
144143

145-
InterfaceTestClass = interface_test_class_fixture()
144+
InterfaceTestClass = interface_test_class_fixture(test_create_instance.__name__)
146145
ob2 = FunctionsTest.create_instance(InterfaceTestClass)
147146
assert ob2.foo() == "InterfaceTestClass"
148147
assert FunctionsTest.test_foo(ob2) == "InterfaceTestClass"
@@ -153,7 +152,6 @@ def test_create_instance():
153152
assert id(y) == id(ob2)
154153

155154

156-
@pytest.mark.skip(reason="FIXME: test randomly pass/fails")
157155
def test_events():
158156
class EventHandler(object):
159157
def handler(self, x, args):
@@ -166,12 +164,12 @@ def handler(self, x, args):
166164
assert FunctionsTest.test_event(x, 1) == 1
167165
assert event_handler.value == 1
168166

169-
InterfaceTestClass = interface_test_class_fixture()
167+
InterfaceTestClass = interface_test_class_fixture(test_events.__name__)
170168
i = InterfaceTestClass()
171169
with pytest.raises(System.NotImplementedException):
172170
FunctionsTest.test_event(i, 2)
173171

174-
DerivedEventTest = derived_event_test_class_fixture()
172+
DerivedEventTest = derived_event_test_class_fixture(test_events.__name__)
175173
d = DerivedEventTest()
176174
d.add_TestEvent(event_handler.handler)
177175
assert FunctionsTest.test_event(d, 3) == 3
@@ -190,3 +188,59 @@ def test_isinstance_check():
190188
for x in b:
191189
assert isinstance(x, System.Object)
192190
assert isinstance(x, System.String)
191+
192+
def test_namespace_and_init():
193+
calls = []
194+
class TestX(System.Object):
195+
__namespace__ = "test_clr_subclass_with_init_args"
196+
def __init__(self, *args, **kwargs):
197+
calls.append((args, kwargs))
198+
t = TestX(1,2,3,foo="bar")
199+
assert len(calls) == 1
200+
assert calls[0][0] == (1,2,3)
201+
assert calls[0][1] == {"foo":"bar"}
202+
203+
def test_namespace_and_argless_init():
204+
calls = []
205+
class TestX(System.Object):
206+
__namespace__ = "test_clr_subclass_without_init_args"
207+
def __init__(self):
208+
calls.append(True)
209+
t = TestX()
210+
assert len(calls) == 1
211+
assert calls[0] == True
212+
213+
214+
def test_namespace_and_no_init():
215+
class TestX(System.Object):
216+
__namespace__ = "test_clr_subclass_without_init"
217+
q = 1
218+
t = TestX()
219+
assert t.q == 1
220+
221+
def test_construction_from_clr():
222+
import clr
223+
calls = []
224+
class TestX(System.Object):
225+
__namespace__ = "test_clr_subclass_init_from_clr"
226+
@clr.clrmethod(None, [int, str])
227+
def __init__(self, i, s):
228+
calls.append((i, s))
229+
230+
# Construct a TestX from Python
231+
t = TestX(1, "foo")
232+
assert len(calls) == 1
233+
assert calls[0][0] == 1
234+
assert calls[0][1] == "foo"
235+
236+
# Reset calls and construct a TestX from CLR
237+
calls = []
238+
tp = t.GetType()
239+
t2 = tp.GetConstructors()[0].Invoke(None)
240+
assert len(calls) == 0
241+
242+
# The object has only been constructed, now it needs to be initialized as well
243+
tp.GetMethod("__init__").Invoke(t2, [1, "foo"])
244+
assert len(calls) == 1
245+
assert calls[0][0] == 1
246+
assert calls[0][1] == "foo"

0 commit comments

Comments
 (0)