214 votes

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

J'essaie de comprendre l'approche de Python concernant la portée des variables. Dans cet exemple, pourquoi f() capable de modifier la valeur de x tel que perçu 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]

9 votes

249voto

J.F. Sebastian Points 102961

Certaines réponses contiennent le mot "copie" dans un contexte d'appel de fonction. Je trouve que cela prête à confusion.

Python ne copie pas objets que vous passez lors d'un appel de fonction jamais .

Les paramètres de la fonction sont noms . Lorsque vous appelez une fonction, Python lie ces paramètres aux objets que vous passez (via des noms dans une portée d'appel).

Les objets peuvent être mutables (comme les listes) ou immuables (comme les entiers, les chaînes de caractères en Python). Un objet mutable peut être modifié. Vous ne pouvez pas changer un nom, vous pouvez juste le lier à un autre objet.

Votre exemple ne concerne pas scopes ou espaces de noms il s'agit de dénomination et liaison y la 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 photos sur la différence entre les variables dans d'autres langues et les noms en Python .

4 votes

Cet article m'a aidé à mieux comprendre le problème et il propose une solution de contournement et quelques utilisations avancées : Valeurs par défaut des paramètres en Python

0 votes

@Gfy, j'ai déjà vu des exemples similaires mais pour moi, cela ne décrit pas une situation réelle. Si vous modifiez quelque chose qui a été transmis, cela n'a pas de sens de lui donner une valeur par défaut.

0 votes

@MarkRansom, je pense que cela a du sens si vous voulez fournir une destination de sortie optionnelle comme dans : def foo(x, l=None): l=l or []; l.append(x**2); return l[-1] .

21voto

John Fouhy Points 14700

Vous avez déjà obtenu un certain nombre de réponses, et je suis largement d'accord avec J.F. Sebastian, mais vous trouverez peut-être ceci utile comme raccourci :

Chaque fois que vous voyez varname = vous créez un nouveau la liaison du nom dans la portée de la fonction. Quelle que soit la valeur varname était lié à avant est perdu dans ce cadre .

Chaque fois que vous voyez varname.foo() vous appelez une méthode sur varname . La méthode peut modifier le nom de famille (par exemple, le nom de l'utilisateur). list.append ). varname (ou, plutôt, l'objet que varname ) peut exister dans plus d'une portée, et comme il s'agit du même objet, toute modification sera visible dans toutes les portées.

(notez que le global le mot-clé crée une exception au premier cas].

0 votes

Cette information était exactement ce dont j'avais besoin, merci.

17voto

Konrad Rudolph Points 231505

f ne modifie pas réellement 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, un copie d'une référence est transmis à la fonction. A l'intérieur de la fonction,

  • n se voit attribuer une nouvelle valeur. Seule la référence à l'intérieur de la fonction est modifiée, pas celle à l'extérieur.
  • x ne se voit pas attribuer une nouvelle valeur : ni la référence à l'intérieur ni à l'extérieur de la fonction ne sont modifiées. Au contraire, x 's valeur est modifié.

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

8 votes

Le terme "copie" est trompeur. Python n'a pas de variables comme le C. Tous les noms en Python sont des références. Vous ne pouvez pas modifier le nom, vous pouvez simplement le lier à un autre objet, c'est tout. Cela n'a de sens que de parler de mutable et d'immuable. objeto en Python, ce ne sont pas des noms.

1 votes

@J.F. Sebastian : Votre déclaration est au mieux trompeuse. Il n'est pas utile de considérer les nombres comme des références.

10 votes

@dysfunctor : les nombres sont des références à des objets immuables. Si vous préférez les considérer d'une autre manière, vous avez un tas de cas particuliers à expliquer. Si vous les considérez comme immuables, il n'y a pas de cas particuliers.

3voto

jcoon Points 5693

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

X est une liste (mutable), et une copie de le pointeur est passé à la fonction, donc x.append(4) change le contenu de la liste. Cependant, si vous aviez dit x = [0,1,2,3,4] dans votre fonction, vous n'auriez pas modifié le contenu de x dans main().

4 votes

Attention à la formulation "copie du pointeur". Les deux endroits obtiennent des références aux objets. n est une référence à un objet immuable ; x est une référence à un objet mutable.

2voto

Luiz Damim Points 1695

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

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

def f(n, x=None):
    if x is None:
        x = []
    ...

4 votes

Cela n'a rien à voir avec la mutabilité. Si vous faites x = x + [4] au lieu de x.append(4) vous ne verrez aucun changement dans l'appelant, même si une liste est mutable. Cela a à voir avec si il est en effet muté.

1 votes

Par contre, si vous faites x += [4] puis x est muté, tout comme ce qui se passe avec x.append(4) pour que l'appelant voie le changement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X