Question Comment puis-je transmettre une variable par référence?


La documentation Python ne semble pas claire si les paramètres sont transmis par référence ou 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'

Y at-il quelque chose que je peux faire pour passer la variable par référence réelle?


2053
2018-06-12 10:23


origine


Réponses:


Les arguments sont passé par mission. La raison derrière cela est double:

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

Alors:

  • Si vous passez un mutable objet dans une méthode, la méthode obtient une référence à ce même objet et vous pouvez le muter à la joie de votre cœur, mais si vous reliez la référence dans la méthode, la portée externe ne saura rien à ce sujet, et après que vous avez terminé, la référence externe pointera toujours sur l'objet d'origine.

  • Si vous passez un immuable objet à 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 le rendre encore plus clair, prenons quelques exemples.

Liste - un type mutable

Essayons de modifier la liste qui a été passée à 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 passé est une référence à outer_list, pas une copie de celui-ci, nous pouvons utiliser les méthodes de la liste de mutation pour le changer et avoir les changements reflétés dans la portée externe.

Voyons maintenant ce qui se passe quand nous essayons de changer la référence qui a été passée en 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 Le paramètre a été transmis par valeur, l'assignation d'une nouvelle liste n'a eu aucun effet que le code en dehors de la méthode pourrait voir. le the_list était une copie de outer_list référence, et nous avions the_list pointer vers une nouvelle liste, mais il n'y avait aucun moyen de changer où outer_list pointu.

String - un type immuable

C'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)

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

Encore une fois, depuis le the_string Le paramètre a été transmis par valeur, l'assignation d'une nouvelle chaîne n'a eu aucun effet que le code en dehors de la méthode pourrait voir. le the_string était une copie de outer_string référence, et nous avions the_string pointer vers une nouvelle chaîne, mais il n'y avait aucun moyen de changer où outer_string pointu.

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

MODIFIER: Il a été noté que cela ne répond pas à la question que @David a initialement posée, "Y at-il quelque chose que je peux faire pour passer la variable par référence réelle?". Travaillons sur ça.

Comment pouvons-nous contourner cela?

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

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 conserver 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.


2221
2018-06-12 11:18



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

a = 1
a = 2

Tu crois ça a est un emplacement de mémoire qui stocke la valeur 1, puis est mis à jour pour stocker la valeur 2. Ce n'est pas comme ça que ça fonctionne en Python. Plutôt, a commence comme une référence à un objet avec la valeur 1, puis réaffecté comme référence à un objet avec la valeur 2. Ces deux objets peuvent continuer à coexister même si a ne se réfère plus au premier; en fait, ils peuvent être partagés 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 la référence utilisée dans l'appel de fonction, il n'y a donc aucun moyen de mettre à jour cette référence 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 'Original'. Quand vous appelez Change vous créez une deuxième référence var à l'objet. Dans la fonction, vous réaffectez la référence var à un objet chaîne différent 'Changed'mais la référence self.variable est séparé et ne change pas.

Le seul moyen de contourner cela est de passer un objet mutable. Étant donné que les deux références se réfèrent au même objet, les modifications apportées à l'objet sont répercutées aux deux endroits.

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

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

529
2017-11-15 17:45



J'ai trouvé les autres réponses plutôt longues et compliquées, j'ai donc créé ce simple diagramme pour expliquer la façon dont Python traite les variables et les paramètres. enter image description here


242
2017-09-04 16:05



Ce n'est ni une valeur de transfert, ni une référence de passe - 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 ne pas objets; ils ne peuvent pas être dénotés par d'autres variables ou référencés par des objets. "

Dans votre exemple, lorsque le Change méthode est appelée - un espace de noms est créé pour cela; et var 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. Prochain, var = 'Changed' 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.


219
2018-06-12 12:55



Pensez à des choses qui sont passées par cession au lieu de par référence / par valeur. De cette façon, il est toujours clair, ce qui se passe aussi longtemps que vous comprenez ce qui se passe pendant l'affectation 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 la fonction ne changera pas la liste d'origine, puisque:

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

Puisque les types immuables ne peuvent pas être modifiés, ils sembler comme être passé par valeur - passer un int dans une fonction signifie assigner l'int au paramètre functions. Vous ne pouvez réattribuer que cela, mais cela ne changera pas la valeur des variables d'origine.


142
2018-06-12 12:17



Effbot (aka Fredrik Lundh) a décrit le style de passage variable de Python en tant qu'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 passés n'importe où.

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

  • Lorsque vous mettez à jour "x" avec x = 2000, un nouvel objet entier est créé et le dictionnaire est mis à jour pour pointer sur le nouvel objet. Le vieil objet mille est inchangé (et peut ou peut ne pas être vivant selon que n'importe quoi d'autre se rapporte à l'objet).

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

  • Les objets tels que les chaînes et les entiers sont immuable. Cela signifie simplement qu'il n'y a pas de méthodes qui peuvent changer l'objet après qu'il a été créé. Par exemple, une fois que l'objet entier mille est créé, il ne changera jamais. Les mathématiques sont effectuées en créant de nouveaux objets entiers.

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

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


53
2018-03-29 04:41



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

Python utilise toujours des valeurs de passe-par-référence. Il n'y a pas d'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, une étape de plus pour sauter à 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é passé en 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 pleine bleue est la valeur de référence qui a été transmise à la fonction (via la flèche bleue en pointillé). L'horrible jaune foncé est le dictionnaire interne. (Il pourrait effectivement être dessiné aussi comme une ellipse verte.La couleur et la forme dit seulement qu'il est interne.)

Vous pouvez utiliser le id() fonction intégrée pour apprendre 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 la forme d'une chaîne) lié à la variable de référence qui contient la valeur de référence à 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 dans la cible.

Les valeurs de référence sont caché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 pas contenus dans le conteneur - seules les références aux éléments le sont.


53
2017-09-15 18:53



Une astuce simple que j'utilise habituellement consiste à simplement l'inclure 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.)


36
2017-08-05 22:52



(edit - Blair a mis à jour sa réponse énormément 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 incorrect en raison 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 message de Blair Conrad semble être correct alors que les définitions ne le sont pas.

Dans la mesure où Python est passé par valeur, toutes les langues sont transmises 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 la 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 connaître les rouages ​​de la raison pour laquelle Python n'est ni en valeur, ni en passant par référence, lisez la réponse de David Cournapeau.


34
2018-06-27 12:33



Il n'y a pas de variables dans 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 b au même objet que le nom x est actuellement lié à. Ensuite, le nom b n'a rien à voir avec le nom x plus

Voir les sections 3.1 et 4,2 dans la référence de langage Python 3.


Donc, dans le code indiqué dans la question, la déclaration self.Change(self.variable) lie le nom var (dans le cadre de la fonction Change) à l'objet qui détient la valeur 'Original' et la mission var = 'Changed' (dans le corps de la fonction Change) assigne de nouveau le même nom: à un autre objet (qui contient aussi une chaîne mais qui aurait pu être autre chose).


25
2018-02-11 11:29