3222 votes

Comment passer une variable par référence ?

Les paramètres sont-ils transmis par référence ou par valeur ? Comment puis-je passer par référence pour que le code ci-dessous produise 'Changed' au lieu de 'Original' ?

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

31 votes

Pour une brève explication/clarification, voir la première réponse à la question suivante cette question de stackoverflow . Les chaînes de caractères étant immuables, elles ne seront pas modifiées et une nouvelle variable sera créée, de sorte que la variable "extérieure" aura toujours la même valeur.

11 votes

Le code de la réponse de BlairConrad est bon, mais l'explication fournie par DavidCournapeau et DarenThomas est correcte.

79 votes

Avant de lire la réponse choisie, pensez à lire ce court texte Les autres langues ont des "variables", Python a des "noms". . Pensez à des "noms" et à des "objets" plutôt qu'à des "variables" et à des "références" et vous devriez éviter beaucoup de problèmes similaires.

3453voto

Blair Conrad Points 56195

Les arguments sont transmis par affectation . La raison de cette décision est double :

  1. le paramètre transmis est en fait un référence à un objet (mais la référence est passée par valeur)
  2. certains types de données sont mutables, mais d'autres ne le sont pas.

Donc :

  • Si vous passez un mutable Mais si vous liez à nouveau la référence dans la méthode, la portée externe n'en saura rien, et une fois que vous aurez terminé, la référence externe pointera toujours vers l'objet original.

  • Si vous passez un immuable à une méthode, vous ne pouvez toujours pas relier la référence externe, et vous ne pouvez même pas muter l'objet.

Pour que cela soit encore plus clair, prenons quelques exemples.

Liste - un type mutable

Essayons de modifier la liste qui a été transmise à une méthode :

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Sortie :

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Puisque le paramètre transmis est une référence à outer_list et non une copie de celle-ci, nous pouvons utiliser les méthodes de la liste de mutation pour la modifier et faire en sorte que les changements soient reflétés dans la portée externe.

Voyons maintenant ce qui se passe lorsque nous essayons de modifier la référence qui a été transmise en tant que paramètre :

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Sortie :

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Depuis le the_list est transmis par valeur, l'affectation d'une nouvelle liste n'a aucun effet visible pour le code extérieur à la méthode. Le site the_list était une copie de la outer_list référence, et nous avions the_list pointent vers une nouvelle liste, mais il n'y avait aucun moyen de changer l'endroit où outer_list pointu.

String - un type immuable

C'est immuable, donc il n'y a rien que nous puissions faire pour changer le contenu de la chaîne de caractères

Maintenant, essayons de changer la référence

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Sortie :

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

Là encore, puisque le the_string est transmis par valeur, l'affectation d'une nouvelle chaîne de caractères n'a aucun effet visible pour le code extérieur à la méthode. Le site the_string était une copie de la outer_string référence, et nous avions the_string vers une nouvelle chaîne, mais il n'y avait aucun moyen de changer l'endroit où les outer_string pointu.

J'espère que cela clarifie un peu les choses.

EDITAR: Il a été noté que cela ne répond pas à la question que @David a posée à l'origine, "Y a-t-il quelque chose que je puisse faire pour passer la variable par référence réelle ?". Travaillons sur ce point.

Comment contourner ce problème ?

Comme le montre la réponse d'@Andrea, vous pouvez retourner la nouvelle valeur. Cela ne change pas la façon dont les données sont transmises, mais vous permet de récupérer les informations que vous souhaitez :

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Si vous voulez vraiment éviter d'utiliser une valeur de retour, vous pouvez créer une classe pour contenir votre valeur et la passer dans la fonction ou utiliser une classe existante, comme une liste :

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Bien que cela semble un peu lourd.

205 votes

C'est la même chose en C, quand vous passez "par référence" vous passez en fait par valeur la référence... Définissez "par référence" :P

122 votes

Je ne suis pas sûr de comprendre vos termes. J'ai quitté le jeu C depuis un certain temps, mais à l'époque où j'y étais, il n'y avait pas de "pass by reference" - on pouvait passer des choses, et c'était toujours pass by value, donc tout ce qui était dans la liste des paramètres était copié. Mais parfois, la chose était un pointeur, que l'on pouvait suivre jusqu'au morceau de mémoire (primitive, tableau, structure, etc.), mais on ne pouvait pas changer le pointeur qui était copié depuis la portée externe - quand on avait fini avec la fonction, le pointeur original pointait toujours vers la même adresse. Le C++ a introduit les références, qui se comportent différemment.

