Comment passer une variable par référence?

2629

La documentation Python ne semble pas claire quant à savoir si les paramètres sont passés par référence ou par valeur, et le code suivant produit la valeur inchangée 'Original'

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

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

Puis-je faire quelque chose pour passer la variable par référence réelle?

David Sykes
la source
23
Pour une brève explication / clarification, voir la première réponse à cette question de stackoverflow . Comme les chaînes sont immuables, elles ne seront pas modifiées et une nouvelle variable sera créée, donc la variable "externe" a toujours la même valeur.
PhilS
10
Le code dans la réponse de BlairConrad est bon, mais l'explication fournie par DavidCournapeau et DarenThomas est correcte.
Ethan Furman
70
Avant de lire la réponse sélectionnée, pensez à lire ce court texte. Les autres langues ont des "variables", Python a des "noms" . Pensez aux "noms" et aux "objets" au lieu des "variables" et des "références" et vous devriez éviter beaucoup de problèmes similaires.
lqc
24
Pourquoi cela utilise-t-il inutilement une classe? Ce n'est pas Java: P
Peter R
2
une autre solution consiste à créer une «référence» d'encapsuleur comme celle-ci: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
robert

Réponses:

2832

Les arguments sont transmis par affectation . La raison derrière cela est double:

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

Donc:

  • Si vous passez un objet mutable dans une méthode, la méthode obtient une référence à ce même objet et vous pouvez le muter à votre plus grand plaisir, mais si vous reliez la référence dans la méthode, la portée externe n'en saura rien, et après vous avez terminé, la référence externe pointera toujours sur l'objet d'origine.

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

Pour le rendre encore plus clair, nous allons avoir quelques exemples.

Liste - un type mutable

Essayons de modifier la liste 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)

Production:

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

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

Voyons maintenant ce qui se passe lorsque nous essayons de modifier la référence 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)

Production:

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']

Étant donné que le the_listparamètre a été transmis par valeur, l'attribution d'une nouvelle liste n'a eu aucun effet que le code en dehors de la méthode pouvait voir. Le the_listétait une copie de la outer_listréférence, et nous avons eu le the_listpoint à une nouvelle liste, mais il n'y avait aucun moyen de changer l' endroit où outer_listpointe.

String - un type immuable

Il est immuable, donc nous ne pouvons rien faire pour changer le contenu de la chaîne

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)

Production:

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

Encore une fois, puisque le the_stringparamètre a été transmis par valeur, lui affecter une nouvelle chaîne n'a eu aucun effet que le code en dehors de la méthode pouvait voir. Le the_stringétait une copie de la outer_stringréférence, et nous avons eu le the_stringpoint d'une nouvelle chaîne, mais il n'y avait aucun moyen de changer l' endroit où outer_stringpointe.

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

EDIT: Il a été noté que cela ne répond pas à la question à laquelle @David avait initialement demandé: "Y a-t-il quelque chose que je puisse faire pour transmettre la variable par référence réelle?". Travaillons là-dessus.

Comment contourner cela?

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

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 vouliez vraiment éviter d'utiliser une valeur de retour, vous pouvez créer une classe pour conserver votre valeur et la transmettre à 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.

Blair Conrad
la source
165
Ensuite, c'est la même chose en C, lorsque vous passez "par référence", vous passez en fait par valeur la référence ... Définissez "par référence": P
Andrea Ambu
104
Je ne suis pas sûr de comprendre vos termes. Je suis hors du jeu C depuis un certain temps, mais à l'époque où j'y étais, il n'y avait pas de "passe par référence" - vous pouviez passer des choses, et c'était toujours par valeur, donc tout ce qui était dans la liste des paramètres a été copié. Mais parfois, la chose était un pointeur, que l'on pouvait suivre jusqu'à la pièce de mémoire (primitive, tableau, struct, peu importe), mais vous ne pouviez pas changer le pointeur qui a été copié à partir de la portée externe - lorsque vous avez terminé avec la fonction , le pointeur d'origine pointait toujours vers la même adresse. C ++ a introduit des références, qui se sont comportées différemment.
Blair Conrad le
34
@Zac Bowling Je ne comprends pas vraiment ce que vous dites est pertinent, dans un sens pratique, pour cette réponse. Si un nouveau venu Python voulait savoir comment passer par ref / val, alors la leçon à retenir de cette réponse est: 1- Vous pouvez utiliser la référence qu'une fonction reçoit comme argument, pour modifier la valeur `` extérieure '' d'une variable, aussi longtemps car vous ne réaffectez pas le paramètre pour faire référence à un nouvel objet. 2- L' affectation à un type immuable créera toujours un nouvel objet, ce qui casse la référence que vous aviez à la variable extérieure.
Cam Jackson le
11
@CamJackson, vous avez besoin d'un meilleur exemple - les nombres sont également des objets immuables en Python. En outre, ne serait-il pas vrai de dire que toute affectation sans indice sur le côté gauche des égaux réattribuera le nom à un nouvel objet, qu'il soit immuable ou non? def Foo(alist): alist = [1,2,3]ne pas modifier le contenu de la liste du point de vue des appelants.
Mark Ransom
59
-1. Le code affiché est bon, l'explication de la façon dont est complètement faux. Voir les réponses de DavidCournapeau ou DarenThomas pour des explications correctes sur pourquoi.
Ethan Furman
685

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

