web-dev-qa-db-fra.com

python pandas dataframe, s'agit-il passe-à-valeur ou passe-à-référence

Si je passe une image de données à une fonction et que je la modifie à l'intérieur de la fonction, est-ce que cela passe par valeur ou passe par référence?

Je lance le code suivant

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
def letgo(df):
    df = df.drop('b',axis=1)
letgo(a)

la valeur de a ne change pas après l'appel de la fonction. Cela signifie-t-il que c'est une valeur de passage?

J'ai aussi essayé ce qui suit

xx = np.array([[1,2], [3,4]])
def letgo2(x):
    x[1,1] = 100
def letgo3(x):
    x = np.array([[3,3],[3,3]])

Il s'avère que letgo2() ne change pas xx et letgo3() ne change pas. Pourquoi est-ce comme ça?

55
nos

La réponse courte est: Python passe toujours par valeur, mais chaque variable Python est en fait un pointeur sur un objet, aussi parfois, ça ressemble à pass -par référence.

Dans Python, chaque objet est modifiable ou non. Par exemple, des listes, des bases de données, des modules et Pandas sont modifiables, ainsi que ints, strings and tuples Les objets mutables peuvent être modifiés en interne (par exemple, ajouter un élément à une liste), mais les objets non mutables ne le peuvent pas.

Comme je l'ai dit au début, vous pouvez considérer chaque variable Python comme un pointeur sur un objet. Lorsque vous transmettez une variable à une fonction, la variable (pointeur) de la fonction est toujours une copie de la variable (pointeur) qui a été transmise. Donc, si vous affectez quelque chose de nouveau à la variable interne, vous ne faites que modifier la variable locale pour qu'elle pointe vers un autre objet. Cela ne modifie pas (ne modifie pas) l'objet d'origine. que la variable pointée vers elle ne pointe pas la variable externe vers le nouvel objet.A ce stade, la variable externe pointe toujours vers l'objet d'origine, mais la variable interne pointe vers un nouvel objet.

Si vous souhaitez modifier l'objet d'origine (uniquement possible avec des types de données mutables), vous devez modifier quelque chose qui altère l'objet sans en affectant une toute nouvelle valeur à la variable locale. C'est pourquoi letgo() et letgo3() ne modifient pas l'élément externe, mais letgo2() le modifie.