3 votes

@andrea, non ce n'est pas du tout comme C. Conrad a raison, mais les termes référence/valeur sont confus en python. C'est pourquoi vous devriez vraiment utiliser un autre terme (voir mon lien vers effbot pour une bonne explication)

866voto

Mark Ransom Points 132545

Le problème vient d'une mauvaise compréhension de ce que sont les variables en Python. Si vous êtes habitué à la plupart des langages traditionnels, vous avez un modèle mental de ce qui se passe dans la séquence suivante :

a = 1
a = 2

Vous croyez que a est un emplacement mémoire qui stocke la valeur 1 puis est mis à jour pour stocker la valeur 2 . Ce n'est pas ainsi que les choses fonctionnent en Python. Plutôt, a commence comme une référence à un objet avec la valeur 1 puis est réaffecté comme une référence à un objet avec la valeur 2 . Ces deux objets peuvent continuer à coexister même si a ne fait plus référence à la première ; en fait, elles peuvent être partagées par un nombre quelconque d'autres références dans le programme.

Lorsque vous appelez une fonction avec un paramètre, une nouvelle référence est créée qui fait référence à l'objet transmis. Cette référence est distincte de celle qui a été utilisée dans l'appel de la fonction. Il n'y a donc aucun moyen de mettre à jour cette référence pour qu'elle fasse référence à un nouvel objet. Dans votre exemple :

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variable est une référence à l'objet chaîne de caractères 'Original' . Lorsque vous appelez Change vous créez une deuxième référence var à l'objet. Dans la fonction, vous réassignez la référence var à un autre objet de type chaîne de caractères 'Changed' mais la référence self.variable est séparé et ne change pas.

La seule façon de contourner ce problème est de passer un objet mutable. Comme les deux références font référence au même objet, toute modification de l'objet est reflétée aux deux endroits.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

127 votes

Bonne explication succincte. Votre paragraphe "Quand vous appelez une fonction..." est l'une des meilleures explications que j'ai entendues de la phrase plutôt cryptique "Les paramètres des fonctions Python sont des références, passées par valeur". Je pense que si vous comprenez ce seul paragraphe, tout le reste prend un sens et s'ensuit comme une conclusion logique. Il suffit ensuite d'être conscient du moment où l'on crée un nouvel objet et de celui où l'on modifie un objet existant.

4 votes

Mais comment réaffecter la référence ? Je pensais que vous ne pouviez pas changer l'adresse de 'var' mais que votre chaîne "Changed" allait maintenant être stockée à l'adresse mémoire de 'var'. Votre description donne l'impression que les chaînes "Changed" et "Original" appartiennent à des endroits différents de la mémoire et qu'il suffit de changer l'adresse de 'var'. Est-ce exact ?

11 votes

@Glassjawed, je pense que vous avez compris. "Changed" et "Original" sont deux objets chaîne différents à des adresses mémoire différentes et "var" change de pointage de l'un à l'autre.

272voto

David Cournapeau Points 21956

Il ne s'agit ni d'un passage par valeur ni d'un passage par référence - c'est un appel par objet. Voir ceci, par Fredrik Lundh :

http://effbot.org/zone/call-by-object.htm

Voici une citation significative :

"...les variables [noms] sont no des objets ; ils ne peuvent pas être désignés par d'autres variables ou se référer à des objets".

Dans votre exemple, lorsque le Change est appelée - une espace de noms est créé pour elle ; et var devient un nom, dans cet espace de nom, pour l'objet chaîne de caractères 'Original' . Cet objet a alors un nom dans deux espaces de noms. Suivant, var = 'Changed' se lie à var en un nouvel objet de type chaîne de caractères, et l'espace de noms de la méthode oublie donc l'objet 'Original' . Enfin, cet espace de nom est oublié, et la chaîne de caractères 'Changed' avec elle.

25 votes

Je trouve qu'il est difficile d'acheter. Pour moi, c'est comme pour Java, les paramètres sont des pointeurs vers des objets en mémoire, et ces pointeurs sont transmis via la pile, ou les registres.

10 votes

Ce n'est pas comme java. L'un des cas où ce n'est pas la même chose est celui des objets immuables. Pensez à la fonction triviale lambda x : x. Appliquez-la pour x = [1, 2, 3] et x = (1, 2, 3). Dans le premier cas, la valeur renvoyée sera une copie de l'entrée, et identique dans le second cas.

33 votes

Non, c'est exactement comme la sémantique des objets de Java. Je ne suis pas sûr de ce que vous voulez dire par "Dans le premier cas, la valeur renvoyée sera une copie de l'entrée, et identique dans le second cas", mais cette affirmation semble tout à fait incorrecte.