a = 1
a = 2

Vous pensez que ac'est un emplacement de 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. aCommence plutôt comme référence à un objet avec la valeur 1, puis est réaffecté comme référence à un objet avec la valeur 2. Ces deux objets peuvent continuer à coexister même s'ils ane font plus référence au premier; en fait, ils peuvent être partagés par un certain nombre d'autres références au sein du 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. Elle est distincte de la référence qui a été utilisée dans l'appel de fonction, il n'y a donc aucun moyen de mettre à jour cette référence et de la faire 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.variableest une référence à l'objet chaîne 'Original'. Lorsque vous appelez, Changevous créez une deuxième référence varà l'objet. Dans la fonction, vous réaffectez la référence varà un autre objet chaîne 'Changed', mais la référence self.variableest distincte et ne change pas.

La seule façon de contourner cela est de passer un objet mutable. Étant donné que les deux références font référence au même objet, toutes les modifications apportées à l'objet sont reflétées aux deux endroits.

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

def Change(self, var):
    var[0] = 'Changed'
Mark Ransom
la source
106
Bonne explication succincte. Votre paragraphe "Lorsque vous appelez une fonction ..." est l'une des meilleures explications que j'ai entendues de la phrase plutôt cryptique selon laquelle "les paramètres de la fonction Python sont des références, passées par valeur". Je pense que si vous comprenez ce paragraphe seul, tout le reste est tout à fait logique et découle comme une conclusion logique à partir de là. Ensuite, il vous suffit de savoir quand vous créez un nouvel objet et lorsque vous modifiez un objet existant.
Cam Jackson
3
Mais comment pouvez-vous 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 dans l'adresse mémoire de 'var'. Votre description donne l'impression que "Changé" et "Original" appartiennent à des endroits différents dans la mémoire à la place et vous passez simplement "var" à une adresse différente. Est-ce exact?
Glassjawed
9
@Glassjawed, je pense que vous comprenez. «Modifié» et «Original» sont deux objets chaîne différents à des adresses mémoire différentes et «var» passe de pointant à l'un à pointant à l'autre.
Mark Ransom
l'utilisation de la fonction id () aide à clarifier les choses, car elle indique clairement quand Python crée un nouvel objet (donc je pense, de toute façon).
Tim Richardson
1
@MinhTran en termes simples, une référence est quelque chose qui "se réfère" à un objet. La représentation physique de cela est très probablement un pointeur, mais c'est simplement un détail d'implémentation. C'est vraiment une notion abstraite dans l'âme.
Mark Ransom
329

J'ai trouvé les autres réponses assez longues et compliquées, j'ai donc créé ce schéma simple pour expliquer la façon dont Python traite les variables et les paramètres. entrez la description de l'image ici

Zenadix
la source
2
charmant, il est facile de repérer la différence subtile qu'il existe une affectation intermédiaire, pas évidente pour un spectateur occasionnel. +1
user22866
9
Peu importe que A soit mutable ou non. Si vous attribuez quelque chose de différent à B, A ne change pas . Si un objet est mutable, vous pouvez le muter, bien sûr. Mais cela n'a rien à voir avec l'affectation directement à un nom ..
Martijn Pieters
1
@Martijn Vous avez raison. J'ai supprimé la partie de la réponse qui mentionne la mutabilité. Je ne pense pas que cela puisse devenir plus simple maintenant.
Zenadix
4
Merci pour la mise à jour, bien mieux! Ce qui déroute la plupart des gens, c'est l'affectation à un abonnement; par exemple B[0] = 2, par rapport à l' affectation directe, B = 2.
Martijn Pieters
11
"A est affecté à B." N'est-ce pas ambigu? Je pense qu'en anglais ordinaire, cela peut signifier soit A=Bou B=A.
Hatshepsut
240

Ce n'est ni un passage par valeur ni 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 importante:

"... les variables [noms] ne sont pas des objets; elles ne peuvent pas être dénotées par d'autres variables ni référencées par des objets."

Dans votre exemple, lorsque la Changeméthode est appelée - un espace de noms est créé pour elle; et vardevient un nom, dans cet espace de noms, pour l'objet chaîne 'Original'. Cet objet a ensuite un nom dans deux espaces de noms. Ensuite, var = 'Changed'se lie varà un nouvel objet chaîne, et donc l'espace de noms de la méthode oublie 'Original'. Enfin, cet espace de noms est oublié et la chaîne 'Changed'avec lui.

