domingo, 7 de diciembre de 2014

Learning Python: Objects, Bindings And Names

As you all know, i'm currently following a course on Maya in order to become CG Generalist one day (hopefully on April/May 2015 i will land a job in the games/film industry).

Having a programming background i can't help myself learning Python to complement my skills in Maya and so, i started a couple of weeks ago to mess around with the Maya Python Command Engine and PyQt for GUIs.

Well, this last friday i received Maya Python for Games and Film which has revealed to be quite instructive. The first chapters telling the basics, one can still learn something new specially if you come from another more traditional language such as C/C++ like me.

Each time we learn a new programming language we try to find the same mechanics we found on the ones we already know as well as the new features, those that sustain the raison d'être of this new language.

Well, taking the first steps in Python that's what happened to me: as i am used to in C++ i try to figure out whether a variable in Python is a reference (&), a copy( new object) or a pointer (*).

The truth is this way of thinking does not fit well into Python. In Python one cannot think any more about "variables" but rather "names" and references and pointers become "bindings". It will make you do less errors and get less unwanted surprises when coding. I've found the following example in the web:

1
2
3
4
5
6
7
8
9
>>> dict = {'a':1,'b':2}
>>> list = dict.values()
>>> list
[1, 2]
>>> dict['a']=3
>>> list
[1, 2]
>>> dict
{'a': 3, 'b': 2}

If you store the result of dict.values(), and change the dictionary afterwards, the previously stored result remains untouched. However, if a dictionary has lists as value entries, the behavior is not the same: If you change the dict, the list you previously created via dict.values() gets automagically updated.

1
2
3
4
5
6
7
8
9
>>> dict = {'a':[1],'b':[2]}
>>> list = dict.values()
>>> list
[[1], [2]]
>>> dict['a'].append(3)
>>> dict
{'a': [1, 3], 'b': [2]}
>>> list
[[1, 3], [2]]

One would think that the same method dict.values() has different behaviour depending on the values of the dictionary. In the first case it would return a copy of the values in a list and in the second case it would return a reference/pointer. Well dismiss references, pointers and variables and think of names, bindings and objects.

EXPLANATION

I find the graphical answer explaining this on the web by Michael Hudson very illustrative and self-explanatory so i will "echo" it here in my blog just as a reminder.

Case 1. "Copy"
1
>>> dict = {'a':1,'b':2}

The above line could be illustrated as follows:

    ,------.       +-------+
    | dict |------>|+-----+|     +---+
    `------'       || "a" |+---->| 1 |
                   |+-----+|     +---+
                   |+-----+|     +---+
                   || "b" |+---->| 2 |
                   |+-----+|     +---+
                   +-------+

where "dict" is a name, all the others surrounded by +---+ are objects and ------> are bindings.

>>> list = dict.values()

    ,------.       +-------+
    | dict |------>|+-----+|             +---+
    `------'       || "a" |+------------>| 1 |
                   |+-----+|             +---+
                   |+-----+|              /\
                   || "b" |+-----.    ,---'
                   |+-----+|     |    |
                   +-------+     `----+----.
                                      |    |
    ,------.       +-----+            |    \/
    | list |------>| [0]-+------------'   +---+
    `------'       | [1]-+--------------->| 2 |
                   +-----+                +---+
Now,  this:

>>> dict['a']=3

    ,------.       +-------+
    | dict |------>|+-----+|             +---+
    `------'       || "a" |+-.           | 1 |
                   |+-----+| |           +---+
                   |+-----+| |            /\
                   || "b" |+-+---.    ,---'
                   |+-----+| |   |    |
                   +-------+ |   `----+----.
                             |        |    |
    ,------.       +-----+   |        |    \/
    | list |------>| [0]-+---+--------'   +---+
    `------'       | [1]-+---+----------->| 2 |
                   +-----+   |            +---+
                             |            +---+
                             `----------->| 3 |
                                          +---+

So list and dict yield the no surprise result:

>>> list
> [1, 2]
> >>> dict
> {'a': 3, 'b': 2}

which is fine.

Case 2. "Reference"


>>> dict = {'a':[1],'b':[2]}
    ,------.       +-------+
    | dict |------>|+-----+|     +-----+   +---+
    `------'       || "a" |+---->| [0]-+-->| 1 |
                   |+-----+|     +-----+   +---+
                   |+-----+|     +-----+   +---+
                   || "b" |+---->| [0]-+-->| 2 |
                   |+-----+|     +-----+   +---+
                   +-------+

>>> list = dict.values()
    ,------.       +-------+
    | dict |------>|+-----+|             +-----+   +---+
    `------'       || "a" |+------------>| [0]-+-->| 1 |
                   |+-----+|             +-----+   +---+
                   |+-----+|               /\
                   || "b" |+-----.    ,----'
                   |+-----+|     |    |
                   +-------+     `----+-----.
                                      |     |
    ,------.       +-----+            |     \/
    | list |------>| [0]-+------------'   +-----+   +---+
    `------'       | [1]-+--------------->| [0]-+-->| 2 |
                   +-----+                +-----+   +---+


>>> dict['a'].append(3)

                                                    +---+
    ,------.       +-------+                     ,->| 1 |
    | dict |------>|+-----+|             +-----+ |  +---+
    `------'       || "a" |+------------>| [0]-+-'
                   |+-----+|             | [1]-+-.
                   |+-----+|             +-----+ |  +---+
                   || "b" |+-----.         /\    `->| 3 |
                   |+-----+|     |    ,----'        +---+
                   +-------+     |    |
                                 `----+-----.
    ,------.       +-----+            |     \/
    | list |------>| [0]-+------------'   +-----+   +---+
    `------'       | [1]-+--------------->| [0]-+-->| 2 |
                   +-----+                +-----+   +---+

Since the list binded by the object "a" is the same binded by the first position object of "list" the above statement is changing the same "referenced" object. So we have the following correct either:


> >>> dict
> {'a': [1, 3], 'b': [2]}
> >>> list
> [[1, 3], [2]]

CONCLUSION

In a nutshell i think that python tries to avoid copying objects whenever possible and it has a reason because copying is always expensive, not to mention deep copying!! So whenever we need a copy of an object we have to explicitly request it via copy.copy().

No hay comentarios:

Publicar un comentario