As you’re using IronPython and the RhinoCommon library which is in C#, I’ll refer to the C# api and C# types.
This depends on if the value you’re working with is a value type (a C# class) or a reference type (a C# struct). Many of the simpler RhinoCommon geometry items are structs, i.e points, planes, vectors etc.
As the type you’re working with (Point3d) is a struct, it’ll function as a reference type.
If you were to use a geometry item such as a Mesh, it would not be passed as a reference.
Is it because r.Point3d() actually creates new object in memory thus it does not point to the same object?
I cannot fully explain the answer to this as I’m not exactly sure. But it certainly will function that way.
You’d be better returning a new struct with your method and setting it.
import Rhino.Geometry as r
return r.Point3d(100, pt.Y, pt.Z)
#create point and run change_point method
pt = r.Point3d(0,0,0)
pt = change_point(pt)
a = pt
Here you change an existing Rhino.Geometry.Point3d. If it weren’t passed by reference, the function would throw an error, because pt would be undefined within its scope. It can only be interpreted as being a reference.
pt.X = 100
And here you’re calling the parametrized constructor of a Rhino.Geometry.Point3d, which even if pt would be passed by reference, would override it with a new point, since the constructor well instantiates a new object.
When handling existing object instances, it’s generally considered best practice to simply change their attributes and thus trigger an assignment instead of a copy, like you do in your first example.
However, when dealing with default value types (int, float, complex, bool) you should return and reassign, like @csykes proposes. Lists and dictionaries are mapping types which are more complex and handled differently still (cf. docs).
Foremost, you should never name a local and global variable the same. B works in Python because a reference instance acting like a pointer is passed over by value (referring here to a C# Struct instance) which allows you to modify its properties because it still refers to the same place in memory. In A, you are overriding this reference with a new reference. So the local pt points to something else. Anything is passed by value in Python. So in A you are changing the local copy of the pointer to another place in memory, while in B simply your two pointers pointing to the same place in memory, allowing it to modify global and local at the same time. This gives the illusion of “passing by reference”. But essentially it’s just ducktyping in practice.
Apart from that, it is good practice in all languages to explicitly return an argument to be modified for further usage. Because a void function rather indicates a state change within the system. Interfaces should clearly declare input and output.
In C# a similar thing to do would be wrapping your object instance into a custom struct called Reference<>. This struct is passed by value (because its a struct) and if you override the local reference instance pointing to another object instance, you could reproduce this behavior in C#.
Another likely relevant note here: Python variable/name scopes tends to trip people up when coming from lower level languages. Sometimes this is referred to as the LEGB rule (not in the official docs though):
This simple confusion seems to have quite some reading behind how the variables are read within and outside functions. It is indeed different to other languages. The creation of objects versus reading and reassignment of properties adding into equation .NET adds some complexity.
I don’t think there’s anything unusual here, most high level languages behave the same way. Usually what confuses people is when objects are mutable.
“Appear to do so” meaning it will give the same result, but internally will do different things regarding what gets copied around, garbage collection, etc.