Victor,

Our API is C# and some of our users actually directly use C# to access them and don't use python.  We don't intend to change the API.  Of course, users may have to change their scripts to be python3 compatible when they opt into using our API from CPython, but we expect most anything unrelated to 2to3 to continue to work.

My expectation is that if a method/property returns an object using the interface but the concrete class which it returns implements a given method, then hasattr should find it.  I think options 4&5 do what I need (but isn't option 4 what we had prior to #1240?)

Thanks,
Mohamed

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

[External Sender]

A dump of thoughts on the matter:

 

  1. IronPython is Python 2 only, Python.NET 3.0+ is Python 3 only. Why not use 2.5.x until you are comfortable to change API?
  2. Flag is possible, but I would not recommend that approach, because it makes combining multiple libraries that use different global flags problematic.
  3. Perhaps we could allow codecs to handle instances of wrapped interfaces (actually, isn’t that currently allowed?)
  4. IronPython apparently allows explicit interface implementations to be called (instead of returning interface-wrapped instances), if there’s no conflict with regular methods. Perhaps we should consider this as an option.
  5. Alternatively, we can keep wrapped instances, but allow to resolve public members too if the method is not found in the interface. I like this option better than 4, because actual type of an element in ISomething[] won’t change behavior of invoking interface member. It might still affect `hasattr`, but this is somewhat expected.

 

Regards,

Victor

 

From: Mohamed Koubaa
Sent: Monday, February 8, 2021 8:34 AM
To: A list for users and developers of Python.NET
Subject: [Python.NET] Re: Wrap in interface (#1240)

 

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