David Cournapeau
la source
21
J'ai du mal à acheter. Pour moi, tout comme Java, les paramètres sont des pointeurs vers des objets en mémoire, et ces pointeurs sont transmis via la pile ou les registres.
Luciano
10
Ce n'est pas comme Java. Un des cas où ce n'est pas la même chose est les objets immuables. Pensez à la fonction triviale lambda x: x. Appliquez ceci pour x = [1, 2, 3] et x = (1, 2, 3). Dans le premier cas, la valeur retournée sera une copie de l'entrée et identique dans le second cas.
David Cournapeau
26
Non, c'est exactement comme la sémantique de Java pour les objets. Je ne suis pas sûr de ce que vous entendez par "Dans le premier cas, la valeur retournée sera une copie de l'entrée, et identique dans le second cas." mais cette déclaration semble être tout à fait incorrecte.
Mike Graham
23
C'est exactement la même chose qu'en Java. Les références aux objets sont passées par valeur. Quiconque pense différemment devrait attacher le code Python pour une swapfonction qui peut échanger deux références, comme ceci: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann
24
C'est exactement la même chose que Java lorsque vous passez des objets en Java. Cependant, Java possède également des primitives, qui sont transmises en copiant la valeur de la primitive. Ils diffèrent donc dans ce cas.
Claudiu
174

Pensez aux trucs transmis par affectation plutôt que par référence / par valeur. De cette façon, il est toujours clair, ce qui se passe tant que vous comprenez ce qui se passe pendant la mission normale.

Ainsi, lors du passage d'une liste à une fonction / méthode, la liste est affectée au nom du paramètre. L'ajout à la liste entraînera la modification de la liste. Réaffecter la liste à l' intérieur de la fonction ne changera pas la liste d'origine, car:

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 être passés par valeur - passer un int dans une fonction signifie assigner l'int au paramètre de la fonction. Vous ne pouvez que réaffecter cela, mais cela ne changera pas la valeur des variables d'origine.

Daren Thomas
la source
3
À première vue, cette réponse semble contourner la question d'origine. Après une deuxième lecture, je me suis rendu compte que cela rend la chose assez claire. Un bon suivi de ce concept "d'attribution de nom" peut être trouvé ici: Code Like a Pythonista: Idiomatic Python
Christian Groleau
65

Effbot (alias Fredrik Lundh) a décrit le style de passage des variables de Python comme un appel par objet: http://effbot.org/zone/call-by-object.htm

Les objets sont alloués sur le tas et les pointeurs vers eux peuvent être transmis n'importe où.

  • Lorsque vous effectuez une affectation telle que x = 1000, une entrée de dictionnaire est créée qui mappe la chaîne "x" dans l'espace de noms actuel vers un pointeur sur l'objet entier contenant mille.

  • Lorsque vous mettez à jour "x" avec x = 2000, un nouvel objet entier est créé et le dictionnaire est mis à jour pour pointer vers le nouvel objet. L'ancien objet mille est inchangé (et peut être vivant ou non selon que quelque chose d'autre se réfère à l'objet).

  • Lorsque vous effectuez une nouvelle affectation telle que y = x, une nouvelle entrée de dictionnaire «y» est créée qui pointe vers le même objet que l'entrée pour «x».

  • Les objets comme les chaînes et les entiers sont immuables . Cela signifie simplement qu'aucune méthode ne peut modifier l'objet après sa création. Par exemple, une fois l'objet entier mille créé, il ne changera jamais. Les mathématiques sont effectuées en créant de nouveaux objets entiers.

  • Les objets comme les listes sont mutables . Cela signifie que le contenu de l'objet peut être modifié par tout élément pointant vers l'objet. Par exemple, x = []; y = x; x.append(10); print ys'imprime [10]. La liste vide a été créée. "X" et "y" pointent tous deux vers la même liste. La méthode append mute (met à jour) l'objet liste (comme l'ajout d'un enregistrement à une base de données) et le résultat est visible à la fois par "x" et "y" (tout comme une mise à jour de base de données serait visible pour chaque connexion à cette base de données).

J'espère que cela clarifie le problème pour vous.

Raymond Hettinger
la source
2
J'apprécie vraiment d'apprendre cela à partir d'un développeur. Est-il vrai que la id()fonction renvoie la valeur du pointeur (référence de l'objet), comme le suggère la réponse de Pepr?
Honest Abe
4
@HonestAbe Oui, dans CPython, l' id () renvoie l'adresse. Mais dans d'autres pythons tels que PyPy et Jython, l' id () n'est qu'un identifiant d'objet unique.
Raymond Hettinger
59

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

Python utilise toujours des valeurs passe-par-référence. Il n'y a aucune exception. Toute affectation de variable signifie la copie de la valeur de référence. Pas 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, un pas de plus pour sauter à la cible.

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

Exemple illustré de passer l'argument

