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?
python
reference
parameter-passing
pass-by-reference
David Sykes
la source
la source
Réponses:
Les arguments sont transmis par affectation . La raison derrière cela est double:
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:
Production:
É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:
Production:
Étant donné que le
the_list
paramè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. Lethe_list
était une copie de laouter_list
référence, et nous avons eu lethe_list
point à une nouvelle liste, mais il n'y avait aucun moyen de changer l' endroit oùouter_list
pointe.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
Production:
Encore une fois, puisque le
the_string
paramè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. Lethe_string
était une copie de laouter_string
référence, et nous avons eu lethe_string
point d'une nouvelle chaîne, mais il n'y avait aucun moyen de changer l' endroit oùouter_string
pointe.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:
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:
Bien que cela semble un peu lourd.
la source
def Foo(alist): alist = [1,2,3]
ne pas modifier le contenu de la liste du point de vue des appelants.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:
Vous pensez que
a
c'est un emplacement de mémoire qui stocke la valeur1
, puis est mis à jour pour stocker la valeur2
. Ce n'est pas ainsi que les choses fonctionnent en Python.a
Commence plutôt comme référence à un objet avec la valeur1
, puis est réaffecté comme référence à un objet avec la valeur2
. Ces deux objets peuvent continuer à coexister même s'ilsa
ne 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:
self.variable
est une référence à l'objet chaîne'Original'
. Lorsque vous appelez,Change
vous créez une deuxième référencevar
à l'objet. Dans la fonction, vous réaffectez la référencevar
à un autre objet chaîne'Changed'
, mais la référenceself.variable
est 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.
la source
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.
la source
B[0] = 2
, par rapport à l' affectation directe,B = 2
.A=B
ouB=A
.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:
Dans votre exemple, lorsque la
Change
méthode est appelée - un espace de noms est créé pour elle; etvar
devient 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 lievar
à 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.la source
swap
fonction qui peut échanger deux références, comme ceci:a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
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:
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.
la source
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 y
s'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.
la source
id()
fonction renvoie la valeur du pointeur (référence de l'objet), comme le suggère la réponse de Pepr?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:
Si l'argument a été passé par valeur, l'extérieur
lst
ne 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.
la source
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.
C'est tout ce qu'il y a à faire. La mutabilité n'est pas pertinente pour cette question.
Exemple:
Cela lie le nom
a
à un objet de type entier qui contient la valeur 1.Cela lie le nom
b
au même objet auquel le nomx
est actuellement lié. Après, le nomb
n'a plus rien à voir avec le nomx
.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 nomvar
(dans la portée de la fonctionChange
) à l'objet qui contient la valeur'Original'
et l'affectationvar = 'Changed'
(dans le corps de la fonctionChange
) 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 fonctionvar[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.
la source
int
déclare une variable, maisInteger
ne le fait pas. Ils déclarent tous deux des variables. LaInteger
variable est un objet, laint
variable est une primitive. À titre d'exemple, vous avez démontré le fonctionnement de vos variables en les montranta = 1; b = a; a++ # doesn't modify b
. C'est également vrai en Python (en utilisant+= 1
car il n'y++
en a pas en Python)!Une astuce simple que j'utilise normalement consiste à simplement l'encapsuler dans une liste:
(Oui, je sais que cela peut être gênant, mais parfois c'est assez simple pour le faire.)
la source
(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.
la source
void swap(int& x, int& y) { int temp = x; x = y; y = temp; }
permutera les variables qui lui sont transmises. En Pascal, vous utilisez à lavar
place de&
.Vous avez ici de très bonnes réponses.
la source
Dans ce cas, la variable intitulée
var
dans la méthodeChange
se voit attribuer une référence àself.variable
, et vous affectez immédiatement une chaîne àvar
. Cela ne pointe plusself.variable
. L'extrait de code suivant montre ce qui se passerait si vous modifiez la structure de données pointée parvar
etself.variable
, dans ce cas, une liste:Je suis sûr que quelqu'un d'autre pourrait clarifier cela davantage.
la source
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:
la source
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:
la source
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.
donne:
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.
la source
Voici l'explication simple (j'espère) du concept
pass by object
utilisé 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:
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_me
essaiera de faire quelque chose comme:ce qui ne changera évidemment pas l'objet passé à la fonction. Si la fonction ressemblait à ceci:
Ensuite, l'appel entraînerait:
ce qui va évidemment changer l'objet. Cette réponse l' explique bien.
la source
list = [1, 2, 3]
causes de réutilisant leslist
nom pour quelque chose d' autre et forgeting l'objet à l' origine passé. Cependant, vous pouvez essayerlist[:] = [1, 2, 3]
(aulist
fait, 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?[0, 1] = [1, 2, 3]
s'agit simplement d'un mauvais exemple. Il n'y a rien de tel en Python.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. :-)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:
Dans les méthodes d'instance, vous vous référez normalement à
self
pour 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 égalementself
le premier argumentdef Change
.Une autre solution serait de créer une méthode statique comme celle-ci:
la source
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. ;-)
C'est un vilain hack, mais ça marche. ;-P
la source
p
est une référence à un objet liste mutable qui à son tour stocke l'objetobj
. La référence «p» est transmisechangeRef
. À l'intérieurchangeRef
, une nouvelle référence est créée (la nouvelle référence est appeléeref
) qui pointe vers le même objet de liste vers lequelp
pointe. 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é laref
ré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 deref
mais cette modification est visible parp
.p
et lesref
pointer vers un objet de la liste qui stocke l'objet unique,PassByReference('Michael')
. Il s'ensuit donc quep[0].name
revientMichael
. Bien sûr, ilref
est désormais hors de portée et peut être récupéré mais tout de même.name
, de l'PassByReference
objet d' origine associé à la référenceobj
. En fait,obj.name
reviendraPeter
. Les commentaires susmentionnés supposent la définitionMark Ransom
donnée.PassByReference
objet par un autrePassByReference
objet dans votre liste et fait référence au dernier des deux objets.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.
la source
dict
, puis retourne ledict
. 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é,dict
mais également être en mesure de signaler la reconstruction. J'ai essayé de passer unbool
par 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).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) ounonlocal
(pour les variables locales dans une fonction) dans une fonction wrapper.La même idée fonctionne pour lire et
del
définir une variable.Pour la lecture, il y a même une manière plus courte de simplement utiliser
lambda: x
qui 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:
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:
Ici, la
ByRef
classe encapsule un accès au dictionnaire. Ainsi, l'accès aux attributswrapped
est traduit en un accès aux éléments dans le dictionnaire passé. En passant le résultat de la commande intégréelocals
et 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.la source
é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:
dans le code réel, vous ajouteriez, bien sûr, la vérification des erreurs lors de la recherche de dict.
la source
É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.
la source
Pass-By-Reference en Python est assez différent du concept de pass by reference en C ++ / Java.
la source
Étant donné que votre exemple se trouve être orienté objet, vous pouvez apporter la modification suivante pour obtenir un résultat similaire:
la source
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.
la source
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"):
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.
la source