218voto

Daren Thomas Points 26812

Pensez aux choses qui passent par affectation plutôt que par référence/par valeur. De cette façon, ce qui se passe est toujours clair, à condition que vous compreniez ce qui se passe pendant l'affectation normale.

Ainsi, lorsqu'on passe une liste à une fonction/méthode, la liste est affectée au nom du paramètre. L'ajout à la liste entraîne la modification de la liste. Réaffectation de la liste à l'intérieur de la fonction ne modifiera pas la liste originale, puisque :

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Comme les types immuables ne peuvent pas être modifiés, ils semblent comme être passé par valeur - passer un int dans une fonction signifie assigner l'int au paramètre de la fonction. Vous pouvez seulement réassigner ce paramètre, mais cela ne changera pas la valeur de la variable originale.

11 votes

À première vue, cette réponse semble éluder la question initiale. Après une deuxième lecture, je me suis rendu compte que cette réponse rend la question tout à fait claire. Un bon suivi de ce concept d'"attribution de nom" peut être trouvé ici : Codez comme un pythoniste : Python idiomatique

67voto

pepr Points 4263

Techniquement, Python utilise toujours les valeurs par référence . Je vais répéter mon autre réponse pour appuyer ma déclaration.

Python utilise toujours des valeurs de type pass-by-reference. Il n'y a pas d'exception. Toute affectation de variable implique la copie de la valeur de référence. Il n'y a pas d'exception. Toute variable est le nom lié à la valeur de référence. Toujours.

Vous pouvez considérer une valeur de référence comme l'adresse de l'objet cible. L'adresse est automatiquement déréférencée lorsqu'elle est utilisée. De cette façon, en travaillant avec la valeur de référence, il semble que vous travaillez directement avec l'objet cible. Mais il y a toujours une référence entre les deux, une étape de plus pour atteindre la cible.

Voici l'exemple qui prouve que Python utilise le passage par référence :

Illustrated example of passing the argument

Si l'argument a été transmis par valeur, le paramètre externe lst n'a pas pu être modifié. Les verts sont les objets cibles (le noir est la valeur stockée à l'intérieur, le rouge est le type d'objet), le jaune est la mémoire avec la valeur de référence à l'intérieur -- dessinée comme la flèche. La flèche bleue pleine est la valeur de référence qui a été transmise à la fonction (via le chemin de la flèche bleue pointillée). L'affreux jaune foncé est le dictionnaire interne. (En fait, il pourrait aussi être dessiné sous la forme d'une ellipse verte. La couleur et la forme indiquent seulement qu'il est interne).

Vous pouvez utiliser le id() pour connaître la valeur de référence (c'est-à-dire l'adresse de l'objet cible).

Dans les langages compilés, une variable est un espace mémoire capable de capturer la valeur d'un type. En Python, une variable est un nom (capturé en interne comme une chaîne de caractères) lié à la variable de référence qui détient la valeur de référence de l'objet cible. Le nom de la variable est la clé dans le dictionnaire interne, la partie valeur de cet élément du dictionnaire stocke la valeur de référence à la cible.

Les valeurs de référence sont cachées dans Python. Il n'y a pas de type utilisateur explicite pour stocker la valeur de référence. Cependant, vous pouvez utiliser un élément de liste (ou un élément dans tout autre type de conteneur approprié) comme variable de référence, car tous les conteneurs stockent les éléments également comme références aux objets cibles. En d'autres termes, les éléments ne sont pas contenus dans le conteneur, seules les références aux éléments le sont.

2 votes

En fait, cela confirme son passage par la valeur de référence. +1 pour cette réponse bien que l'exemple n'était pas bon.

45 votes

Inventer une nouvelle terminologie (telle que "passer par valeur de référence" ou "appeler par objet" n'est pas utile). "Call by (value|reference|name)" sont des termes standard. "référence" est un terme standard. Passer des références par valeur décrit avec précision le comportement de Python, de Java et d'une foule d'autres langages, en utilisant une terminologie standard.

6 votes

@cayhorstmann : Le problème est que Variable Python n'a pas le même sens terminologique que dans les autres langues. De cette façon, appel par référence ne convient pas bien ici. Aussi, comment faites-vous exactement définir le terme référence ? De manière informelle, la méthode Python pourrait être facilement décrite comme le passage de l'adresse de l'objet. Mais cela ne convient pas à une implémentation potentiellement distribuée de Python.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X