Si l'argument a été passé par valeur, l'extérieur lstne peut pas être modifié. Le vert 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 continue est la valeur de référence qui a été transmise à la fonction (via le chemin de la flèche bleue en pointillés). Le laid jaune foncé est le dictionnaire interne. (En fait, il pourrait également être dessiné sous la forme d'une ellipse verte. La couleur et la forme indiquent uniquement qu'elles sont internes.)

Vous pouvez utiliser la id()fonction intégrée pour savoir quelle est 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 du type. En Python, une variable est un nom (capturé en interne sous forme de chaîne) lié à la variable de référence qui contient la valeur de référence de l'objet cible. Le nom de la variable est la clé du dictionnaire interne, la partie valeur de cet élément de dictionnaire stocke la valeur de référence sur la cible.

Les valeurs de référence sont masquées dans Python. Il n'y a pas de type d'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 également les éléments en tant que références aux objets cibles. En d'autres termes, les éléments ne sont en fait pas contenus à l'intérieur du conteneur - seules les références aux éléments le sont.

pepr
la source
1
En fait, cela est confirmé son passage par la valeur de référence. +1 pour cette réponse bien que l'exemple ne soit pas bon.
BugShotGG
30
Inventer une nouvelle terminologie (telle que «passer par valeur de référence» ou «appeler par objet» n'est pas utile). "Appel par (valeur | référence | nom)" sont des termes standard. "référence" est un terme standard. La transmission de références par valeur décrit avec précision le comportement de Python, Java et d'une foule d'autres langages, en utilisant une terminologie standard.
cayhorstmann
4
@cayhorstmann: Le problème est que la variable Python n'a pas la même signification terminologique que dans d'autres langages. De cette façon, l' appel par référence ne convient pas bien ici. De plus, comment définissez -vous exactement le terme référence ? De manière informelle, la méthode Python pourrait facilement être décrite comme passant l'adresse de l'objet. Mais cela ne correspond pas à une implémentation potentiellement distribuée de Python.
pepr
1
J'aime cette réponse, mais vous pourriez vous demander si l'exemple aide ou bloque vraiment le flux. De plus, si vous remplacez «valeur de référence» par «référence d'objet», vous utiliserez une terminologie que nous pourrions considérer comme «officielle», comme on le voit ici: Définir les fonctions
Honest Abe
2
Il y a une note de bas de page indiquée à la fin de cette citation, qui se lit comme suit: "En fait, l' appel par référence d'objet serait une meilleure description, car si un objet mutable est passé, l'appelant verra toutes les modifications que l'appelé lui apportera ... " Je suis d'accord avec vous que la confusion est causée en essayant d'adapter la terminologie établie avec d'autres langues. Mis à part la sémantique, les choses à comprendre sont: les dictionnaires / espaces de noms, les opérations de liaison de noms et la relation nom → pointeur → objet (comme vous le savez déjà).
Honest Abe
56

Il n'y a pas de variables en Python

La clé pour comprendre le passage des paramètres est d'arrêter de penser aux "variables". Il y a des noms et des objets en Python et ensemble, ils apparaissent comme des variables, mais il est utile de toujours distinguer les trois.

  1. Python a des noms et des objets.
  2. L'affectation lie un nom à un objet.
  3. Passer un argument dans une fonction lie également un nom (le nom du paramètre de la fonction) à un objet.

C'est tout ce qu'il y a à faire. La mutabilité n'est pas pertinente pour cette question.

Exemple:

a = 1

Cela lie le nom aà un objet de type entier qui contient la valeur 1.

b = x

Cela lie le nom bau même objet auquel le nom xest actuellement lié. Après, le nom bn'a plus rien à voir avec le nom x.

Voir les sections 3.1 et 4.2 dans la référence du langage Python 3.

Comment lire l'exemple de la question

Dans le code affiché dans la question, l'instruction self.Change(self.variable)lie le nom var(dans la portée de la fonction Change) à l'objet qui contient la valeur 'Original'et l'affectation var = 'Changed'(dans le corps de la fonction Change) attribue à nouveau ce même nom: à un autre objet (cela se produit contenir également une chaîne mais aurait pu être autre chose).

Comment passer par référence

Donc, si la chose que vous voulez changer est un objet mutable, il n'y a pas de problème, car tout est effectivement transmis par référence.

S'il s'agit d'un objet immuable (par exemple un booléen, un nombre, une chaîne), le chemin à parcourir est de l'envelopper dans un objet mutable.
La solution rapide et sale pour cela est une liste à un élément (au lieu de self.variable, passer [self.variable]et modifier la fonction var[0]).
L' approche la plus pythonique serait d'introduire une classe triviale à un attribut. La fonction reçoit une instance de la classe et manipule l'attribut.

Lutz Prechelt
la source
20
"Python n'a pas de variables" est un slogan stupide et déroutant, et je souhaite vraiment que les gens cessent de le dire ... :( Le reste de cette réponse est bon!
Ned Batchelder
9
Cela peut être choquant, mais ce n'est pas idiot. Et je ne pense pas que cela soit déroutant non plus: cela ouvre, espérons-le, l'esprit de la destinataire pour l'explication qui vient et la place dans une attitude utile "Je me demande ce qu'ils ont au lieu de variables". (Oui, votre kilométrage peut varier.)
Lutz Prechelt
13
diriez-vous également que Javascript n'a pas de variables? Ils fonctionnent de la même manière que Python. Aussi, Java, Ruby, PHP, .... Je pense qu'une meilleure technique d'enseignement est, "les variables de Python fonctionnent différemment de celles de C."
Ned Batchelder
9
Oui, Java a des variables. Il en va de même pour Python, et JavaScript, Ruby, PHP, etc. Vous ne diriez pas en Java qui intdéclare une variable, mais Integerne le fait pas. Ils déclarent tous deux des variables. La Integervariable est un objet, la intvariable est une primitive. À titre d'exemple, vous avez démontré le fonctionnement de vos variables en les montrant a = 1; b = a; a++ # doesn't modify b. C'est également vrai en Python (en utilisant += 1car il n'y ++en a pas en Python)!
Ned Batchelder
1
Le concept de "variable" est complexe et souvent vague: une variable est un conteneur pour une valeur, identifiée par un nom. En Python, les valeurs sont des objets, les conteneurs sont des objets (voyez le problème?) Et les noms sont en fait des choses distinctes. Je pense qu'il est beaucoup plus difficile d'obtenir une compréhension précise des variables de cette manière. L'explication des noms et des objets semble plus difficile, mais est en fait plus simple.
Lutz Prechelt
43

Une astuce simple que j'utilise normalement consiste à simplement l'encapsuler dans une liste:

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

variable = ['Original']
self.Change(variable)      
print variable[0]

(Oui, je sais que cela peut être gênant, mais parfois c'est assez simple pour le faire.)

AmanicA
la source
7
+1 pour une petite quantité de texte donnant la solution de contournement essentielle au problème de Python n'ayant pas de passe-par-référence. (En tant que commentaire / question de suivi qui tient ici aussi bien que n'importe où sur cette page: il n'est pas clair pour moi pourquoi python ne peut pas fournir un mot clé "ref" comme C # le fait, qui enveloppe simplement l'argument de l'appelant dans une liste comme ceci, et traiter les références à l'argument dans la fonction comme le 0ème élément de la liste.)
M Katz
5
Agréable. Pour passer par ref, enveloppez dans [].
Justas
38

(modifier - Blair a mis à jour sa réponse extrêmement populaire pour qu'elle soit maintenant exacte)

Je pense qu'il est important de noter que le poste actuel avec le plus de votes (par Blair Conrad), tout en étant correct en ce qui concerne son résultat, est trompeur et est à la limite incorrect sur la base de ses définitions. Bien qu'il existe de nombreux langages (comme C) qui permettent à l'utilisateur de passer par référence ou de passer par valeur, Python n'en fait pas partie.

La réponse de David Cournapeau pointe vers la vraie réponse et explique pourquoi le comportement dans le post de Blair Conrad semble être correct alors que les définitions ne le sont pas.

Dans la mesure où Python est transmis par valeur, tous les langages sont transmis par valeur car certaines données (que ce soit une "valeur" ou une "référence") doivent être envoyées. Cependant, cela ne signifie pas que Python est passé par valeur dans le sens où un programmeur C y penserait.

Si vous voulez le comportement, la réponse de Blair Conrad est très bien. Mais si vous voulez savoir les raisons pour lesquelles Python n'est ni passé par valeur ni passé par référence, lisez la réponse de David Cournapeau.

KobeJohn
la source
4
Il n'est tout simplement pas vrai que toutes les langues sont appelées par valeur. En C ++ ou Pascal (et sûrement beaucoup d'autres que je ne connais pas), vous avez appel par référence. Par exemple, en C ++, void swap(int& x, int& y) { int temp = x; x = y; y = temp; }permutera les variables qui lui sont transmises. En Pascal, vous utilisez à la varplace de &.
cayhorstmann
2
Je pensais avoir répondu à cela il y a longtemps mais je ne le vois pas. Pour être complet - Cayhorstmann a mal compris ma réponse. Je ne disais pas que tout est appelé par valeur dans les termes que la plupart des gens apprennent en premier sur C / C ++ . C'était simplement qu'une certaine valeur était transmise (valeur, nom, pointeur, etc.) et que les termes utilisés dans la réponse originale de Blair étaient inexacts.
KobeJohn
24

Vous avez ici de très bonnes réponses.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )
bobobobo
la source
2
oui, cependant si vous faites x = [2, 4, 4, 5, 5], y = x, X [0] = 1, imprimez x # [1, 4, 4, 5, 5] imprimez y # [1 , 4, 4, 5, 5]
laycat
19

