Clonage de variables de type composé

Objectifs

  • Savoir copier des valeurs en JavaScript et en python ;

  • Connaître la différence entre copie et copie récursive.

Mise en situation

Nous avons appris à copier une variable, et cela n'a rien de très compliqué. Cependant cela est valable pour les variables simples, et c'est une autre affaire quand il s'agit de variables composées, comme des enregistrements. Par exemple si l'on copie un tableau d'enregistrements, qui contiennent eux même des enregistrements, est-ce que les enregistrements imbriqués sont dupliqués ou est-ce que la copie du tableau ne fera que de simples références ? On sent bien que ici la copie des variables est un peu plus compliquée à appréhender.

ExempleUne erreur commune

Parfois on souhaite copier des valeurs entre différentes variables. Pour cela on utilise généralement l'affectation. Par exemple en Python :

1
tokens = [1, 2, 3, 4, 5]
2
numbers = tokens
3
print(numbers)
1
[1, 2, 3, 4, 5]

Néanmoins, si on modifie le « premier tableau », le « second » est aussi modifié :

1
tokens[2] = 98
2
print(numbers)
1
[1, 2, 98, 4, 5]

On obtient le même comportement en JavaScript :

1
let tokens = [1, 2, 3, 4, 5]
2
let numbers = tokens
3
tokens[2] = 98
4
console.log(numbers)
1
[1, 2, 98, 4, 5]

Pourquoi est-ce que cela se produit ? Comment y remédier ?

FondamentalExplication : référencement de valeur de type composé

Lors de l'affectation d'une variable par une autre, on recopie dans le cas du type composé la référence vers la valeur.

Deux variables référençant le même tableau après affectationInformations[1]

Ainsi, si on change la variable à l'aide d'une des deux variables, l'autre variable renverra la valeur mise à jour et non l'ancienne.

Modification d'un élément d'un tableau référencé par deux variablesInformations[2]

La valeur 3 n'est plus référencée : une nouvelle valeur 98 est créée, et référencée dans le tableau.

ExempleExplication de l'exemple précédent

On peut voir que les deux variables en Python référencent la même valeur :

1
print(id(tokens) == id(numbers))
1
True

On peut voir cela aussi cela en JavaScript avec le test d'égalité avec l'opérateur ===.

1
print(tokens === numbers)
1
true

FondamentalRéaliser une copie

Pour réaliser une copie d'une valeur d'un type composé il faut utiliser des fonctions dédiées qui s'occupent de réaliser la copie des variables de types primitifs.

ExempleRéaliser une copie en Python

En Python on utilise la fonction copy du module copy.

1
import copy
2
tokens = [1, 2, 3, 4, 5]
3
numbers = copy.copy(tokens)

numbers et tokens référencent deux valeurs différentes :

1
print(id(tokens) == id(numbers))
1
False

Ainsi, si on modifie la valeur d'un variable l'autre n'est pas impactée :

1
tokens[2] = 98
2
print(tokens, numbers)
1
[1, 2, 98, 4, 5], [1, 2, 3, 4, 5]

ExempleRéaliser une copie en JavaScript

En JavaScript, on utilise la méthode Object.assign() pour réaliser une copie.

1
let tokens = [1, 2, 3, 4, 5]
2
let numbers = Object.assign([], tokens)

Le comportement est identique à celui rencontré avec Python:

1
tokens[2] = 98
2
console.log(tokens, numbers)
1
[1, 2, 98, 4, 5], [1, 2, 3, 4, 5]

ComplémentCopie récursive de valeurs

Lors d'une copie, seules les premières valeurs superficielles sont copiées, ainsi dans le cas de structures imbriquées (comme un tableau de tableau), toutes les valeurs ne sont pas copiées.

Pour recopier entièrement toutes les valeurs présentes, il faut utiliser une copie récursive.

ExempleCopie récursive de valeurs en Python

En Python, on peut utiliser la fonction copy.deepcopy qui réalise une copie récursive de valeurs.

1
import copy
2
3
cours = {
4
  'id_cours': 1337,
5
  'nom_cours': 'IngDoc',
6
  'theme': 'Ingénierie Documentaire',
7
  'etudiants': [
8
    {
9
      'nom': 'Norris',
10
      'prenom': 'Chuck',
11
      'age': 73,
12
      'pays': 'USA'
13
    },
14
    {
15
      'nom': 'Doe',
16
      'prenom': 'Jane',
17
      'age': 45,
18
      'pays': 'Angleterre'
19
    }
20
  ]
21
}
22
23
copie_cours = copy.deepcopy(cours)

RemarqueCopie récursive de valeurs en JavaScript

En JavaScript (ES6), il n'est pas nativement possible de réaliser de copie récursive : il faut définir une fonction dédiée ou utiliser une bibliothèque spécialisée.

ExempleCopie récursive en JavaScript avec une fonction dédiée

On définit la fonction suivante permettant de réaliser une copie récursive :

1
const deepCopyFunction = (inObject) => {
2
  let outObject, value, key
3
4
  if (typeof inObject !== "object" || inObject === null) {
5
   // Retourne la valeur de inObject si ce n'est pas un objet    
6
   return inObject
7
  }
8
9
  // Crée un tableau ou un objet pour contenir les valeurs
10
  outObject = Array.isArray(inObject) ? [] : {}
11
12
  for (key in inObject) {
13
    value = inObject[key]
14
15
    // Copie récursivement les objets imbriquées, donc les tableaux
16
    outObject[key] = deepCopyFunction(value)
17
  }
18
19
  return outObject
20
}
1
const cours = {
2
  'id_cours': 1337,
3
  'nom_cours': 'IngDoc',
4
  'theme': 'Ingénierie Documentaire',
5
  'etudiants': [
6
    {
7
      'nom': 'Norris',
8
      'prenom': 'Chuck',
9
      'age': 73,
10
      'pays': 'USA'
11
    },
12
    {
13
      'nom': 'Doe',
14
      'prenom': 'Jane',
15
      'age': 45,
16
      'pays': 'Angleterre'
17
    }
18
  ]
19
}
20
21
const copie_cours = deepCopyFunction(cours)
22
cours.etudiants[0].id_cours = 1987
23
console.log(cours, copie_cours) 

Ici, seul l'identifiant (id) du premier cours est modifié.

1
{
2
  id_cours: 1987,
3
  nom_cours: 'IngDoc',
4
  theme: 'Ingénierie Documentaire',
5
  etudiants: [
6
    { nom: 'Norris', prenom: 'Chuck', age: 73, pays: 'USA' },
7
    { nom: 'Doe', prenom: 'Jane', age: 45, pays: 'Angleterre' }
8
  ]
9
} {
10
  id_cours: 1337,
11
  nom_cours: 'IngDoc',
12
  theme: 'Ingénierie Documentaire',
13
  etudiants: [
14
    { nom: 'Norris', prenom: 'Chuck', age: 73, pays: 'USA' },
15
    { nom: 'Doe', prenom: 'Jane', age: 45, pays: 'Angleterre' }
16
  ]
17
}

À retenir

Pour copier la valeur d'une variable dans une autre, il faut utiliser les fonctions de copie : copy.copy en python et Object.assign en JavaScript.

Pour réaliser une copie intégrale d'une valeur, il faut utiliser les fonctions ou syntaxes de copie récursive : copy.deepcopy en Python et mettre au point une fonction dédiée en JavaScript.