From 47300d1111c3e8e10b467a9f6acc73b9d1c2986a Mon Sep 17 00:00:00 2001 From: Martin-Molinero Date: Tue, 17 May 2022 16:33:55 -0300 Subject: [PATCH 1/3] Keep calling base managed constructor --- src/embed_tests/QCTest.cs | 145 ++++++++++++++++++++++++++++++- src/runtime/Types/ClassObject.cs | 39 ++++++++- 2 files changed, 178 insertions(+), 6 deletions(-) diff --git a/src/embed_tests/QCTest.cs b/src/embed_tests/QCTest.cs index 789834735..5f50fd601 100644 --- a/src/embed_tests/QCTest.cs +++ b/src/embed_tests/QCTest.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using NUnit.Framework; using Python.Runtime; @@ -9,13 +7,22 @@ namespace Python.EmbeddingTest { class QCTests { + private static dynamic pythonSuperInitInt; + private static dynamic pythonSuperInitDefault; + private static dynamic pythonSuperInitNone; + private static dynamic pythonSuperInitNotCallingBase; + + private static dynamic withArgs_PythonSuperInitNotCallingBase; + private static dynamic withArgs_PythonSuperInitDefault; + private static dynamic withArgs_PythonSuperInitInt; + private static dynamic containsTest; private static dynamic module; private static string testModule = @" from clr import AddReference AddReference(""System"") AddReference(""Python.EmbeddingTest"") -from Python.EmbeddingTest import Algo, Insight +from Python.EmbeddingTest import * class PythonModule(Algo): def TestA(self): try: @@ -28,6 +35,34 @@ def ContainsTest(key, collection): if key in collection.Keys: return True return False + +class WithArgs_PythonSuperInitNotCallingBase(SuperInit): + def __init__(self, jose): + return + +class WithArgs_PythonSuperInitDefault(SuperInit): + def __init__(self, jose): + super().__init__() + +class WithArgs_PythonSuperInitInt(SuperInit): + def __init__(self, jose): + super().__init__(jose) + +class PythonSuperInitNotCallingBase(SuperInit): + def __init__(self): + return + +class PythonSuperInitDefault(SuperInit): + def __init__(self): + super().__init__() + +class PythonSuperInitInt(SuperInit): + def __init__(self): + super().__init__(1) + +class PythonSuperInitNone(SuperInit): + def jose(self): + return 1 "; [OneTimeSetUp] @@ -37,6 +72,15 @@ public void Setup() var pyModule = PyModule.FromString("module", testModule); containsTest = pyModule.GetAttr("ContainsTest"); module = pyModule.GetAttr("PythonModule").Invoke(); + + pythonSuperInitInt = pyModule.GetAttr("PythonSuperInitInt"); + pythonSuperInitDefault = pyModule.GetAttr("PythonSuperInitDefault"); + pythonSuperInitNone = pyModule.GetAttr("PythonSuperInitNone"); + pythonSuperInitNotCallingBase = pyModule.GetAttr("PythonSuperInitNotCallingBase"); + + withArgs_PythonSuperInitNotCallingBase = pyModule.GetAttr("WithArgs_PythonSuperInitNotCallingBase"); + withArgs_PythonSuperInitDefault = pyModule.GetAttr("WithArgs_PythonSuperInitDefault"); + withArgs_PythonSuperInitInt = pyModule.GetAttr("WithArgs_PythonSuperInitInt"); } [OneTimeTearDown] @@ -62,6 +106,87 @@ public void ContainsTest(string key, bool expected) var dic = new Dictionary { { "SPY", new object() } }; Assert.AreEqual(expected, (bool)containsTest(key, dic)); } + + [Test] + public void WithArgs_NoBaseConstructorCall() + { + using (Py.GIL()) + { + var instance = withArgs_PythonSuperInitNotCallingBase(1); + // this is true because we call the constructor always + Assert.IsTrue((bool)instance.CalledInt); + Assert.IsFalse((bool)instance.CalledDefault); + } + } + + [Test] + public void WithArgs_IntConstructor() + { + using (Py.GIL()) + { + var instance = withArgs_PythonSuperInitInt(1); + Assert.IsTrue((bool)instance.CalledInt); + Assert.IsFalse((bool)instance.CalledDefault); + } + } + + [Test] + public void WithArgs_DefaultConstructor() + { + using (Py.GIL()) + { + var instance = withArgs_PythonSuperInitDefault(1); + Assert.IsTrue((bool)instance.CalledInt); + Assert.IsTrue((bool)instance.CalledDefault); + } + } + + [Test] + public void NoArgs_NoBaseConstructorCall() + { + using (Py.GIL()) + { + var instance = pythonSuperInitNotCallingBase(); + Assert.IsFalse((bool)instance.CalledInt); + // this is true because we call the default constructor always + Assert.IsTrue((bool)instance.CalledDefault); + } + } + + [Test] + public void NoArgs_IntConstructor() + { + using (Py.GIL()) + { + var instance = pythonSuperInitInt(); + Assert.IsTrue((bool)instance.CalledInt); + // this is true because we call the default constructor always + Assert.IsTrue((bool)instance.CalledDefault); + } + } + + [Test] + public void NoArgs_DefaultConstructor() + { + using (Py.GIL()) + { + var instance = pythonSuperInitNone(); + Assert.IsFalse((bool)instance.CalledInt); + Assert.IsTrue((bool)instance.CalledDefault); + } + } + + [Test] + public void NoArgs_NoConstructor() + { + using (Py.GIL()) + { + var instance = pythonSuperInitDefault.Invoke(); + + Assert.IsFalse((bool)instance.CalledInt); + Assert.IsTrue((bool)instance.CalledDefault); + } + } } public class Algo @@ -83,6 +208,20 @@ public void EmitInsights(params Insight[] insights) } + public class SuperInit + { + public bool CalledInt { get; private set; } + public bool CalledDefault { get; private set; } + public SuperInit(int a) + { + CalledInt = true; + } + public SuperInit() + { + CalledDefault = true; + } + } + public class Insight { public string info; diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 5ba83c25e..721cd08af 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -15,12 +15,13 @@ namespace Python.Runtime [Serializable] internal class ClassObject : ClassBase { + private ConstructorInfo[] constructors; internal readonly int NumCtors = 0; internal ClassObject(Type tp) : base(tp) { - var _ctors = type.Value.GetConstructors(); - NumCtors = _ctors.Length; + constructors = type.Value.GetConstructors(); + NumCtors = constructors.Length; } @@ -110,8 +111,40 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo } object obj = FormatterServices.GetUninitializedObject(type); + var pythonObj = self.NewObjectToPython(obj, tp); - return self.NewObjectToPython(obj, tp); + try + { + var binder = new MethodBinder(); + for (int i = 0; i < self.constructors.Length; i++) + { + binder.AddMethod(self.constructors[i]); + } + + // let's try to generate a binding using the args/kw we have + var binding = binder.Bind(pythonObj.Borrow(), args, kw); + if (binding != null) + { + binding.info.Invoke(obj, BindingFlags.Default, null, binding.args, null); + } + else + { + // if we didn't match any constructor let's fall back into the default constructor, no args + using var tuple = Runtime.PyTuple_New(0); + binding = binder.Bind(pythonObj.Borrow(), tuple.Borrow(), null); + if(binding != null) + { + binding.info.Invoke(obj, BindingFlags.Default, null, binding.args, null); + } + } + } + catch (Exception) + { + Exceptions.Clear(); + // we try our best to call the base constructor but don't let it stop us + } + + return pythonObj; } protected virtual void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder) From ba8ad8862ad40b49500001a58910e8ae99100414 Mon Sep 17 00:00:00 2001 From: Martin-Molinero Date: Tue, 17 May 2022 19:22:46 -0300 Subject: [PATCH 2/3] Add more tests. Refactor solution --- src/embed_tests/QCTest.cs | 56 +++++++++++++++++++++----------- src/runtime/Types/ClassObject.cs | 14 ++------ 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/embed_tests/QCTest.cs b/src/embed_tests/QCTest.cs index 5f50fd601..5fd2afd29 100644 --- a/src/embed_tests/QCTest.cs +++ b/src/embed_tests/QCTest.cs @@ -16,6 +16,8 @@ class QCTests private static dynamic withArgs_PythonSuperInitDefault; private static dynamic withArgs_PythonSuperInitInt; + private static dynamic pureCSharpConstruction; + private static dynamic containsTest; private static dynamic module; private static string testModule = @" @@ -63,6 +65,9 @@ def __init__(self): class PythonSuperInitNone(SuperInit): def jose(self): return 1 + +def PureCSharpConstruction(): + return SuperInit(1) "; [OneTimeSetUp] @@ -81,6 +86,8 @@ public void Setup() withArgs_PythonSuperInitNotCallingBase = pyModule.GetAttr("WithArgs_PythonSuperInitNotCallingBase"); withArgs_PythonSuperInitDefault = pyModule.GetAttr("WithArgs_PythonSuperInitDefault"); withArgs_PythonSuperInitInt = pyModule.GetAttr("WithArgs_PythonSuperInitInt"); + + pureCSharpConstruction = pyModule.GetAttr("PureCSharpConstruction"); } [OneTimeTearDown] @@ -107,15 +114,26 @@ public void ContainsTest(string key, bool expected) Assert.AreEqual(expected, (bool)containsTest(key, dic)); } + [Test] + public void PureCSharpConstruction() + { + using (Py.GIL()) + { + var instance = pureCSharpConstruction(); + Assert.AreEqual(1, (int)instance.CalledInt); + Assert.AreEqual(1, (int)instance.CalledDefault); + } + } + [Test] public void WithArgs_NoBaseConstructorCall() { using (Py.GIL()) { var instance = withArgs_PythonSuperInitNotCallingBase(1); - // this is true because we call the constructor always - Assert.IsTrue((bool)instance.CalledInt); - Assert.IsFalse((bool)instance.CalledDefault); + Assert.AreEqual(0, (int)instance.CalledInt); + // we call the constructor always + Assert.AreEqual(1, (int)instance.CalledDefault); } } @@ -125,8 +143,8 @@ public void WithArgs_IntConstructor() using (Py.GIL()) { var instance = withArgs_PythonSuperInitInt(1); - Assert.IsTrue((bool)instance.CalledInt); - Assert.IsFalse((bool)instance.CalledDefault); + Assert.AreEqual(1, (int)instance.CalledInt); + Assert.AreEqual(1, (int)instance.CalledDefault); } } @@ -136,8 +154,8 @@ public void WithArgs_DefaultConstructor() using (Py.GIL()) { var instance = withArgs_PythonSuperInitDefault(1); - Assert.IsTrue((bool)instance.CalledInt); - Assert.IsTrue((bool)instance.CalledDefault); + Assert.AreEqual(0, (int)instance.CalledInt); + Assert.AreEqual(2, (int)instance.CalledDefault); } } @@ -147,9 +165,9 @@ public void NoArgs_NoBaseConstructorCall() using (Py.GIL()) { var instance = pythonSuperInitNotCallingBase(); - Assert.IsFalse((bool)instance.CalledInt); + Assert.AreEqual(0, (int)instance.CalledInt); // this is true because we call the default constructor always - Assert.IsTrue((bool)instance.CalledDefault); + Assert.AreEqual(1, (int)instance.CalledDefault); } } @@ -159,9 +177,9 @@ public void NoArgs_IntConstructor() using (Py.GIL()) { var instance = pythonSuperInitInt(); - Assert.IsTrue((bool)instance.CalledInt); + Assert.AreEqual(1, (int)instance.CalledInt); // this is true because we call the default constructor always - Assert.IsTrue((bool)instance.CalledDefault); + Assert.AreEqual(1, (int)instance.CalledDefault); } } @@ -171,8 +189,8 @@ public void NoArgs_DefaultConstructor() using (Py.GIL()) { var instance = pythonSuperInitNone(); - Assert.IsFalse((bool)instance.CalledInt); - Assert.IsTrue((bool)instance.CalledDefault); + Assert.AreEqual(0, (int)instance.CalledInt); + Assert.AreEqual(2, (int)instance.CalledDefault); } } @@ -183,8 +201,8 @@ public void NoArgs_NoConstructor() { var instance = pythonSuperInitDefault.Invoke(); - Assert.IsFalse((bool)instance.CalledInt); - Assert.IsTrue((bool)instance.CalledDefault); + Assert.AreEqual(0, (int)instance.CalledInt); + Assert.AreEqual(2, (int)instance.CalledDefault); } } } @@ -210,15 +228,15 @@ public void EmitInsights(params Insight[] insights) public class SuperInit { - public bool CalledInt { get; private set; } - public bool CalledDefault { get; private set; } + public int CalledInt { get; private set; } + public int CalledDefault { get; private set; } public SuperInit(int a) { - CalledInt = true; + CalledInt++; } public SuperInit() { - CalledDefault = true; + CalledDefault++; } } diff --git a/src/runtime/Types/ClassObject.cs b/src/runtime/Types/ClassObject.cs index 721cd08af..28abd3cd9 100644 --- a/src/runtime/Types/ClassObject.cs +++ b/src/runtime/Types/ClassObject.cs @@ -121,22 +121,12 @@ static NewReference tp_new_impl(BorrowedReference tp, BorrowedReference args, Bo binder.AddMethod(self.constructors[i]); } - // let's try to generate a binding using the args/kw we have - var binding = binder.Bind(pythonObj.Borrow(), args, kw); + using var tuple = Runtime.PyTuple_New(0); + var binding = binder.Bind(pythonObj.Borrow(), tuple.Borrow(), null); if (binding != null) { binding.info.Invoke(obj, BindingFlags.Default, null, binding.args, null); } - else - { - // if we didn't match any constructor let's fall back into the default constructor, no args - using var tuple = Runtime.PyTuple_New(0); - binding = binder.Bind(pythonObj.Borrow(), tuple.Borrow(), null); - if(binding != null) - { - binding.info.Invoke(obj, BindingFlags.Default, null, binding.args, null); - } - } } catch (Exception) { From 6edaf091734eea61c4b1af82ec84ecacad7e2628 Mon Sep 17 00:00:00 2001 From: Martin-Molinero Date: Tue, 17 May 2022 19:33:32 -0300 Subject: [PATCH 3/3] Bump to version 2.0.15 --- src/perf_tests/Python.PerformanceTests.csproj | 4 ++-- src/runtime/Properties/AssemblyInfo.cs | 4 ++-- src/runtime/Python.Runtime.csproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index a7726f2d7..6e3ca4966 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + compile @@ -25,7 +25,7 @@ - + diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index ff96d4531..99a65c3d9 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -4,5 +4,5 @@ [assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] [assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] -[assembly: AssemblyVersion("2.0.14")] -[assembly: AssemblyFileVersion("2.0.14")] +[assembly: AssemblyVersion("2.0.15")] +[assembly: AssemblyFileVersion("2.0.15")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 24d007d75..e2b0d8beb 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -5,7 +5,7 @@ Python.Runtime Python.Runtime QuantConnect.pythonnet - 2.0.14 + 2.0.15 false LICENSE https://github.com/pythonnet/pythonnet