marco de datos de python pandas, es pasar por valor o pasar por referencia

84

Si paso un marco de datos a una función y lo modifico dentro de la función, ¿es paso por valor o paso por referencia?

Ejecuto el siguiente código

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

el valor de ano cambia después de la llamada a la función. ¿Significa que se pasa por valor?

También probé lo siguiente

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

Resulta letgo2()que cambia xxy letgo3()no. ¿Por qué es como este?

nos
fuente

Respuestas:

90

La respuesta corta es, Python siempre pasa por valor, pero cada variable de Python es en realidad un puntero a algún objeto, por lo que a veces parece pasar por referencia.

En Python, cada objeto es mutable o no mutable. por ejemplo, listas, dictados, módulos y marcos de datos de Pandas son mutables, y las entradas, cadenas y tuplas no son mutables. Los objetos mutables se pueden cambiar internamente (por ejemplo, agregar un elemento a una lista), pero los objetos no mutables no.

Como dije al principio, puede pensar en cada variable de Python como un puntero a un objeto. Cuando pasa una variable a una función, la variable (puntero) dentro de la función es siempre una copia de la variable (puntero) que se pasó. Entonces, si asigna algo nuevo a la variable interna, todo lo que está haciendo es cambiar el variable local para apuntar a un objeto diferente. Esto no altera (muta) el objeto original al que apuntaba la variable, ni hace que la variable externa apunte al nuevo objeto. En este punto, la variable externa todavía apunta al objeto original, pero la variable interna apunta a un nuevo objeto.

Si desea alterar el objeto original (solo es posible con tipos de datos mutables), debe hacer algo que altere el objeto sin asignar un valor completamente nuevo a la variable local. Esta es la razón letgo()y letgo3()dejar el elemento externo inalterado, pero letgo2()lo altera.

Como señaló @ursan, si se letgo()usa algo como esto, entonces alteraría (mutaría) el objeto original al que dfapunta, lo que cambiaría el valor visto a través de la avariable global :

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

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

En algunos casos, puede vaciar completamente la variable original y rellenarla con nuevos datos, sin hacer una asignación directa, por ejemplo, esto alterará el objeto original al que vapunta, lo que cambiará los datos que se ven cuando use vmás adelante:

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

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

Observe que no estoy asignando algo directamente a x; Estoy asignando algo a todo el rango interno de x.

Si es absolutamente necesario crear un objeto completamente nuevo y hacerlo visible externamente (que a veces es el caso de los pandas), tiene dos opciones. La opción 'limpiar' sería simplemente devolver el nuevo objeto, por ejemplo,

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

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

Otra opción sería llegar fuera de su función y alterar directamente una variable global. Esto cambia apara apuntar a un nuevo objeto, y cualquier función a la que se refiera aposteriormente verá ese nuevo objeto:

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

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

Alterar directamente las variables globales suele ser una mala idea, porque cualquiera que lea su código tendrá dificultades para averiguar cómo ase modificó. (Por lo general, uso variables globales para parámetros compartidos utilizados por muchas funciones en un script, pero no dejo que alteren esas variables globales).

Matthias Fripp
fuente
7

La pregunta no es PBV vs. PBR. Estos nombres solo causan confusión en un lenguaje como Python; fueron inventados para lenguajes que funcionan como C o como Fortran (como lenguajes PBV y PBR por excelencia). Es cierto, pero no esclarecedor, que Python siempre pasa por valor. La pregunta aquí es si el valor en sí está mutado o si obtiene un nuevo valor. Los pandas suelen pecar de este último.

http://nedbatchelder.com/text/names.html explica muy bien cuál es el sistema de nombres de Python.

Mike Graham
fuente
1
La semántica de pasar y asignar en Python es exactamente la misma que en Java, y las mismas cosas que dices se pueden aplicar igualmente a Java. Sin embargo, en StackOverflow y en otras partes de Internet, la gente aparentemente encuentra "esclarecedor" impresionarle de que Java siempre pasa por alto cuando surge este problema.
newacct
7

Para agregar a la respuesta de @Mike Graham, quien señaló una muy buena lectura:

En su caso, lo que es importante recordar es la diferencia entre nombres y valores . a, df, xx, x, Son todos nombres , pero se refieren a los mismos o diferentes valores en diferentes puntos de sus ejemplos:

  • En el primer ejemplo, letgo vuelve df a enlazar a otro valor, porque df.dropdevuelve un nuevo a DataFramemenos que establezcas el argumento inplace = True( ver doc ). Eso significa que el nombre df(local a la letgofunción), que se refería al valor de a, ahora se refiere a un nuevo valor, aquí el df.dropvalor de retorno. El valor al que ase refiere todavía existe y no ha cambiado.

  • En el segundo ejemplo, letgo2 muta x , sin volver a unirlo, por lo que xxse modifica por letgo2. A diferencia del ejemplo anterior, aquí el nombre local xsiempre se refiere al valor al que xxse refiere el nombre y cambia ese valor en su lugar , por lo que el valor al que xxse refiere ha cambiado.

  • En el tercer ejemplo, se letgo3 vuelve x a vincular a un nuevo np.array. Eso hace que el nombre x, local letgo3y anteriormente referido al valor de xx, ahora se refiera a otro valor, el nuevo np.array. El valor al que xxse refiere no ha cambiado.

ursan
fuente
3

Python no se pasa por valor ni pasa por referencia. Se pasa por asignación.

Referencia de apoyo, las preguntas frecuentes de Python: https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

OIA:

  1. Si pasa un valor inmutable, los cambios en él no cambian su valor en el llamador, porque está volviendo a vincular el nombre a un nuevo objeto.
  2. Si pasa un valor mutable, los cambios realizados en la función llamada, también cambia el valor en el llamador, siempre y cuando no vuelva a vincular ese nombre a un nuevo objeto. Si reasigna la variable, creando un nuevo objeto, ese cambio y los cambios posteriores en el nombre no se ven en la persona que llama.

Entonces, si pasa una lista y cambia su valor 0, ese cambio se ve tanto en el llamado como en el llamador. Pero si reasigna la lista con una nueva lista, este cambio se pierde. Pero si se mire la lista y reemplazar que con una nueva lista, que el cambio es visto tanto en la llamada y la persona que llama.

P.EJ:

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 eres un fanático de C, puedes pensar en esto como pasar un puntero por valor, no un puntero a un puntero a un valor, solo un puntero a un valor.

HTH.

Dstromberg
fuente
0

Aquí está el documento para soltar:

Devuelve un nuevo objeto con las etiquetas eliminadas en el eje solicitado.

Entonces se crea un nuevo marco de datos. El original no ha cambiado.

Pero como para todos los objetos en Python, el marco de datos se pasa a la función por referencia.

Israel Unterman
fuente
pero lo asigné dfdentro de la función, ¿no significa que el valor al que se hace referencia se ha cambiado al nuevo objeto?
nos
La asignación a un nombre local nunca cambiará el objeto al que está vinculado un nombre en otro ámbito.
Mike Graham
0

necesita hacer 'a' global al comienzo de la función, de lo contrario es una variable local y no cambia la 'a' en el código principal.

zosan
fuente