web-dev-qa-db-fra.com

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

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'

Est-ce que je peux faire quelque chose pour passer la variable par référence réelle?

2393
David Sykes

Les arguments sont passés par assignation . La raison derrière ceci 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 modifiables, mais d'autres ne le sont pas

Alors:

  • Si vous transmettez un objet mutable à une méthode, la méthode obtient une référence à ce même objet et vous pouvez le muter à votre guise, mais si vous reliez la référence dans la méthode, la portée externe ne rien savoir, et une fois que vous avez terminé, la référence externe sera toujours dirigée vers l'objet d'origine.

  • Si vous transmettez un objet immutable à 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 ce soit encore plus clair, donnons 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)

Sortie:

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

Comme 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 en mutation pour le modifier et faire en sorte que les modifications soient reflétées dans l'étendue externe.

Voyons maintenant ce qui se passe lorsque nous essayons de changer 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)

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

Puisque le paramètre the_list a été passé par valeur, l'affectation d'une nouvelle liste à celle-ci n'a aucun effet visible du code en dehors de la méthode. Le the_list était une copie de la référence outer_list et nous avions the_list pointé sur une nouvelle liste, mais il n'y avait aucun moyen de modifier la position de outer_list.

String - un type immuable

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

Essayons maintenant 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

De nouveau, puisque le paramètre the_string a été passé par valeur, l’affectation d’une nouvelle chaîne à celle-ci n’a aucun effet visible sur le code situé en dehors de la méthode. Le the_string était une copie de la référence outer_string et nous avions the_string pointer vers une nouvelle chaîne, mais il n'y avait aucun moyen de modifier la position de outer_string.

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

EDIT: Il a été noté que cela ne répond pas à la question posée à l'origine par @David: "Est-ce que je peux faire quelque chose pour passer la variable par référence?". Travaillons là-dessus.

Comment pouvons-nous 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 de récupérer les informations souhaitées:

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 transmettre à la fonction ou utiliser une classe existante, telle qu'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.

2585
Blair Conrad

Le problème provient d'un malentendu sur 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 l'ordre suivant:

a = 1
a = 2

Vous pensez 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 comme ça que ça marche en Python. Au lieu de cela, a commence en tant que référence à un objet avec la valeur 1, puis est réaffecté en tant que 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 au premier; En fait, ils peuvent être partagés par un nombre quelconque 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 utilisée dans l'appel de fonction. Il est donc impossible 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.variable est une référence à l'objet chaîne 'Original'. Lorsque 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 autre objet chaîne 'Changed', mais la référence self.variable est séparée et ne change pas.

Le seul moyen de contourner ce problème consiste à passer un objet mutable. Étant donné que les deux références font référence au même objet, toute modification apportée à cet 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'
627
Mark Ransom

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

296
Zenadix

Ce n'est ni passage par valeur ni passage par référence - c'est 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 méthode Change est appelée, un espace de noms est créé pour elle; et var devient un nom, au sein de cet espace de noms, pour l'objet chaîne 'Original'. Cet objet a alors un nom dans deux espaces de noms. Ensuite, var = 'Changed' lie var à un nouvel objet chaîne et l’espace de nom de la méthode oublie donc 'Original'. Enfin, cet espace de noms est oublié et la chaîne 'Changed' en même temps.

231
David Cournapeau

Pensez au contenu passé par affectation au lieu de 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 au cours d’une affectation normale.

Ainsi, lorsque vous passez une liste à une fonction/méthode, la liste est affectée au nom du paramètre. Ajouter à la liste entraînera la modification de la liste. Réaffecter la liste à l'intérieur , la fonction ne modifiera 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']

Puisque les types immuables ne peuvent pas être modifiés, ils semblent ressembler à être passés par valeur - passer un int à une fonction signifie assigner l'int à le paramètre de fonctions. Vous ne pouvez jamais que réaffecter cela, mais cela ne changera pas la valeur des variables initiales.

159
Daren Thomas

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

Les objets sont alloués sur le tas et les pointeurs qui leur sont associés peuvent être échangés n'importe où.

  • Lorsque vous effectuez une affectation telle que x = 1000, une entrée de dictionnaire est créée pour mapper la chaîne "x" de l'espace de noms actuel sur un pointeur sur 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 vers le nouvel objet. L'ancien objet mille est inchangé (et peut être ou ne pas être en vie selon que toute autre chose 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 sur le même objet que l'entrée de "x".

  • Les objets tels que 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 que l'objet entier mille est créé, il ne changera jamais. Les maths sont faits en créant de nouveaux objets entiers.

  • Des objets tels que des listes sont mutables . 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. "X" et "y" désignent tous deux la même liste. La méthode append mute (met à jour) l'objet de la liste (comme l'ajout d'un enregistrement à une base de données) et le résultat est visible à la fois par "x" et par "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.

62
Raymond Hettinger

Techniquement, Python utilise toujours les valeurs de type passe par référence . 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 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 penser à une valeur de référence en tant qu'adresse de l'objet cible. L'adresse est automatiquement déréférencée lors de l'utilisation. 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 était passé par valeur, la lst extérieure ne pourrait pas être modifiée. Le vert représente les objets cibles (le noir correspond à la valeur stockée à l'intérieur, le rouge correspond au type d'objet), le jaune correspond à la mémoire contenant la valeur de référence à l'intérieur - dessinée comme une flèche. La flèche bleue continue correspond à la valeur de référence transmise à la fonction (via le chemin de la flèche bleue en pointillé). Le laid jaune foncé est le dictionnaire interne. (Elle pourrait en fait être dessinée aussi comme une ellipse verte. La couleur et la forme indiquent uniquement qu’elle est interne.)

Vous pouvez utiliser la fonction intégrée id() 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é de manière interne en tant que chaîne) lié à la variable de référence qui contient la valeur de référence pour l'objet cible. Le nom de la variable est la clé du 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 masquées en Python. Il n'y a pas de type d'utilisateur explicite pour stocker la valeur de référence. Toutefois, vous pouvez utiliser un élément de liste (ou un élément dans tout autre type de conteneur approprié) en tant que variable de référence, car tous les conteneurs stockent les éléments également en tant que références aux objets cibles. En d'autres termes, les éléments ne sont en réalité pas contenus dans le conteneur - seules les références aux éléments le sont.

59
pepr

Un truc simple que j'utilise normalement est de l'envelopper dans une liste:

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

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

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

42
AmanicA

Il n'y a pas de variables en Python

La clé pour comprendre le passage de paramètre est d’arrêter de penser aux "variables". Il y a des noms et des objets dans 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 de 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 celui auquel le nom x est actuellement lié. Ensuite, le nom b n'a plus rien à voir avec le nom x.

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

Comment lire l'exemple dans la question

Dans le code indiqué dans la question, l'instruction self.Change(self.variable) lie le nom var (dans l'étendue de la fonction Change) à l'objet qui contient la valeur 'Original' et l'affectation. var = 'Changed' (dans le corps de la fonction Change) assigne à nouveau le même nom: à un autre objet (qui contient également une chaîne mais qui aurait pu être totalement différent).

Comment passer par référence

(edit 2019-04-28)

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

S'il s'agit d'un objet immuable (par exemple, un booléen, un nombre, une chaîne de caractères), vous devez l'envelopper dans un objet mutable.
La solution rapide pour cela est une liste à un élément (au lieu de self.variable, passez [self.variable] et dans la fonction modify var[0]).
L’approche la plus Pythonic consisterait à introduire une classe triviale à un attribut. La fonction reçoit une instance de la classe et manipule l'attribut.

40
Lutz Prechelt

(edit - Blair a mis à jour sa réponse extrêmement populaire afin qu'elle soit maintenant précise)

Je pense qu'il est important de noter que le message actuel qui a obtenu le plus grand nombre de votes (par Blair Conrad), bien que correct quant à son résultat, est trompeur et constitue une limite incorrecte sur la base de ses définitions. Bien qu'il existe de nombreuses langues (telles que C) permettant à l'utilisateur de passer par référence ou 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 transmis valeur par valeur, toutes les langues sont transmises valeur par valeur, du fait qu'un élément de donnée (qu'il s'agisse d'une "valeur" ou d'une "référence") doit être envoyé. Cependant, cela ne signifie pas que Python est transmis valeur par valeur, dans le sens où un programmeur C y penserait.

Si vous voulez ce comportement, la réponse de Blair Conrad est correcte. Mais si vous voulez savoir en détail pourquoi Python ne passe ni par valeur ni par référence, lisez la réponse de David Cournapeau.

37
KobeJohn

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

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' )
23
bobobobo

Le schéma d’assignation par passage de Python n’est pas tout à fait le même que l’option de paramètres de référence du C++, mais il s’avère très similaire au modèle de passage d’arguments du langage C (et d’autres) en pratique:

  • Les arguments immuables sont effectivement passés "par valeur". Des objets tels que des entiers et des 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 de cette manière, l'effet est beaucoup comme faire une copie.
  • Les arguments mutables sont effectivement transmis "par pointeur". Des objets tels que des listes et des dictionnaires sont également transmis par référence d'objet, ce qui est similaire à la façon dont C passe les tableaux en tant que pointeurs; les objets mutables peuvent être modifiés à la place dans la fonction, un peu comme les tableaux C.
17
ajknzhol

Dans ce cas, la variable intitulée var dans la méthode Change reçoit une référence à self.variable et vous affectez immédiatement une chaîne à var. Il ne pointe plus vers self.variable. L'extrait de code suivant montre ce qui se produirait si vous modifiiez la structure de données indiquée par var et 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.

17
Mike Mazur

Comme vous pouvez le constater, vous devez avoir un objet mutable, 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
15
Nuno Aniceto

Beaucoup de perspicacités dans les réponses ici, mais je pense qu'un point supplémentaire n'est pas clairement mentionné ici explicitement. Citations de python documentation 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 que dans une fonction sont implicitement globales. Si une nouvelle valeur est affectée à une variable n'importe où dans le corps de la fonction, elle est supposée être locale. Si une nouvelle valeur est affectée à une variable dans la fonction, la variable est implicitement locale et vous devez la déclarer explicitement comme 'globale'. Bien que cela semble un peu surprenant au premier abord, un instant suffit à l'expliquer. D'une part, exiger la globalité pour les variables affectées constitue un obstacle contre les effets secondaires non voulus. d’autre part, si global était requis pour toutes les références globales, vous utiliseriez constamment Global. Vous devez déclarer globalement chaque référence à une fonction intégrée ou à un composant d’un module importé. vaincrait l’utilité de la déclaration mondiale d’identifier les effets secondaires ".

Même en passant un objet mutable à une fonction, cela reste valable. Et pour moi, explique clairement la raison de la différence de comportement entre l'affectation à l'objet et son utilisation 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.

13
Joop

Voici l'explication simple (j'espère) du concept pass by object utilisé en Python.
Chaque fois que vous transmettez un objet à la fonction, l'objet lui-même est transmis (l'objet dans 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 en cours de transmission. Donc, en fait, la fonction change_me essaiera de faire quelque chose comme:

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

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

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

Alors l'appel se traduirait par:

[0, 1].append(2)

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

10
matino

Mis à part toutes les bonnes explications sur la façon dont cela fonctionne en Python, je ne vois pas de suggestion simple pour le problème. Comme vous semblez créer des objets et des instances, la méthode Pythonic de gestion et de modification des variables d’instance 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 à 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 des méthodes d'instance. C'est aussi pourquoi vous passez self ainsi que le 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
8
Dolf Andringa

Il y a un petit truc pour passer un objet par référence, même si le langage ne le permet pas. Cela fonctionne aussi dans 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 bidule laid, mais ça marche. ;-P

6
itmuckel

J'ai utilisé la méthode suivante pour convertir rapidement quelques codes Fortran en Python. Certes, il ne s’agit pas d’une référence, car la question initiale a été posée, mais c’est un moyen simple de contourner le problème 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
4
Brad Porter

Pass-By-Reference dans Python est assez différent du concept de pass par référence en C++/Java.

  • Java & C #: types primitifs (chaîne à inclure) passés par valeur (copie), le type de référence étant passé par référence (copie d'adresse), toutes les modifications apportées à la Les paramètres de la fonction appelée sont visibles pour l'appelant.
  • C++: Les deux pass-by-reference ou pass-by-value sont autorisés. Si un paramètre est passé par référence, vous pouvez le modifier ou non, selon que le paramètre a été passé en tant que const ou non. Cependant, que ce soit constant 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 sur un autre objet dans la fonction appelée.
  • Python: Python est une "référence objet à objet", dont on dit souvent: "Les références d'objet sont passées par valeur. ”[Lire ici] 1 . L'appelant et la fonction se réfèrent au même objet, mais le paramètre de la fonction est une nouvelle variable qui ne contient qu'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/réinitialisation de la variable mutable réside dans le fait 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.
3
Alok Garg

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

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

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

bien sûr, dans le code réel, vous ajouteriez une vérification d'erreur à la recherche de dict.

3
mARK bLOORE

Bien que passer par référence ne soit pas un élément qui rentre bien dans python et devrait être rarement utilisé, certaines solutions de contournement peuvent en réalité permettre d’obtenir l’objet actuellement assigné à une variable locale ou même de réaffecter une variable locale de l’intérieur d’un objet. appelée fonction.

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

Une solution consiste à utiliser global (pour les variables globales) ou nonlocal (pour les variables locales dans une fonction) dans une fonction d'encapsulation.

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 delether une variable.

Pour simplement lire, il existe même un moyen plus court d’utiliser lambda: x qui renvoie un appelable qui, lorsqu’il est appelé, renvoie la valeur actuelle de x. Cela ressemble un peu à "appel par nom" utilisé dans des langues du 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

Le support "réflexion" de Pythons permet d’obtenir un objet capable de réaffecter un nom/une variable dans une portée donnée sans définir explicitement les fonctions dans cette étendue:

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 classe ByRef enveloppe un accès par dictionnaire. Donc, attribuer un accès à wrapped est traduit en un accès à un élément dans le dictionnaire passé. En passant le résultat de la variable intégrée locals et le nom d'une variable locale, vous accédez à une variable locale. La documentation de python à partir de la version 3.5 indique que la modification du dictionnaire pourrait ne pas fonctionner, mais cela semble fonctionner pour moi.

3
textshell

Étant donné que votre exemple est 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'
2
Jesse Hogan

Comme il semble être mentionné nulle part ailleurs, une approche pour simuler des références est connue, par exemple. C++ doit utiliser une fonction "update" et la transmettre à la place de la variable réelle (ou plutôt de "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 externes" ou dans une situation avec plusieurs threads/processus (en sécurisant la fonction de mise à jour thread/multitraitement).

Évidemment, ce qui précède ne permet pas lecture la valeur, elle ne fait que la mettre à jour.

1
Daniel Jour

Les dictionnaires étant passés par référence, vous pouvez utiliser une variable dict pour stocker toutes les valeurs référencées.

# 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
0
Liakos

Vous pouvez simplement utiliser ne classe vide comme instance pour stocker des objets de référence, car les attributs d'objet sont stockés en interne 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)
0
sergzach