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 <lostfree...@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<mailto:mohamed.kou...@ansys.com>
Sent: Monday, February 8, 2021 8:34 AM
To: A list for users and developers of Python.NET<mailto:pythonnet@python.org>
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 <lostfree...@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<mailto:mohamed.kou...@ansys.com>
Sent: Monday, November 2, 2020 6:15 PM
To: pythonnet@python.org<mailto:pythonnet@python.org>
Subject: [Python.NET] Wrap in interface (#1240)



Hello,



This 
PR<https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fpythonnet%2Fpythonnet%2Fpull%2F1240&data=04%7C01%7Cmohamed.koubaa%40ansys.com%7C4b457216f75446301a4508d8cc634c3b%7C34c6ce6715b84eff80e952da8be89706%7C0%7C0%7C637484074196952423%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=cAFUSSzeNl11sru%2B1ZNS8EjdoqhST%2FAGvY73dUQgV9U%3D&reserved=0>
 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




_______________________________________________
PythonNet mailing list -- pythonnet@python.org
To unsubscribe send an email to pythonnet-le...@python.org
https://mail.python.org/mailman3/lists/pythonnet.python.org/
Member address: arch...@mail-archive.com

Reply via email to