Victor,

Sorry for the late response.  I think what I would need is some kind of flag in the python runtime to choose the old behavior (without having to have my own branch).  Of course there is a workaround but we've invested a lot of time in designing the API for both CPython & IronPython and it would be ideal if they did not differ in this way.  

This could be a global flag on the runtime and/or an attribute on a class/interface to decide on a granular level whether the specific class/interface will use interface method binding or instance method binding.

Thoughts?

Thanks!
Mohamed

From: Victor “LOST” Milovanov <lostfreeman@gmail.com>
Sent: Monday, November 2, 2020 8:35 PM
To: A list for users and developers of Python.NET <pythonnet@python.org>
Subject: [Python.NET] Re: Wrap in interface (#1240)
 

[External Sender]

The original bug report described a similar problem, except the behavior without defaulting to stricter interface types is very unintuitive. Imagine you have a well documented library, that exposes property Prop of type IInterface. When specific class implements some Method  in IInterface explicitly (which arguably is a very minor implementation detail), and you call Prop.Method() in Python, it can fail with AttributeError despite the documentation claiming this method exists on the interface. This is very confusing. IMHO, more confusing, than inability to get Length of an element of IShape[].

 

We could try to improve the situation for Python folks by allowing interface instance wrappers to fall back to the original class instance in case the attribute is not found in the interface itself. I think this might still be problematic, as it would make the behavior of hasattr and other similar functions a bit surprising.

 

Regardless, there’s a workaround for the scenario you described which is just doing m.GetShape(1).__raw_implementation__.Length or

m.GetShape(1).__implementation__.Length depending if you need an encoded instance.

 

Regards,

Victor Milovanov

 

From: Mohamed Koubaa
Sent: Monday, November 2, 2020 6:15 PM
To: pythonnet@python.org
Subject: [Python.NET] Wrap in interface (#1240)

 

Hello,

 

This PR 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 use-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 less 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 = 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 = 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 = new Dictionary<int, IShape>();

        private int _nextId = 1;

 

        public int AddShape(IShape shape)

        {

            int id = _nextId;

            _shapes[id] = shape;

            _nextId++;

            return id;

        }

 

        public IShape GetShape(int id)

        {

            return _shapes[id];

        }

    }

}

 

 

**** Python code ****

>>> import clr

>>> clr.AddReference("Example1")

>>> import Example1

>>> sq1 = Example1.Square(2.)

>>> ci1 = Example1.Circle(1.3)

>>> sq2 = Example1.Square(2.5)

>>> m = 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 probably also lots of object oriented systems.  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.  Maybe an attribute on the method which returns an interface 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 way. 



Thanks!
Mohamed