[Python.NET] Why are default constructors called on exceptions?

Alexey Borzenkov snaury at gmail.com
Wed Dec 16 22:39:29 CET 2009


On Tue, Dec 15, 2009 at 6:02 AM, Chris Gagnon <cgagnon at zindagigames.com> wrote:
>> namespace MyLib
>> {
>>     public class CXDoc
>>     {
>>         // Construction
>>         public CXDoc(string sPath)
>>         {
>>             XmlDocument oDoc = new XmlDocument();
>>             oDoc.Load(sPath);
>>         }
>>         public CXDoc()
>>         {
>>             throw new Exception("Deprecated constructor.");
>>         }
>>     }
>> }
>
>  Now this script
>
>>>> testI2 = MyLib.CXDoc("path that dosn't exist")
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> System.Exception: Deprecated constructor.
>    at MyLib.CXDoc..ctor()

I think what happens here is a mistake in classobject.cs tp_new. If
some exception happens during invoking of constructor, it just assumes
that binding failed and tries to call constructor without argument in
hopes that subclass's __init__ will initialize it. Here's some
preliminary patch, though I didn't test it at all. You may try it and
tell me if it helps. This patch ensures that we call "default"
constructor only if we failed to bind. It won't happen if bound
constructor throws some exception.

diff --git a/src/runtime/classobject.cs b/src/runtime/classobject.cs
index 4d70918..4a90c44 100644
--- a/src/runtime/classobject.cs
+++ b/src/runtime/classobject.cs
@@ -106,19 +106,7 @@ namespace Python.Runtime {

             Object obj = self.binder.InvokeRaw(IntPtr.Zero, args, kw);
             if (obj == null) {
-                // It is possible for __new__ to be invoked on construction
-                // of a Python subclass of a managed class, so args may
-                // reflect more args than are required to instantiate the
-                // class. So if we cant find a ctor that matches, we'll see
-                // if there is a default constructor and, if so, assume that
-                // any extra args are intended for the subclass' __init__.
-
-                IntPtr eargs = Runtime.PyTuple_New(0);
-                obj = self.binder.InvokeRaw(IntPtr.Zero, eargs, kw);
-                Runtime.Decref(eargs);
-                if (obj == null) {
-                    return IntPtr.Zero;
-                }
+                return IntPtr.Zero;
             }

             return CLRObject.GetInstHandle(obj, tp);
diff --git a/src/runtime/constructorbinder.cs b/src/runtime/constructorbinder.cs
index f7244c3..69d8c17 100644
--- a/src/runtime/constructorbinder.cs
+++ b/src/runtime/constructorbinder.cs
@@ -43,10 +43,23 @@ namespace Python.Runtime {
             Object result;

             if (binding == null) {
-                Exceptions.SetError(Exceptions.TypeError,
-                                    "no constructor matches given arguments"
-                                    );
-                return null;
+                // It is possible for __new__ to be invoked on construction
+                // of a Python subclass of a managed class, so args may
+                // reflect more args than are required to instantiate the
+                // class. So if we cant find a ctor that matches, we'll see
+                // if there is a default constructor and, if so, assume that
+                // any extra args are intended for the subclass' __init__.
+
+                IntPtr eargs = Runtime.PyTuple_New(0);
+                binding = this.Bind(inst, eargs, kw);
+                Runtime.Decref(eargs);
+
+                if (binding == null) {
+                    Exceptions.SetError(Exceptions.TypeError,
+                                        "no constructor matches given
arguments"
+                                        );
+                    return null;
+                }
             }

             // Object construction is presumed to be non-blocking and fast


More information about the PythonDotNet mailing list