Learntofish's Blog

A blog about math, physics and computer science

Tutorial: Object Oriented Programming in Python – Part 8

Posted by Ed on December 11, 2011

Update (03 Jan 2012):
When I wrote this tutorial I erroneously assumed that call by value and call by reference also exist in Python. However, I learned today that this is not the case. In Python you have something called call by object reference meaning:
– The value of a reference is copied. Keep in mind that the value of a reference is an address (see further reading down below).
– Everything in Python is an object, even integers. For example, b=1 means an object with value 1 is created (right hand side) and b (left hand side) is a reference to that object. Try this in the Python-Shell:

>>> b = 1
>>> id(b)
505497992
>>> b = 3
>>> id(b)
505498024

With id() you can check the address of b. You will notice that b has another address after the “assignment” b=3.

See my post: Call by Object Reference (Call by Sharing)

The section below was written before I knew about call by object reference.
—————————————————
 

This is the continuation of the Python OOP tutorial. Here I want to talk about the difference between call by value and call by reference. Besides, I’ve already explained something similar using C++ here.

Example 1 – Call by value:
Consider this code:

# testValue.py
def myProgram():
    x = 5
    y = x
    print("The value of x is: ", x)
    print("The value of y is: ", y)

    print("\nLet's increase the value of y...")
    y = y + 1
    print("\nThe value of y is: ", y)
    print("The value of x is: ", x)

if __name__ == "__main__":
    myProgram()

Save it in a file testValue.py and run it (e.g. by pressing F5 in IDLE). The output is:

>>> ================================ RESTART ================================
>>>
The value of x is:  5
The value of y is:  5

Let's increase the value of y...

The value of y is:  6
The value of x is:  5

Nothing spectacular happened. Let’s review the code in testValue.py:
– In line 03 we assign the value 5 to x.
– In line 04 we copy the value of x and save it in y.
– In line 09 we increase y by 1.
– In line 10 and 11 we check the values of x and y. As exptected the value of y is increased by 1 and the value of x remains the same.
This is called call by value since we copied the value from one variable to the other.

Example 2 – Call by reference:
Consider the following class:

# module audio

class Radio:
    # attributes
    volume = 0

    # methods
    def __init__(self, volume):
        self.volume = volume

    def setVolume(self, volume):
        self.volume = volume

    def getVolume(self):
        return self.volume

Save this code in a file audio.py.

Next, consider the following code:

# testRef.py

from audio import Radio

def myProgram():
    x = Radio(10)
    y = x
    print("The volume of radio x is: ", x.getVolume())
    print("The volume of radio y is: ", y.getVolume())

    print("\nLet's change the volume of y to 55...")
    y.setVolume(55)

    print("\nThe volume of radio y is: ", y.getVolume())
    print("The volume of radio x is: ", x.getVolume())

    print("\nHuh? Why is the volume of x also set to 55?")

if __name__ == "__main__":
    myProgram()

Save the code in a file testRef.py and run it. The output is:

>>> ================================ RESTART ================================
>>>
The volume of radio x is:  10
The volume of radio y is:  10

Let's change the volume of y to 55...

The volume of radio y is:  55
The volume of radio x is:  55

Huh? Why is the volume of x also set to 55?

Let’s analyze what happens:
– In line 03 of testRef.py we import the Radio class from the audio module.
– In line 06 we create a Radio object and initialize the volume to 10.
– In line 07 we try to create a new radio y by copying x, but there is a twist.
– In line 08 and 09 we print both their volume which is 10.
– In line 12 we change the volume of radio y to 55.
– In line 14 and 15 we check their volumes again. However, the twist becomes obvious:
The volume of radio x was also changed to 55. But why?

Explanation:
In order to understand this we will first have a look at the computer memory, e.g. the RAM. The computer memory has addresses that help the computer to find data. Think of it as addresses of people in a city. The postman would not know where to deliver his mails if there were no addresses.
Now for simplicity, lets just call the addresses in the memory 1,2,3,..800000.

Recall what happened in Example 1 (call by value):
– When we create a variable x and assign the value 5 to it, the computer first looks for a free spot in the memory. Let’s say address 1 is available. The computer then reserves this free spot for x and saves the value 5 there.

variable ---------> memory address
x ----------------> 1

– When we try to print x, the computer examines x, recognizes that it was saved in address 1 and looks there for the value. It finds 5 there and prints 5.
– When we try to create a copy y of x we use the expression y=x. The following happens:
(i) The computer examines the expression from left to right. It first reads y and looks for a free spot in the memory. It finds e.g. address 2 which is reserved for y.

