Wednesday, December 12, 2012

Comment Python assigne les "variables"

Dans le deuxième tutoriel, je parle de variables et d'assignations (ou d'affectations pour les lecteurs du vieux continent). Bien que ces termes sont utiles pour décrire une ligne de code Python comme:

a = 4

strictement parlant, a n'est pas une variable comme on l'entends au même titre qu'avec un langage comme C ou C++ et l’opérateur d'assignation (=), lui, et bien, vous allez voir. Je vais couvrir ici de l'information supplémentaire, pour ceux qui aiment comprendre dans le détail. Même si vous débutez a peine, c'est une bonne idée de lire cet article et de s'y referer dans le futur, quand on a une meilleure compréhension du langage.

Le modèle C


En C, on assigne la valeur 4 a un entier a, que l'on définit grâce au mot clé int


int a = 4;
int b = 17;

Regardons ce qui se passe en arrière plan, avec une représentation de la mémoire. Prenons un exemple simple, avec 4 espaces mémoire et ou chaque int prend 1 espace.

espace 1espace 2espace 3espace 4

Après avoir assigné a = 4 et b = 17, on se retrouve avec:

espace 1espace 2espace 3espace 4
4  17   

La variable a est a l'espace 1, qui contient une valeur de 4. Le compilateur sait que, a chaque fois que l'on utilise la variable a, on parle de l'espace mémoire 1. Faire a = 5 nous donne simplement:


espace 1espace 2espace 3espace 4
5  17   

L'espace 2 est utilise par une autre variable (b). De ce fait, il serait impossible de changer notre variable a d'un entier par une chaine de caractères, car l'espace 2, 3, etc n'est pas disponible. De toute façon, C réserve l'espace mémoire pour un int au moment de la compilation et on ne sait jamais l'ordre dans lequel les variables sont en mémoire (chaque compilateur le fait différemment).

Avec Python, pas de problème de ce genre:

Le modèle Python


En Python, quand on déclare a = 4, en fait ce que l'on fait c'est de créer un objet, un entier de valeur 4:

objet: 4

Puis le a = lui donne un nom:

objet: 4
a

Si on "assigne" 5 en écrivant a = 5, on crée un autre objet, un entier de valeur 5:

objet: 5
objet: 4
a

Puis le a = lui donne un nom, et comme il ne peut y avoir qu'un seul objet avec le même nom, notre objet:4 perd son nom:

objet: 5
a
objet: 4

Éventuellement, Python se débarrasse de l'objet sans nom (4) et on se retrouve avec:

objet: 5
a

Et que se passe t'il si on fait b = a ?

objet: 5
a  b

Les variables a et b nomment toutes deux l'objet int() de valeur 5.


Cette flexibilité entraine néanmoins une perte de performance par rapport au modèle du langage C, car il faut créer un nouvel objet pour chaque "assignation" (création d'un objet et étiquetage du nom, en fait).

dir()


Il y a une fonction bien utile avec python, c'est dir(). Cela retourne une liste des noms qui sont disponibles. Donc si on déclare a = 4, on y trouvera 'a' dans cette liste. Et si on fait dir(a), alors on peut voir toutes les méthodes qui peuvent être appliquées a cet objet 'a'.

C'est le temps d'essayer quelques trucs avec python en mode interactif:

pi@fdion-rpi ~ $ python
Python 2.7.3rc2 (default, May  6 2012, 20:02:25)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> a = 4
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a']
>>> b = 17
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a', 'b']
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
>>> a
4
On voit bien que python retourne 4, et que a a des methodes du genre __int__ et real, celles de int(). Continuons:

>>> a = 5
>>> a
5

Entre la première et deuxième ligne, python a du travailler dur a créer un nouvel objet et lui a donné le nom a. Notre ancien a (4) lui, il est dans les vapes (et python s’apprête a faire le ménage et a le virer). Mais comme c'est pas évident de voir que c'est ce qui ce passe, on va utiliser une chaine de caractère, cette fois:

>>> a = "wow"
>>> dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>>

C'est tres clair que a n'est pas le meme objet, car on ne retrouve plus __int__ ni real, par exemple. On y retrouve les methodes d'un objet str().

Conclusion


La ligne de code Python a = 4 represente donc un objet int() de valeur 4 qui a pour nom a.

On va donc clore cette petite (!) parenthèse la dessus, et on se revoit pour le prochain tutoriel.

No comments: