views:

186

answers:

4

There is a well known issue when it comes to using .NET value types in IronPython. This has recently caused me a headache when trying to use Python as an embedded scripting language in C#. The problem can be summed up as follows:

Given a C# struct such as:

struct Vector {
    public float x;
    public float y;
}

And a C# class such as:

class Object {
    public Vector position;
}

The following will happen in IronPython:

obj = Object()
print obj.position.x    # prints ‘0’
obj.position.x = 1
print obj.position.x    # still prints ‘0’

As the article states, this means that value types are mostly immutable. However, this is a problem as I was planning on using a vector library that is implemented as seen above. Are there any workarounds for working with existing libraries that rely on value types? Modifying the library would be the very last resort, but I'd rather avoid that.

+1  A: 

When you call

obj.position.x = 1

What you get is that the obj object gets you an instance of the position struct, which it essentially makes a copy of, so, setting the X value doesn't get propogated.

What you're saying is that obj.position = Vector(1,0) is what you should be doing. Similar things happen in C#.


Edit - possible work around.

If you don't want to set up the constructor, I believe this will work:

obj = Object()
pos = obj.position; # gets the object
pos.x = 1
pos.y = 2
obj.position = pos # I'm not sure if this line is necessary
McKay
Well, yes and no. I understand the concept of value types and I can see the difficulties that they present when it comes to translating them over to IronPython. However, the current solution is different from the behavior in C# (as mentioned in the linked article) and different from the way Python works (which doesn't have value types). If IronPython had something like a `getattrref` function, that would enable users to recreate the C# behavior, which as far as I can tell isn't possible right now.
kloffy
Yeah, C# is different, but the behavior is kind of similar. I've updated my response, that might work a little better
McKay
That's not a bad way of making it look similar syntactically, but I'm wondering if there is a way to get the same semantics as in C# (i.e. do not create a new copy of Vector).
kloffy
I'm not aware of any way to get the same semantics in IronPython, but I'm still very rusty in IronPython.Have you tried it in IronRuby? It might meet the requirements of your project and give different behavior.
McKay
The workaround won't work either. It has the same copy problem.>>> bar = MyStruct()>>> print "%d:%d" % (bar.x, bar.y)0:0>>> bar.x = 8>>> print "%d:%d" % (bar.x, bar.y)0:0
Laurion Burchall
+2  A: 

There's no need to modify the library, just use a proxy.

struct Vector {
    public float X;
    public float Y;
}

class BetterVector {
    public float X;
    public float Y;
    public Vector Optimized { get { return new Vector(X, Y); } }
}

class Object {
    public BetterVector Position { get; set; }
}

Now the Python code can set fields as normal and your code can call Optimized when it needs to feed the data to OpenGL or XNA or whatever you're using.

You can even use implicit coercion if calling Optimized seems like too much work:

class BetterVector {
   // ...
   public static implicit operator Vector(BetterVector v) {
       return v.Optimized;
   }
}
Frank Krueger
That is pretty cool, especially with the implicit coercion. One would have to write a bunch of proxies but it shouldn't be too much work. I'll leave the question open for a little more, just to see if there are any other suggestions, but I like this approach.
kloffy
+1  A: 

The only way I've found to update structs is to make use of the fact that you can specify any public field/property when creating a struct. The syntax looks like named/optional parameters in Python.

namespace StructExample
{
    public struct MyStruct
    {
        public int x;
        public int y { get; set; }
    }

    public class MyClass
    {
        public MyStruct a;
    }
}

We can use the classes in IronPython like this:

>>> foo = MyClass()
>>> print "%d:%d" % (foo.a.x, foo.a.y)
0:0
>>> foo.a.x = 1 # doesn't work!
>>> print "%d:%d" % (foo.a.x, foo.a.y)
0:0
>>> foo.a = MyStruct(x=1,y=2)
>>> print "%d:%d" % (foo.a.x, foo.a.y)
1:2

It would be nice if Python had a syntax like the F# 'with' for creating a new struct, copying the fields from the old one. Unfortunately we have to specify all the fields when cloning a struct.

Laurion Burchall
Thanks for mentioning the kwargs constructor, that can be very handy.
kloffy
A: 

Rather puzzling that you ended up in such a corner. In python (tried this on IronPython 2.6.1, .Net 4.0) the equivalent code would be about this:

>>> class a:
...  x = 0
...  y = 0
...
>>> class b:
...  Vector = a()
...
>>> c = b()
>>> c.Vector.x = 1
>>> print c.Vector.x
1

Note there is one difference between my pseudo code and yours - static property is assigned an instance of a class, not just left with type definition. As a side effect, an actual instance of a class is initialized as b.Vector when b is instantiated.

(The pseudo-code is still "broken" - the initialization must go into def init(self), but that's a different story)

The moral of the example, instead of leaving "public Vector position" uninitialized, build initialization of "position" into class Object.

ddotsenko
Thank you for your comment. However, I don't think it is applicable because I was talking about .NET value types. I am aware that you wouldn't run into the same problem using IronPython classes only.
kloffy