diff --git a/CHANGELOG.md b/CHANGELOG.md index 38fa56a62..5ae62d692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. attribute (#481) - Fixed conversion of 'float' and 'double' values (#486) - Fixed 'clrmethod' for python 2 (#492) +- Fixed double calling of constructor when deriving from .NET class (#495) ## [2.3.0][] - 2017-03-11 diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index c180f9acc..6a10a5add 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -55,6 +55,33 @@ internal ClassDerivedObject(Type tp) : base(tp) return Converter.ToPython(obj, cls.GetType()); } + public new static int tp_init(IntPtr obj, IntPtr args, IntPtr kw) + { + Runtime.XIncref(obj); + Runtime.XIncref(Runtime.PyNone); + using (var pyself = new PyObject(obj)) + using (var pynone = new PyObject(Runtime.PyNone)) + using (var init = pyself.GetAttr("__init__", pynone)) + { + if (init.Handle != Runtime.PyNone) + { + // if __init__ hasn't been overridden then it will be a managed object + if (GetManagedObject(init.Handle) == null) + { + Runtime.XIncref(args); + Runtime.XIncref(kw); + using (var args_ = new PyTuple(args)) + using (var kw_ = new PyDict(kw)) + using (var res = init.Invoke(args_, kw_)) + { + } + } + } + } + + return 0; + } + public new static void tp_dealloc(IntPtr ob) { var self = (CLRObject)GetManagedObject(ob); @@ -822,33 +849,7 @@ public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, objec FieldInfo fi = obj.GetType().GetField("__pyobj__"); fi.SetValue(obj, self); - Runtime.XIncref(self.pyHandle); - var pyself = new PyObject(self.pyHandle); - disposeList.Add(pyself); - - Runtime.XIncref(Runtime.PyNone); - var pynone = new PyObject(Runtime.PyNone); - disposeList.Add(pynone); - - // call __init__ - PyObject init = pyself.GetAttr("__init__", pynone); - disposeList.Add(init); - if (init.Handle != Runtime.PyNone) - { - // if __init__ hasn't been overridden then it will be a managed object - ManagedType managedMethod = ManagedType.GetManagedObject(init.Handle); - if (null == managedMethod) - { - var pyargs = new PyObject[args.Length]; - for (var i = 0; i < args.Length; ++i) - { - pyargs[i] = new PyObject(Converter.ToPython(args[i], args[i]?.GetType())); - disposeList.Add(pyargs[i]); - } - disposeList.Add(init.Invoke(pyargs)); - } - } } finally { diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index bfb71e26d..bd0d8c3e8 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; namespace Python.Runtime @@ -125,19 +125,6 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) } - public static IntPtr tp_alloc(IntPtr mt, int n) - { - IntPtr type = Runtime.PyType_GenericAlloc(mt, n); - return type; - } - - - public static void tp_free(IntPtr tp) - { - Runtime.PyObject_GC_Del(tp); - } - - /// /// Metatype __call__ implementation. This is needed to ensure correct /// initialization (__init__ support), because the tp_call we inherit @@ -188,6 +175,19 @@ public static IntPtr tp_call(IntPtr tp, IntPtr args, IntPtr kw) } + + public static IntPtr tp_alloc(IntPtr mt, int n) + { + IntPtr type = Runtime.PyType_GenericAlloc(mt, n); + return type; + } + + + public static void tp_free(IntPtr tp) + { + Runtime.PyObject_GC_Del(tp); + } + /// /// Type __setattr__ implementation for reflected types. Note that this /// is slightly different than the standard setattr implementation for diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py index 739c24c07..40b8a65f5 100644 --- a/src/tests/test_subclass.py +++ b/src/tests/test_subclass.py @@ -190,3 +190,17 @@ def test_isinstance_check(): for x in b: assert isinstance(x, System.Object) assert isinstance(x, System.String) + + +def test_derived_constructor_only_called_once(): + calls = [] + + class X(System.Object): + __namespace__ = "PyTest" + + def __init__(self, *args): + calls.append(args) + + x = X(1, 2) + + assert calls == [(1, 2)]