variable ---------> memory address | value
x ----------------> 1              | 5
y ----------------> 2              | no value yet

(ii) Afterwards, it reads the equals sign and x. It looks for the value that is saved “at x”, i.e. at address 1 and finds 5. This value is copied to address 2.

variable ---------> memory address | value
x ----------------> 1              | 5
y ----------------> 2              | 5

This whole procedure is called copy by value since the value is copied.

Recall what happened in Example 2 (call by reference):
When we create an object we have to use the constructor, e.g. for the Radio class the constructor is Radio(). Now, usually we write r = Radio(). But try this:
– Run the audio.py file in the Python-Shell (e.g. by typing F5 in IDLE). Then enter this in the Python-Shell:

>>> Radio(80)
<__main__.Radio object at 0x02811DB0>
>>> Radio(80)
<__main__.Radio object at 0x02811E90>
>>> Radio(80)
<__main__.Radio object at 0x02809690>

Here, we created three Radio objects. Python even shows us the address of them in hexadecimal format. (Don’t worry if the hexadecimal number is not the same as yours. It’s different on every computer)

reference---------> memory address | object volume
? ----------------> 0x02811DB0     | 80
? ----------------> 0x02811E90     | 80
? ----------------> 0x02809690     | 80

The problem is that we don’t have access to the objects because we did not write r=Radio(). Here, r is the reference. Think of it as the postman who knows that there exist people with addresses somewhere in the city, but he does not know how to get to them.
Type the following in the Python-Shell:

>>> r = Radio(80)
>>> r
<__main__.Radio object at 0x02811ED0>

This time we have a reference to the object and can access it to manipulate its volume.

reference---------> memory address | object volume
? ----------------> 0x02811DB0     | 80
? ----------------> 0x02811E90     | 80
? ----------------> 0x02809690     | 80
r ----------------> 0x02811ED0     | 80

Now, comes the crucial step. Let’s try to copy the object at r by using s=r, where s shall be the copy. Type s=r in the Python-Shell:

>>> s = r
>>> s
<__main__.Radio object at 0x02811ED0>

The following happened:

reference---------> memory address | object volume
? ----------------> 0x02811DB0     | 80
? ----------------> 0x02811E90     | 80
? ----------------> 0x02809690     | 80
r ----------------> 0x02811ED0     | 80
s ----------------> 0x02811ED0     | 80

Have you noticed that s and r point to the same address? The computer did not look for a new memory spot for s but instead copied the reference r, i.e. address of r.
What happens if we change the volume of s? Let’s try in the Python-Shell:

>>> s.setVolume(55)
>>> print("The volume of s is: ", s.getVolume())
The volume of s is:  55
>>> print("The volume of r is: ", r.getVolume())
The volume of r is:  55

The volume of r also has changed! But it is clear why:
– When we change the volume of s, we change the volume of the object at 0x02811ED0.
– Then we look at the volume of r, i.e. we also look at the object at 0x02811ED0 since r and s point to the same address.

reference---------> memory address | object volume
? ----------------> 0x02811DB0     | 80
? ----------------> 0x02811E90     | 80
? ----------------> 0x02809690     | 80
r ----------------> 0x02811ED0     | 55
s ----------------> 0x02811ED0     | 55

No wonder r is also changed. Actually, it doesn’t even make sense to say that “r has changed”. What can only change is the object r is pointing at.

Copying an object
If we really wanted to copy an object and not the reference we have to use the copy() function it. Type this in the Python-Shell:

>>> from copy import copy
>>> r_copy = copy(r)
>>> r_copy
<__main__.Radio object at 0x0280B8B0>

We see that r_copy has a different address than r. Let’s change the volume of r_copy:

>>> r_copy.setVolume(13)
>>> print("The volume of r is: ", r.getVolume())
The volume of r is:  55
>>> print("The volume of r_copy is: ", r_copy.getVolume())
The volume of r_copy is:  13

We changed the volume of r_copy without affecting the volume of r:

reference---------> memory address | object volume
? ----------------> 0x02811DB0     | 80
? ----------------> 0x02811E90     | 80
? ----------------> 0x02809690     | 80
r ----------------> 0x02811ED0     | 55
s ----------------> 0x02811ED0     | 55
r_copy -----------> 0x0280B8B0     | 13

References:
1) How do I copy an object in Python? (effbot.org)
2) copy — Shallow and deep copy operations (Python documentation)

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: