sur - python variable globale classe




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

La documentation Python ne semble pas préciser 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?


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 existe 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 le nom x est actuellement lié. Ensuite, le nom b n’a plus rien à voir avec le nom x .

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

Ainsi, 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 ce même nom: à un autre objet (qui contient aussi une chaîne mais qui aurait pu être autre chose).


(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 (comme le 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 renvoie à 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 le sont, car certaines données (qu'il s'agisse d'une "valeur" ou d'une "référence") doivent être envoyées. Cependant, cela ne signifie pas que Python est transmis valeur par valeur au 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 n'est ni valeur par valeur ni valeur par référence, lisez la réponse de David Cournapeau.


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 pointé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.


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 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" dans 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 sur 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.

  • Les objets comme les 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 x = []; y = x; x.append(10); print y x = []; y = x; x.append(10); print y imprimera [10] . La liste vide a été créée. "X" et "y" désignent tous les deux la même liste. La méthode append modifie (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 "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.


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 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 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. Celle-ci 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 de chaîne 'Original' . Lorsque vous appelez Change vous créez une deuxième référence à l'objet. Dans la fonction, vous réaffectez la référence à un autre objet de 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'

Le schéma d'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 très similaire au modèle de transmission d'arguments du langage C (et d'autres) dans la pratique:

  • Les arguments immuables sont effectivement transmis « 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 la même manière, l'effet ressemble beaucoup à la 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 des tableaux à des pointeurs. .

Pensez aux éléments 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 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 dans 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']

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


Techniquement, Python utilise toujours des valeurs 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-à-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:

Si l'argument était passé par valeur, le premier lst ne pourrait pas être modifié. 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. (En réalité, elle pourrait être dessinée 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 connaître la valeur de référence (c'est-à-dire l'adresse de l'objet cible).

Dans les langages compilés, une variable est un espace mémoire capable de capturer la valeur 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.


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

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

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

Etant donné la façon dont 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.


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 bidule laid, mais ça marche. ;-P


J'ai utilisé la méthode suivante pour convertir rapidement quelques codes Fortran en Python. Certes, il ne s’agit pas d’un renvoi car la question initiale a été posée, mais il s’agit d’un simple remède 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

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

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


Vous pouvez simplement utiliser une classe vide en tant qu'instance pour stocker des objets de référence, car les attributs d'objet en interne 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)






pass-by-reference