Discussion:
[Python.NET] Why are default constructors called on exceptions?
Chris Gagnon
2009-12-15 03:02:12 UTC
Permalink
Consider this C# library

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
import clr
clr.AddReference("System")
<System.Reflection.Assembly object at 0x0435AE50>
from System import *
clr.AddReference("System.Data")
<System.Reflection.Assembly object at 0x0435AE90>
from System.Data import *
clr.AddReference("System.Xml")
<System.Reflection.Assembly object at 0x044F9B10>
from System.Xml import *
clr.AddReference("MyLib")
<System.Reflection.Assembly object at 0x02A95390>
import MyLib
from MyLib import *
Ready to go!
testI1 = MyLib.CXDoc("path that does exist")
Yeah all worked fine!
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()

What happened! Well inside the constructor it hit an file not found
exception of some such.
Can someone explain why it hides that from me and calls the default
constructor and shows me that exception.

This seems like an unintuitive thing to do, and has made debugging a huge
pain.

-Chris
Alexey Borzenkov
2009-12-16 21:39:29 UTC
Permalink
Post by Chris Gagnon
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
Post by Chris Gagnon
testI2 = MyLib.CXDoc("path that dosn't exist")
  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
_________________________________________________
Python.NET mailing list - ***@python
Alexey Borzenkov
2009-12-17 09:01:09 UTC
Permalink
You may try it and tell me if it helps.
Ok, I just checked it myself and it appears to work in your case. The
patch is now in my tree:

http://git.kitsu.ru/patched/pythonnet.git?a=commitdiff;h=995109e3ed27d6061dde5376f2968c9fbad894b7

I hope maintainers would take a look at this and maybe apply it in their tree.
_________________________________________________
Python.NET mailing list - PythonDotNet-+ZN9ApsXKcEdnm+***@public.gmane.org
http://mail.python.org/mailman/listinfo/pythondotnet
Chris Gagnon
2009-12-17 17:16:32 UTC
Permalink
Thanks Alexey,

I haven't had a chance to try it yet, but I'm sure I'll be integrating your
patch into our python.NET here.

Cheers,
Chris
Post by Alexey Borzenkov
You may try it and tell me if it helps.
Ok, I just checked it myself and it appears to work in your case. The
http://git.kitsu.ru/patched/pythonnet.git?a=commitdiff;h=995109e3ed27d6061dde5376f2968c9fbad894b7
I hope maintainers would take a look at this and maybe apply it in their tree.
Loading...