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 df
apunta, lo que cambiaría el valor visto a través de la a
variable global :
def letgo(df):
df.drop('b', axis=1, inplace=True)
a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo(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 v
apunta, lo que cambiará los datos que se ven cuando use v
más adelante:
def letgo3(x):
x[:] = np.array([[3,3],[3,3]])
v = np.empty((2, 2))
letgo3(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 a
para apuntar a un nuevo objeto, y cualquier función a la que se refiera a
posteriormente verá ese nuevo objeto:
def letgo():
global a
a = a.drop('b',axis=1)
a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo()
Alterar directamente las variables globales suele ser una mala idea, porque cualquiera que lea su código tendrá dificultades para averiguar cómo a
se 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).