Comme @ursan l'a fait remarquer, si letgo() utilisait quelque chose comme ceci à la place, alors l'objet original vers lequel pointait df serait modifié (ce qui modifierait la valeur vue via le global a variable:

def letgo(df):
    df.drop('b', axis=1, inplace=True)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo(a)  # will alter a

Dans certains cas, vous pouvez vider complètement la variable d'origine et la remplir avec de nouvelles données, sans effectuer une affectation directe, par ex. cela modifiera l'objet original vers lequel pointe v, ce qui modifiera les données vues lorsque vous utiliserez v plus tard:

def letgo3(x):
    x[:] = np.array([[3,3],[3,3]])

v = np.empty((2, 2))
letgo3(v)   # will alter v

Notez que je n’assigne pas directement quelque chose à x; J'attribue quelque chose à toute la plage interne de x.

Si vous devez absolument créer un tout nouvel objet et le rendre visible de l'extérieur (ce qui est parfois le cas avec les pandas), vous avez deux options. L’option "nettoyer" consisterait simplement à renvoyer le nouvel objet, par exemple,

def letgo(df):
    df = df.drop('b',axis=1)
    return df

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
a = letgo(a)

Une autre option consiste à accéder à l'extérieur de votre fonction et à modifier directement une variable globale. Cela change a pour qu'il pointe vers un nouvel objet, et toute fonction faisant référence à a ensuite verra ce nouvel objet:

def letgo():
    global a
    a = a.drop('b',axis=1)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo()   # will alter a!

Modifier directement les variables globales est généralement une mauvaise idée, car quiconque lira votre code aura du mal à comprendre comment a a été modifié. (J'utilise généralement des variables globales pour les paramètres partagés utilisés par de nombreuses fonctions dans un script, mais je ne les laisse pas modifier ces variables globales.)

60
Matthias Fripp

La question n'est pas PBV vs. PBR. Ces noms ne sont source de confusion que dans un langage comme Python; ils ont été inventés pour les langages fonctionnant comme le C ou le fortran (comme les langages par excellence PBV et PBR). Il est vrai, mais non éclairant, que Python passe toujours par valeur. La question ici est de savoir si la valeur elle-même est mutée ou si vous obtenez une nouvelle valeur. Pandas se trompe généralement du côté de ce dernier.

http://nedbatchelder.com/text/names.html explique très bien ce qu'est le système de noms de Python.

7
Mike Graham

Pour ajouter à la réponse de @Mike Graham, qui a souligné une très bonne lecture:

Dans votre cas, ce qui est important à retenir est la différence entre noms et valeurs. a, df, xx, x, sont tous noms, mais ils font référence au même ou différent = valeurs à différents moments de vos exemples:

  • Dans le premier exemple, letgo renoue df vers une autre valeur, car df.drop renvoie un nouveau DataFrame à moins que vous ne définissiez l’argument inplace = True _ ( voir doc ). Cela signifie que le nom df (local de la fonction letgo, qui faisait référence à la valeur de a, fait maintenant référence à une nouvelle valeur, ici le df.drop valeur de retour. La valeur a fait référence à existe toujours et n'a pas changé.

  • Dans le deuxième exemple, letgo2 _ mute x, sans le réassocier, raison pour laquelle xx est modifié par letgo2. Contrairement à l'exemple précédent, le nom local x fait toujours référence à la valeur à laquelle le nom xx fait référence et modifie cette valeur à la place, qui est pourquoi la valeur que xx fait référence a changé.

  • Dans le troisième exemple, letgo3 rebinds x vers un nouveau np.array. Cela donne le nom x, local à letgo3 et en faisant précédemment référence à la valeur de xx, pour faire maintenant référence à une autre valeur, la nouvelle np.array. La valeur que xx fait référence à n'a pas changé.

5
ursan

Python n'est ni passe par valeur ni passe par référence. C'est passe par cession.

Référence de support, the Python FAQ: https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function- with-output-parameters-call-by-reference

IOW:

  1. Si vous transmettez une valeur immuable, les modifications qui y sont apportées ne modifient pas sa valeur dans l'appelant, car vous réassociez le nom à un nouvel objet.
  2. Si vous transmettez une valeur modifiable, les modifications apportées à la fonction appelée changent également la valeur de l'appelant, tant que vous ne reliez pas ce nom à un nouvel objet. Si vous réaffectez la variable, en créant un nouvel objet, cette modification et les modifications ultérieures du nom ne sont pas visibles dans l'appelant.

Ainsi, si vous transmettez une liste et modifiez sa 0e valeur, cette modification est visible à la fois chez l'appelé et l'appelant. Mais si vous réaffectez la liste avec une nouvelle liste, cette modification est perdue. Mais si vous coupez la liste en tranches et remplacez ce que par une nouvelle liste, cette modification est visible à la fois chez l'appelé et l'appelant.

PAR EXEMPLE:

def change_it(list_):
    # This change would be seen in the caller if we left it alone
    list_[0] = 28

    # This change is also seen in the caller, and replaces the above
    # change
    list_[:] = [1, 2]

    # This change is not seen in the caller.
    # If this were pass by reference, this change too would be seen in
    # caller.
    list_ = [3, 4]

thing = [10, 20]
change_it(thing)
# here, thing is [1, 2]

Si vous êtes un fan de C, vous pouvez considérer cela comme un pointeur par valeur, et non comme un pointeur vers un pointeur vers une valeur, mais simplement comme un pointeur vers une valeur.

HTH.

1
dstromberg

vous devez faire 'un' global au début de la fonction, sinon c'est une variable locale et ne change pas le 'a' dans le code principal.

0
zosan

Voici le doc pour drop:

Renvoie un nouvel objet avec les étiquettes dans l'axe demandé supprimées.

Un nouveau cadre de données est donc créé. L'original n'a pas changé.

Mais comme pour tous les objets en python, le cadre de données est transmis à la fonction par référence.

0
Israel Unterman