Pourquoi une fonction peut-elle modifier certains arguments tels qu'ils sont perçus par l'appelant, mais pas d'autres?

J'essaie de comprendre L'approche de Python à la portée variable. Dans cet exemple, Pourquoi f() est-il capable de modifier la valeur de x, telle qu'elle est perçue dans main(), mais pas la valeur de n?

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

Sortie:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]
130
demandé sur Aran-Fey 2009-02-22 19:42:51

9 réponses

Certaines réponses contiennent le mot "Copier" dans un contexte d'appel de fonction. Je trouve cela déroutant.

Python ne copie pas les objets que vous transmettez lors d'un appel de fonctionjamais .

Les paramètres de fonction sont noms . Lorsque vous appelez une fonction, Python lie ces paramètres à tous les objets que vous transmettez (via des noms dans une portée de l'appelant).

Les objets peuvent être mutables (comme les listes) ou immuables (comme les entiers, les chaînes en Python). Objet Mutable que vous pouvez modifier. Vous ne pouvez pas changer un nom, vous pouvez simplement le lier à un autre objet.

Votre exemple ne concerne pas étendues ou espaces de noms , Il s'agit de nommage et liaisonet mutabilité d'un objet en Python.

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

Voici de belles images sur la différence entre les variables dans d'autres langages et les noms en Python .

166
répondu jfs 2018-09-25 17:17:24

Vous avez déjà un certain nombre de réponses, et je suis globalement d'accord avec J. F. Sebastian, mais vous pourriez trouver cela utile comme raccourci:

Chaque fois que vous voyez varname =, vous créez une liaison New name dans la portée de la fonction. Quelle que soit la valeur varname était liée à before est perdue dans cette portée.

Chaque fois que vous voyez varname.foo() vous appelez une méthode sur varname. La méthode peut modifier varname (par exemple list.append). varname (ou, plutôt, l'objet qui varname names) peut exister dans plus d'une portée, et comme c'est le même objet, toutes les modifications seront visibles dans toutes les étendues.

[notez que le mot clé global crée une exception au premier cas]

12
répondu John Fouhy 2011-09-15 20:36:33

f ne pas modifier la valeur de x (qui est toujours la même référence à une instance d'une liste). Au contraire, il modifie le contenu de cette liste.

Dans les deux cas, une copie d'une référence est passée à la fonction. À l'intérieur de la fonction,

  • n reçoit une nouvelle valeur. Seule la référence à l'intérieur de la fonction est modifiée, pas celle à l'extérieur.
  • x ne reçoit pas de nouvelle valeur: ni la référence à l'intérieur ni en dehors de la fonction sont modifiés. Au lieu de cela, la valeur x de est modifiée.

Puisque les x à l'intérieur de la fonction et à l'extérieur se réfèrent à la même valeur, les deux voient la modification. En revanche, les n à l'intérieur de la fonction et à l'extérieur se réfèrent à différentes valeurs après n a été réaffecté à l'intérieur de la fonction.

12
répondu Konrad Rudolph 2018-06-07 08:40:57

Je vais renommer les variables pour réduire la confusion. n -> nf ou nmain. x -> xf ou xmain:

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

Lorsque vous appelez la fonction f, l'exécution Python fait une copie de xmain et l'attribue à xf, et, de même, attribue une copie de nmain à nf.

Dans le cas de n, la valeur copiée est 1.

Dans le cas de x la valeur copiée est pas le littéral liste [0, 1, 2, 3]. C'est une référence à cette liste. xf et xmain sont pointant sur la même liste, de sorte que lorsque vous modifiez xf, vous êtes également la modification de xmain.

Si, cependant, vous deviez écrire quelque chose comme:

    xf = ["foo", "bar"]
    xf.append(4)

Vous trouverez que xmain n'a pas changé. C'est parce que, dans la ligne xf = ["foo", "bar"] vous avez changer de xf pour indiquer un nouveaux liste. Toutes les modifications que vous apportez à cette nouvelle liste n'auront aucun effet sur la liste que xmain pointe toujours.

J'espère que ça aide. :-)

3
répondu Pitarou 2009-02-22 17:15:36

C'est parce qu'une liste est un objet mutable. Vous ne définissez pas x à la valeur de [0,1,2,3], vous définissez une étiquette pour l'objet [0,1,2,3].

Vous devriez déclarer votre fonction f () comme ceci:

def f(n, x=None):
    if x is None:
        x = []
    ...
2
répondu Luiz Damim 2009-02-22 17:06:01

N est un int (immuable), et une copie est passée à la fonction, donc dans la fonction vous modifiez la copie.

X est une liste (mutable), et une copie de le pointeur est passé o la fonction x.append(4) modifie le contenu de la liste. Cependant, vous avez dit x = [0,1,2,3,4] dans votre fonction, vous ne changeriez pas le contenu de x dans main().

2
répondu Jason Coon 2009-02-22 17:07:59

Si les fonctions sont réécrites avec des variables complètement différentes et que nous appelons id sur elles, cela illustre bien le point. Je n'ai pas eu cela au début et j'ai lu le post de jfs avec la grande explication , alors j'ai essayé de comprendre / me convaincre:

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

def main():
    n = 1
    x = [0,1,2,3]
    print ('Before in main:', n, x,id(n),id(x))
    f(n, x)
    print ('After in main:', n, x,id(n),id(x))

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

Z et x ont le même id. Juste des balises différentes pour la même structure sous-jacente que l'article dit.

1
répondu jouell 2017-12-30 18:00:38

Python est un pur langage pass-by-value si vous y pensez de la bonne façon. Une variable python stocke l'emplacement d'un objet en mémoire. La variable Python ne stocke pas l'objet lui-même. Lorsque vous passer une variable à une fonction, vous êtes de passage à un copie de l'adresse de l'objet pointé par la variable.

Contrasst ces deux fonctions

def foo(x):
    x[0] = 5

def goo(x):
    x = []

Maintenant, lorsque vous tapez dans le shell

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

Comparez ceci à goo.

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

Dans le premier cas, nous passons une copie de l'adresse de vache à foo et foo modifié l'état de l'objet qui y réside. L'objet se modifie.

Dans le second cas, vous passez une copie de l'adresse de cow à goo. Ensuite, goo procède à changer cette copie. Effet: aucun.

J'appelle cela le principe de la maison rose. Si vous faites une copie de votre adresse et dites à un peintre pour peindre la maison à cette adresse rose, vous allez vous retrouver avec une maison rose. Si vous donnez au peintre une copie de votre adresse et lui dire de le changer pour une nouvelle adresse, l'adresse de votre maison ne change pas.

L'explication élimine beaucoup de confusion. Python transmet les variables d'adresses stockées par valeur.

0
répondu ncmathsadist 2011-05-28 18:42:33

Python est une copie par valeur de référence. Un objet occupe un champ en mémoire, et une référence est associée à cet objet, mais elle-même occupe un champ en mémoire. Et name / value est associé à une référence. En fonction python, il est toujours copier la valeur de la référence, dans votre code, n est copié pour être un nouveau nom, lorsque vous attribuez cela, il dispose d'un nouvel espace en appelant la pile. Mais pour la liste, le nom a également été copié, mais il fait référence à la même mémoire (puisque vous n'attribuez jamais la liste a nouvelle valeur). C'est une magie en python!

0
répondu sunxd 2016-01-16 16:30:33