Dans ce cas, la variable intitulée vardans la méthode Changese voit attribuer une référence à self.variable, et vous affectez immédiatement une chaîne à var. Cela ne pointe plus self.variable. L'extrait de code suivant montre ce qui se passerait si vous modifiez la structure de données pointée par varet self.variable, dans ce cas, une liste:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

Je suis sûr que quelqu'un d'autre pourrait clarifier cela davantage.

Mike Mazur
la source
18

Le schéma de pass-by-assignation de Python n'est pas tout à fait le même que l'option de paramètres de référence de C ++, mais il se révèle être très similaire au modèle de passage d'arguments du langage C (et d'autres) dans la pratique:

  • Les arguments immuables sont effectivement transmis « par valeur ». Les objets tels que les entiers et les chaînes sont passés par référence d'objet plutôt que par copie, mais comme vous ne pouvez pas modifier les objets immuables en place de toute façon, l'effet ressemble beaucoup à la création d'une copie.
  • Les arguments mutables sont effectivement passés « par pointeur ». Les objets tels que les listes et les dictionnaires sont également passés par référence à un objet, ce qui est similaire à la façon dont C passe les tableaux en tant que pointeurs - les objets modifiables peuvent être modifiés sur place dans la fonction, un peu comme les tableaux C.
ajknzhol
la source
16

