Pass-by-reference : Could a C#-like approach work in Python?

Stephen Horne $$$$$$$$$$$$$$$$$ at $$$$$$$$$$$$$$$$$$$$.co.uk
Wed Sep 10 14:04:45 EDT 2003


On Wed, 10 Sep 2003 16:55:28 +0200, Peter Otten <__peter__ at web.de>
wrote:

>Obviously, your example could easily be rewritten to
>
>def inc(p):
>    return p + 1
>
>x = inc(x)
>
>and a similar function with two by-ref parameters would still be readable:
>
>x, y  = inc2(x, y)
>
>So here's my question. How many functions are there in your *real* code that
>could benefit from, say three or more by-ref parameters?
>Hint: for me it comes close to 0 :-)

Its not an issue of whether the same effect can be achieved in Python.
It's more an issue of whether the method used expresses the
programmers intentions properly.

If a function is defined as...

  def Inc (p) :
    return p + 1

then that to me expresses an intention which is different to that
expressed by...

  def Inc (ref p) :
    p += 1

The latter case expresses the intention to in-place rebinding of the
parameter. In doing so, it better expresses the purpose of some kinds
of functions (though not this noddy example, obviously).

This is all very fuzzy, so let's examine an example which is based on
an issue in some real Python code (though this is simplified to make
the point). The problem is to insert an object into some container -
appending to a list will do here. However, the object being inserted
will typically be quite large, so we want to avoid copying it if
possible.

No problem there, it seems...

  class c_Container :
    f_List = []

    def Add (self, p_Object) :
      f_List.append (p_Object)

The problem with this, however, is the risk of accidental
side-effects. If the caller goes on to modify the object that he
passed as a parameter, that will change the content of the container.

So how can rebindable parameters help with this?

One approach would be to make the function rebind the parameter to
either None or some kind of read-only proxy object. If the caller
tried to modify the object after passing it in, he'd very quickly get
error messages. They may seem cryptic, but they'd be better than the
strange hard-to-trace errors you get with accidental side-effects.

Having figured out what the problem is, the caller simply has to make
a copy of the object himself (or use a variant of the function that
does the copying itself).

  class c_Container :
    f_List = []

    def Add (self, ref p_Object) :
      f_List.append (p_Object)
      p_Object = None
      # p_Object = Read_Only_Proxy (p_Object)

    def Add_Copy (self, p_Object) :
      f_List.append (copy.deepcopy (p_Object))


The point of the ref parameter here is not just to allow information
to be passed to the caller from the function - it is to ensure that
the source of the specific parameter passed in gets rebound. The
intention to 'give away' that object would be expressed by the 'ref'
in the call...

  l_Container = c_Container ()
  l_Object    = Get_The_Object ()

  l_Container.Add (ref l_Object)

Actually, an 'in' or 'give' modifier might make even more sense here
(very different to C# - I can't imagine a reason for an 'out' modifier
in Python).

Where such problems occur, this is by no means foolproof of course.
For example...

  l_Container   = c_Container ()
  l_Object_Ref1 = Get_The_Object ()
  l_Object_Ref2 = l_Object_Ref2

  l_Container.Add (ref l_Object_Ref1)

  l_Object_Ref2.Mutate_Somehow ()

However, it would probably catch a very substantial portion of real
accidental side-effect errors.

>By the way, this is strictly from a user perspective, I trust the Python
>developers to overcome the implementation issuses, *if* there is a
>significant usability improvement.

And that is the big question.

C# 'needs' ref parameters because it has no convenient way to return
multiple values from a function. Python does not. The question is
really whether this argument about expressing intentions and evading
errors is compelling or not.





More information about the Python-list mailing list