Ouvrir la navigation secondaire

Ce billet a plus de deux ans. S'il contient des informations techniques elles sont peut être obsolètes.

Précédent Browserify
Suivant Sélecteurs CSS

Passage par partage d'objet en Python et JavaScript

L'autre jour au DevFloor, je suis tombé sur un bouquin sur JavaScript très sympa.

Dans quasiment tous les livres sur Python ou JavaScript que je lis, je trouve un chapitre sur l'assignation/passage par valeur ou par référence. Or pour être plus précis, il faudrait parler d'assignation/passage par partage d'objet.

Activation du mode bikeshedding :D

Variables en Python et JavaScript

En JavaScript et en Python, une affectation crée une association entre un nom et un objet stocké en mémoire. Cette association (parfois appelée référence ou pointeur) est l'adresse mémoire de l'objet.

Les variables ne sont jamais associées à d'autres variables mais toujours à un objet en mémoire. En CPython il est possible de récupérer cette adresse mémoire via id(object) (je ne connais pas d'équivalent en JavaScript) :

In [1]: a = b = 1

In [2]: print(a, '->', hex(id(a)), id(a) == id(b))
1 -> 0x101915fc0 True

In [3]: print(b, '->', hex(id(b)), id(a) == id(b))
1 -> 0x101915fc0 True

In [4]: b = 2

In [5]: print(a, '->', hex(id(a)), id(a) == id(b))
1 -> 0x101915fc0 False

In [6]: print(b, '->', hex(id(b)), id(a) == id(b))
2 -> 0x101915fe0 False

À la première ligne, 1 n'est pas copié à la fois dans a et b mais a et b partagent le même objet. Ensuite en réaffectant b on l'a réassociée vers l'addresse mémoire d'un nouvel objet 2.

1 et 2 sont des objets de type int, un type d'objet immuable. La différence entre des objets immuables et mutables (JavaScript ou Python) est fondamentale :

In [1]: a = b = {'foo': 1}

In [2]: print(a, '->', hex(id(a)), id(a) == id(b))
{'foo': 1} -> 0x10c594188 True

In [3]: print(b, '->', hex(id(b)), id(a) == id(b))
{'foo': 1} -> 0x10c594188 True

In [4]: a['foo'] = 2

In [5]: b
Out[5]: {'foo': 2}

In [6]: print(a, '->', hex(id(a)), id(a) == id(b))
{'foo': 2} -> 0x10c594188 True

In [7]: print(b, '->', hex(id(b)), id(a) == id(b))
{'foo': 2} -> 0x10c594188 True

Lors de la quatrième opération le dict est mué mais a et b le partagent toujours. Il n'y a pas eu de nouvelle affectation de a ni de b.

Les objets sont assignés/passés par partage d'objet (a.k.a. call by sharing ou call by object ou encore call by object reference).

Le call by sharing, un héritage de CLU

Le passage par partage d'objet est une stratégie d'évaluation apparue avec le langage CLU, parfois mentionnée dans les papiers sur JavaScript et Python (résumé d'un long thread de 2003).

Sa documentation est très claire :

Argument passing is defined in terms of assignment; the formal arguments of a routine are considered to be local variables of the routine and are initialized, by assignment, to the objects resulting from the evaluation of the argument expressions. We call the argument passing technique call by sharing, because the argument objects are shared between the caller and the called routine. The technique does not correspond to most traditional argument passing techniques (it is similar to argument passing in LISP). In particular it is not call by value because mutations of arguments performed by the called routine will be visible to the caller. And it is not call by reference because access is not given to the variables of the caller, but merely to certain objects.

Les paramètres/arguments passés aux fonctions sont toujours initialisés par assignement dans le scope de la fonction. La différence de traitement dépend ensuite du caractère mutable ou immuable de l'objet passé.

Pour aller plus loin