Comme vous pouvez le dire, vous devez avoir un objet modifiable, mais laissez-moi vous suggérer de vérifier les variables globales car elles peuvent vous aider ou même résoudre ce genre de problème!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

exemple:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2
Nuno Aniceto
la source
5
J'ai été tenté de poster une réponse similaire - l'interrogateur d'origine ne savait peut-être pas que ce qu'il voulait était en fait d'utiliser une variable globale, partagée entre les fonctions. Voici le lien que j'aurais partagé: stackoverflow.com/questions/423379/… En réponse à @Tim, Stack Overflow n'est pas seulement un site de questions et réponses, c'est un vaste référentiel de connaissances de référence qui ne fait que se renforcer et se nuancer. comme un wiki- actif avec plus d'entrée.
Max P Magee
13

Beaucoup d'idées dans les réponses ici, mais je pense qu'un point supplémentaire n'est pas clairement mentionné ici explicitement. Citant de la documentation python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"En Python, les variables qui ne sont référencées qu'à l'intérieur d'une fonction sont implicitement globales. Si une variable reçoit une nouvelle valeur n'importe où dans le corps de la fonction, elle est supposée être locale. Si une variable se voit attribuer une nouvelle valeur à l'intérieur de la fonction, la variable est implicitement locale, et vous devez la déclarer explicitement comme "globale". Bien qu'un peu surprenant au début, un moment de réflexion l'explique. D'une part, l'exigence de global pour les variables affectées fournit une barre contre les effets secondaires involontaires. d'autre part, si global était requis pour toutes les références globales, vous utiliseriez globalement tout le temps. Vous devriez déclarer comme global chaque référence à une fonction intégrée ou à un composant d'un module importé. Cet encombrement irait à l'encontre de l'utilité de la déclaration mondiale pour identifier les effets secondaires. "

Même lors du passage d'un objet modifiable à une fonction, cela s'applique toujours. Et pour moi, cela explique clairement la raison de la différence de comportement entre l'attribution à l'objet et l'action sur l'objet dans la fonction.

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

donne:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

L'affectation à une variable globale qui n'est pas déclarée globale crée donc un nouvel objet local et rompt le lien avec l'objet d'origine.

Joop
la source
10

Voici l'explication simple (j'espère) du concept pass by objectutilisé en Python.
Chaque fois que vous passez un objet à la fonction, l'objet lui-même est passé (l'objet en Python est en fait ce que vous appelleriez une valeur dans d'autres langages de programmation) et non la référence à cet objet. En d'autres termes, lorsque vous appelez:

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

L'objet réel - [0, 1] (qui serait appelé une valeur dans d'autres langages de programmation) est passé. Donc, en fait, la fonction change_meessaiera de faire quelque chose comme:

[0, 1] = [1, 2, 3]

ce qui ne changera évidemment pas l'objet passé à la fonction. Si la fonction ressemblait à ceci:

def change_me(list):
   list.append(2)

Ensuite, l'appel entraînerait:

[0, 1].append(2)

ce qui va évidemment changer l'objet. Cette réponse l' explique bien.

