Archive

Posts Tagged ‘Python’

Passing arguments to a function: by-value, by-reference, by-object…

February 18, 2013 1 comment

When calling a function, the exact mechanism for passing arguments (assigning arguments to parameters) depends on the evaluation strategy and how it is implemented. Some common ways of passing arguments to a function are:

  • by value
  • by reference
  • by object

This is not a theoretical article about passing arguments and doesn’t contain rigorous definitions. I just will try to show clearly how the above mechanisms work on some specific languages widely used: C, C++, Python and Perl.

C

In C, a variable declaration doesn’t create anything, it just ensures that, at runtime, there will be a chunk of memory in the stack big enough to hold the variable value. Keeping that in mind let’s look at the following simple code:

int a = 3;
void update_the_copy(int b) {b = 5;}
update_the_copy(a);
a == 5; // Evaluates to false

In the above example we have two variable declarations, one for a and one more for b. They point to different chunks of memory. When the function is called its arguments are evaluated and a copy of their value is assigned to the function parameters (so b contains a byte-to-byte copy of a). This is called to pass by value. When doing it there is no way to change the arguments from inside the function simply because one has no access to them. The only way to return a value to the caller is via the function’s return value.

Obviously that is memory inefficient when working with large values or nontrivial types. And it doesn’t allow to change in-place variables defined in the caller. C solves the problem by passing (by value) a pointer. In this case the passed value is a memory address i.e.; just a few bytes, so passing the argument is cheap in terms of memory. Although the original pointer and the passed pointer are different entities, they still point to the same memory address so the function can change the pointed data in-place:

int a = 3;
int *pa = &a;
void update_inplace(int *b) {*b = 5;}
update_inplace(pa);
a == 5; // Evaluates to true

C++

C++ supports passing-by-value. However it doesn’t use pointers to deal with the problems of passing a large amount of data or changing values in-place. Instead it uses references. A C++ reference allows you to create a new name for a variable. The reference doesn’t contain a copy of the variable but behind the scenes it keeps around its address. So you can use the reference to read or change the original value stored in that variable:

void swap_values(int& a, int& b) {
++++temp = a;
++++a = b;
++++b = temp;
}

int first = 1;
int second = 2;
swap_values(first, second);

After the swap first will be 2 and second will be 1. Notice that when we call the function we use regular int variables, not references. But, in the function definition, the parameters are references. As a result when the function is called it receives an implicit reference to the variables used as arguments instead of a copy of them. This is called to pass by reference. It allows the function to access the original arguments, no a copy of them. And changes to those values inside the function will be seen outside the function.

Perl

Perl always passes by reference, but not in the same form that C++. The Perl syntax for defining functions doesn’t include a explicit signature so the programmer cannot declare a given parameter as being a reference as she does in C++. Perl passes by reference using an aliasing mechanism: when a function is called, all incoming arguments are flattened into a single list which is then aliased to the implicit variable @_. In this way the function gets implicit references to the arguments:

sub swap_values
{
++++my $temp = $_[0]; # Make a local copy of the first received argument
++++$_[0] = $_[1];
++++$_[1] = $temp;
}

my $first = 1;
my $second = 2;
swap_values($first, $second);

After the swap first will be 2 and second will be 1. Once again, notice that passing by reference doesn’t mean that we use Perl references as arguments, Perl references are another mechanism provided by Perl for modifying values in-place. The important thing is that when the function is called it receives implicit references to the arguments. Therefore we are passing by reference. Also notice that the flattening of arguments can be tricky (although this quirk has nothing to do with the fact that Perl passes by reference. If you are interested, you can find nice details about flattening here ).

To the end to simulate pass-by-value one needs to do explicit copies of the arguments inside the function:

my $name = "Chuck";
sub reverse_name
{
my $name = shift; # a local copy of the argument
$name = reverse $name; # change the local copy
}

reverse_name($name);
print $name; # output "Chuck"

Python

Things are different in Python. All Python values are objects (so they are first-class entities) that can be tagged with one or more names or can have no name at all. Variable names are just names (you can see it graphically here). As a result talking about pass-by-value or pass-by-reference makes non sense in Python (unless one consider that CPython is implemented in C. But I will stay at Python level in this section).

So how does Python pass arguments to a function? It passes by object. Let’s see what does it means with a couple of examples::

obj1 = 3
def simulate_pass_by_value(obj2):
++++obj2 = 5

simulate_pass_by_value(obj1)
obj1 == 5 # Evaluates to false

We get the same result as passing by value. It is not an unexpected result if one remember what we have said about object names at the beginning of this section. The object named obj1 is passed itself to the function. When doing so a new tag, obj2, is added to it. Now the object has two names, obj1 and obj2. The effect of the assignment inside the function is to move the obj2 name to a new object whose value is 5. When the function ends the object named obj2 goes away and the passed object remains untouched and can still be accessed with the obj1 name.

In the following example we pass a mutable object to a function in order to simulate a pass by reference:

def simulate_pass_by_reference(list2):
++++list2.append(2)

list1 = [1]
simulate_pass_by_reference(list1)
list1 # Evaluates to [1, 2]

Now we get the same result as passing by reference i.e; the argument is changed in-place. The object named list1 is passed to the function and a new tag, list2, is attached to it. In this case the new name is never reassigned to a new value. Instead, a method call modifies the object in-place. When the function ends the list2 tag goes away but the modified object is still accessible via its list1 tag.

In summary

  • C always passes by-value. Functions can use pointer arguments to change caller variables in-place.
  • C++ supports both, pass-by-value and pass-by-reference.
  • Perl always passes by-reference. Functions can do local copies of its arguments in order to simulate passing by-value.
  • Python passes by-object. Functions can simulate passing by-value and passing by-reference.