[Python.NET] Wrap in interface (#1240)

Mohamed Koubaa <[email protected]> Tue, 3 Nov 2020 02:15:49 +0000
Newsgroups gmane.comp.python.dotnet
Message-ID <SN6PR01MB5007657AC4CE6BC4ABBDC78599110@SN6PR01MB5007.prod.exchangelabs.com>
--===============5251633457715666591==
Content-Language: en-US
Content-Type: multipart/alternative;
	boundary="_000_SN6PR01MB5007657AC4CE6BC4ABBDC78599110SN6PR01MB5007prod_"

--_000_SN6PR01MB5007657AC4CE6BC4ABBDC78599110SN6PR01MB5007prod_
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

Hello,

This PR<https://github.com/pythonnet/pythonnet/pull/1240> changed behavior =
of pythonnet to use stricter typing when a method returns an interface.  I'=
d like a way to opt-out of this behavior, and I'll share a representative u=
se-case below.  The tl;dr is that python is a dynamic language and I expect=
 the C# objects which pythonnet wraps to behave more like "dynamic" and les=
s like the static types on the interface.

**** C# code ****
namespace Example1
{
    public enum ShapeType
    {
        Square,
        Circle
    }
    public interface IShape
    {
        void Draw();

        double Area { get; }

        ShapeType Type { get; }
    }

    public class Square : IShape
    {
        public double Length { get; }
        public Square(double length)
        {
            Length =3D length;
        }

        public void Draw() {}
        public double Area { get { return Length * Length; } }
        public ShapeType Type { get { return ShapeType.Square; } }
    }

    public class Circle : IShape
    {
        public double Radius { get; }
        public Circle(double radius)
        {
            Radius =3D radius;
        }

        public void Draw() {}
        public double Area { get { return Math.PI * Radius * Radius; } }
        public ShapeType Type { get { return ShapeType.Circle; } }
    }

    public class ShapeDataModel
    {
        private Dictionary<int, IShape> _shapes =3D new Dictionary<int, ISh=
ape>();
        private int _nextId =3D 1;

        public int AddShape(IShape shape)
        {
            int id =3D _nextId;
            _shapes[id] =3D shape;
            _nextId++;
            return id;
        }

        public IShape GetShape(int id)
        {
            return _shapes[id];
        }
    }
}


**** Python code ****
>>> import clr
>>> clr.AddReference("Example1")
>>> import Example1
>>> sq1 =3D Example1.Square(2.)
>>> ci1 =3D Example1.Circle(1.3)
>>> sq2 =3D Example1.Square(2.5)
>>> m =3D Example1.ShapeDataModel()
>>> m.AddShape(sq1)
1
>>> m.AddShape(sq2)
2
>>> m.AddShape(ci1)
3
>>> m.GetShape(2)
<Example1.IShape object at 0x000002A3448A09A0>
>>> m.GetShape(2).Area
6.25
>>> m.GetShape(2).Length
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'IShape' object has no attribute 'Length'

**** summary ****
This factory and/or datamodel pattern is very common in my codebase and pro=
bably also lots of object oriented systems.  The mentioned PR breaks this c=
ommon design pattern in C# objects wrapped by pythonnet, and I would like a=
 way to opt-out of it.  Maybe an attribute on the method which returns an i=
nterface can be used, or maybe something global to switch this behavior.  I=
 think perhaps the original bug might also have been fixed in a different w=
ay.

Thanks!
Mohamed

--_000_SN6PR01MB5007657AC4CE6BC4ABBDC78599110SN6PR01MB5007prod_
Content-Type: text/html; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<html>
<head>
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Diso-8859-=
1">
<style type=3D"text/css" style=3D"display:none;"> P {margin-top:0;margin-bo=
ttom:0;} </style>
</head>
<body dir=3D"ltr">
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
Hello,</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<br>
</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
This <a href=3D"https://github.com/pythonnet/pythonnet/pull/1240" title=3D"=
https://github.com/pythonnet/pythonnet/pull/1240">
PR</a>&nbsp;changed behavior of pythonnet to use stricter typing when a met=
hod returns an interface.&nbsp; I'd like a way to opt-out of this behavior,=
 and I'll share a representative use-case below.&nbsp; The tl;dr is that py=
thon is a dynamic language and I expect the C#
 objects which pythonnet wraps to behave more like &quot;dynamic&quot; and =
less like the static types on the interface.</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<br>
</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
**** C# code ****</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"font-family: &quot;Courier New&quot;, monospace;">namespace =
Example1</span>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">{</sp=
an></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; public enum ShapeType</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; Square,</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; Circle</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; public interface IShape</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; void Draw();</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; double Area { get; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; ShapeType Type { get; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; public class Square : IShape</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public double Length { get; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public Square(double length)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Length =3D length;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public void Draw() {}</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public double Area { get { return Length * Length; }=
 }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public ShapeType Type { get { return ShapeType.Squar=
e; } }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; public class Circle : IShape</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public double Radius { get; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public Circle(double radius)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Radius =3D radius;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;"><br>
</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public void Draw() {}</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public double Area { get { return Math.PI * Radius *=
 Radius; } }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public ShapeType Type { get { return ShapeType.Circl=
e; } }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; public class ShapeDataModel</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; private Dictionary&lt;int, IShape&gt; _shapes =3D ne=
w Dictionary&lt;int, IShape&gt;();</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; private int _nextId =3D 1;</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public int AddShape(IShape shape)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int id =3D _nextId;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _shapes[id] =3D shape;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _nextId++;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return id;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; }</span></div>
<div><br>
</div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; public IShape GetShape(int id)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; {</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return _shapes[id];</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; &nbsp; &nbsp; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&nbsp=
; &nbsp; }</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">}</sp=
an></div>
<br>
</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<br>
</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(24, 26, 27); display: inline !importan=
t; --darkreader-inline-bgcolor:#131516;" data-darkreader-inline-bgcolor=3D"=
">**** Python code ****</span><br>
</div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111; font-family: &quot;Courier New&quot;, monospace;" data-darkread=
er-inline-bgcolor=3D"">&gt;&gt;&gt; import clr</span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111; font-family: &quot;Courier New&quot;, monospace;" data-darkread=
er-inline-bgcolor=3D"">&gt;&gt;&gt; clr.AddReference(&quot;Example1&quot;)<=
/span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111; font-family: &quot;Courier New&quot;, monospace;" data-darkread=
er-inline-bgcolor=3D"">&gt;&gt;&gt; import Example1</span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D"">
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; sq1 =3D Example1.Square(2.)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; ci1 =3D Example1.Circle(1.3)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; sq2 =3D Example1.Square(2.5)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m =3D Example1.ShapeDataModel()</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.AddShape(sq1)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">1</sp=
an></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.AddShape(sq2)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">2</sp=
an></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.AddShape(ci1)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">3</sp=
an></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.GetShape(2)</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&lt;E=
xample1.IShape object at 0x000002A3448A09A0&gt;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.GetShape(2).Area</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">6.25<=
/span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">&gt;&=
gt;&gt; m.GetShape(2).Length</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace;">Trace=
back (most recent call last):</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace; color:=
 rgb(237, 92, 87); --darkreader-inline-color:#ed605b;" data-darkreader-inli=
ne-color=3D"">&nbsp; File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&=
gt;</span></div>
<div><span style=3D"font-family: &quot;Courier New&quot;, monospace; color:=
 rgb(237, 92, 87); --darkreader-inline-color:#ed605b;" data-darkreader-inli=
ne-color=3D"">AttributeError: 'IShape' object has no attribute 'Length'</sp=
an></div>
</span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D""><br>
</span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D"">**** summary ****</span></=
div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D"">This factory and/or datamo=
del pattern is very common in my codebase and probably also lots of object =
oriented systems.&nbsp; The mentioned PR
 breaks this common design pattern in C# objects wrapped by pythonnet, and =
I would like a way to opt-out of it.&nbsp; Maybe an attribute on the method=
 which returns an interface can be used, or maybe something global to switc=
h this behavior.&nbsp; I think perhaps the
 original bug might also have been fixed in a different way.&nbsp;</span></=
div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D""><br>
</span></div>
<div style=3D"font-family: Calibri, Arial, Helvetica, sans-serif; font-size=
: 12pt; color: rgb(0, 0, 0); --darkreader-inline-color:#e8e6e3;" data-darkr=
eader-inline-color=3D"">
<span style=3D"background-color: rgb(19, 21, 22); --darkreader-inline-bgcol=
or:#0f1111;" data-darkreader-inline-bgcolor=3D"">Thanks!<br>
Mohamed</span></div>
</body>
</html>

--_000_SN6PR01MB5007657AC4CE6BC4ABBDC78599110SN6PR01MB5007prod_--

--===============5251633457715666591==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
PythonNet mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/pythonnet.python.org/
Member address: [email protected]

--===============5251633457715666591==--