matino
la source
2
Le problème est que l'affectation fait autre chose que ce que vous attendez. Les list = [1, 2, 3]causes de réutilisant les listnom pour quelque chose d' autre et forgeting l'objet à l' origine passé. Cependant, vous pouvez essayer list[:] = [1, 2, 3](au listfait, le nom est incorrect pour une variable. Penser à [0, 1] = [1, 2, 3]un non-sens complet. Quoi qu'il en soit, que signifie, selon vous, que l'objet lui-même est passé ? Qu'est-ce qui est copié dans la fonction à votre avis?
Pepr
Les objets @pepr ne sont pas des littéraux. Ce sont des objets. La seule façon d'en parler est de leur donner des noms. C'est pourquoi c'est si simple une fois que vous le comprenez, mais extrêmement compliqué à expliquer. :-)
Veky
@Veky: J'en suis conscient. Quoi qu'il en soit, le littéral de liste est converti en l'objet liste. En fait, n'importe quel objet en Python peut exister sans nom, et il peut être utilisé même sans nom. Et vous pouvez les considérer comme des objets anonymes. Pensez aux objets comme étant les éléments d'une liste. Ils n'ont pas besoin d'un nom. Vous pouvez y accéder via l'indexation ou l'itération de la liste. Quoi qu'il en soit, j'insiste sur le fait qu'il [0, 1] = [1, 2, 3]s'agit simplement d'un mauvais exemple. Il n'y a rien de tel en Python.
pepr
@pepr: Je ne veux pas nécessairement dire des noms en définition Python, juste des noms ordinaires. Bien sûr, alist[2]compte comme nom d'un troisième élément d'alist. Mais je pense que j'ai mal compris quel était votre problème. :-)
Veky
Argh. Mon anglais est évidemment bien pire que mon Python. :-) Je vais réessayer. Je viens de dire que vous devez donner un nom à l'objet juste pour en parler. Par "noms", je ne voulais pas dire "noms tels que définis par Python". Je connais les mécanismes Python, ne vous inquiétez pas.
Veky
8

Mis à part toutes les excellentes explications sur la façon dont ces choses fonctionnent en Python, je ne vois pas de suggestion simple pour le problème. Comme vous semblez créer des objets et des instances, la manière pythonique de gérer les variables d'instance et de les modifier est la suivante:

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

    def Change(self):
        self.variable = 'Changed'

Dans les méthodes d'instance, vous vous référez normalement à selfpour accéder aux attributs d'instance. Il est normal de définir des attributs d'instance dans __init__et de les lire ou de les modifier dans les méthodes d'instance. C'est aussi pourquoi vous passez également selfle premier argument def Change.

Une autre solution serait de créer une méthode statique comme celle-ci:

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

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var
Dolf Andringa
la source
6

Il y a une petite astuce pour passer un objet par référence, même si le langage ne le permet pas. Cela fonctionne aussi en Java, c'est la liste avec un élément. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

C'est un vilain hack, mais ça marche. ;-P

itmuckel
la source
pest une référence à un objet liste mutable qui à son tour stocke l'objet obj. La référence «p» est transmise changeRef. À l'intérieur changeRef, une nouvelle référence est créée (la nouvelle référence est appelée ref) qui pointe vers le même objet de liste vers lequel ppointe. Mais comme les listes sont modifiables, les modifications apportées à la liste sont visibles par les deux références. Dans ce cas, vous avez utilisé la refréférence pour modifier l'objet à l'index 0 afin qu'il stocke ensuite l' PassByReference('Michael')objet. La modification de l'objet liste a été effectuée à l'aide de refmais cette modification est visible par p.
Minh Tran
Alors maintenant, les références pet les refpointer vers un objet de la liste qui stocke l'objet unique, PassByReference('Michael'). Il s'ensuit donc que p[0].namerevient Michael. Bien sûr, il refest désormais hors de portée et peut être récupéré mais tout de même.
Minh Tran
Cependant, vous n'avez pas modifié la variable d'instance privée name, de l' PassByReferenceobjet d' origine associé à la référence obj. En fait, obj.namereviendra Peter. Les commentaires susmentionnés supposent la définition Mark Ransomdonnée.
Minh Tran
En fait, je ne suis pas d'accord pour dire que c'est un hack (ce que je veux dire pour désigner quelque chose qui fonctionne mais pour des raisons inconnues, non testées ou involontaires par l'implémenteur). Vous avez simplement remplacé un PassByReferenceobjet par un autre PassByReferenceobjet dans votre liste et fait référence au dernier des deux objets.
Minh Tran
5

J'ai utilisé la méthode suivante pour convertir rapidement quelques codes Fortran en Python. Certes, ce n'est pas un renvoi comme la question d'origine a été posée, mais c'est un simple contournement dans certains cas.

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c
Brad Porter
la source
Oui, cela résout également le «passage par référence» dans mon cas d'utilisation. J'ai une fonction qui nettoie essentiellement les valeurs dans un dict, puis retourne le dict. Cependant, lors du nettoyage, il peut devenir évident qu'une reconstruction d'une partie du système est nécessaire. Par conséquent, la fonction doit non seulement renvoyer le nettoyé, dictmais également être en mesure de signaler la reconstruction. J'ai essayé de passer un boolpar référence, mais cela ne fonctionne pas. Déterminant comment résoudre ce problème, j'ai trouvé que votre solution (renvoyant essentiellement un tuple) fonctionnait mieux tout en n'étant pas du tout un hack / solution de contournement (IMHO).
kasimir
3

Alors que passer par référence n'est rien qui s'intègre bien dans python et devrait être rarement utilisé, il existe des solutions de contournement qui peuvent réellement fonctionner pour obtenir l'objet actuellement assigné à une variable locale ou même réaffecter une variable locale à l'intérieur d'une fonction appelée.

L'idée de base est d'avoir une fonction qui peut faire cet accès et qui peut être passée en tant qu'objet dans d'autres fonctions ou stockée dans une classe.

Une façon consiste à utiliser global(pour les variables globales) ou nonlocal(pour les variables locales dans une fonction) dans une fonction wrapper.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

La même idée fonctionne pour lire et deldéfinir une variable.

Pour la lecture, il y a même une manière plus courte de simplement utiliser lambda: xqui retourne un appelable qui, lorsqu'il est appelé, retourne la valeur actuelle de x. C'est un peu comme "l'appel par le nom" utilisé dans les langues d'un passé lointain.

Passer 3 wrappers pour accéder à une variable est un peu compliqué, donc ceux-ci peuvent être encapsulés dans une classe qui a un attribut proxy:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

La prise en charge de la "réflexion" Pythons permet d'obtenir un objet capable de réaffecter un nom / une variable dans une portée donnée sans définir explicitement de fonctions dans cette portée:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Ici, la ByRefclasse encapsule un accès au dictionnaire. Ainsi, l'accès aux attributs wrappedest traduit en un accès aux éléments dans le dictionnaire passé. En passant le résultat de la commande intégrée localset le nom d'une variable locale, cela finit par accéder à une variable locale. La documentation de python à partir de 3.5 indique que le changement de dictionnaire pourrait ne pas fonctionner mais cela semble fonctionner pour moi.

texthell
la source
3

étant donné la façon dont python gère les valeurs et les références à celles-ci, la seule façon de référencer un attribut d'instance arbitraire est par son nom:

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

dans le code réel, vous ajouteriez, bien sûr, la vérification des erreurs lors de la recherche de dict.

mARK bLOORE
la source
3

Étant donné que les dictionnaires sont transmis par référence, vous pouvez utiliser une variable dict pour stocker toutes les valeurs référencées à l'intérieur.

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
Liakos
la source
3

Pass-By-Reference en Python est assez différent du concept de pass by reference en C ++ / Java.

  • Java & C #: les types primitifs (chaîne incluse) passent par valeur (copie), le type de référence est passé par référence (copie d'adresse) de sorte que toutes les modifications apportées au paramètre dans la fonction appelée sont visibles pour l'appelant.
  • C ++: Les deux passes par référence ou passe par valeur sont autorisées. Si un paramètre est transmis par référence, vous pouvez le modifier ou non selon que le paramètre a été transmis en tant que const ou non. Cependant, const ou non, le paramètre conserve la référence à l'objet et la référence ne peut pas être affectée pour pointer vers un autre objet dans la fonction appelée.
  • Python: Python est une «référence d'objet par objet», dont on dit souvent: «Les références d'objet sont transmises par valeur». [Lire ici] 1. L'appelant et la fonction font référence au même objet, mais le paramètre dans la fonction est une nouvelle variable qui contient simplement une copie de l'objet dans l'appelant. Comme C ++, un paramètre peut être modifié ou non en fonction - Cela dépend du type d'objet transmis. par exemple; Un type d'objet immuable ne peut pas être modifié dans la fonction appelée tandis qu'un objet mutable peut être mis à jour ou réinitialisé. Une différence cruciale entre la mise à jour ou la réaffectation / la réinitialisation de la variable mutable est que la valeur mise à jour est reflétée dans la fonction appelée, contrairement à la valeur réinitialisée. La portée de toute affectation d'un nouvel objet à une variable mutable est locale à la fonction dans le python. Les exemples fournis par @ blair-conrad sont excellents pour comprendre cela.
Alok Garg
la source
1
Vieux mais je me sens obligé de le corriger. Les chaînes sont passées par référence à la fois en Java et en C #, PAS par valeur
John
2

Étant donné que votre exemple se trouve être orienté objet, vous pouvez apporter la modification suivante pour obtenir un résultat similaire:

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

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

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'
Jesse Hogan
la source
1
Bien que cela fonctionne. Ce n'est pas passé par référence. C'est «passer par référence d'objet».
Bishwas Mishra
1

Vous pouvez simplement utiliser une classe vide comme instance pour stocker des objets de référence car les attributs d'objet internes sont stockés dans un dictionnaire d'instances. Voir l'exemple.

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)
sergzach
la source
1

Puisqu'il ne semble être mentionné nulle part, une approche pour simuler des références, comme par exemple C ++, consiste à utiliser une fonction "mise à jour" et à la transmettre à la place de la variable réelle (ou plutôt, "nom"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Ceci est surtout utile pour les "références en sortie uniquement" ou dans une situation avec plusieurs threads / processus (en rendant la fonction de mise à jour thread / multiprocessing sûre).

Évidemment, ce qui précède ne permet pas de lire la valeur, mais seulement de la mettre à jour.

Daniel Jour
la source