¿Por qué una función puede modificar algunos argumentos según lo percibe el llamante, pero no otros?

Estoy tratando de entender el enfoque de Python al scope variable. En este ejemplo, ¿por qué f() puede alterar el valor de x , como se percibe dentro de main() , pero no el valor 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() 

Salida:

 Before: 1 [0, 1, 2, 3] In f(): 2 [0, 1, 2, 3, 4] After: 1 [0, 1, 2, 3, 4] 

Algunas respuestas contienen la palabra “copiar” en el contexto de una llamada de función. Lo encuentro confuso.

Python no copia los objetos que pasa durante una llamada de función nunca .

Los parámetros de función son nombres . Cuando llama a una función, Python vincula estos parámetros a cualquier objeto que pase (a través de nombres en el ámbito de la persona que llama).

Los objetos pueden ser mutables (como listas) o inmutables (como enteros, cadenas en Python). Objeto mutable que puedes cambiar. No puedes cambiar un nombre, solo puedes vincularlo a otro objeto.

Su ejemplo no se trata de ámbitos o espacios de nombres , se trata de nombrar y vincular y la mutabilidad de un objeto 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 

Aquí hay buenas imágenes sobre la diferencia entre variables en otros idiomas y nombres en Python .

Ya tienes varias respuestas, y estoy de acuerdo con JF Sebastian, pero puede que te resulte útil como acceso directo:

Cada vez que vea varname = , está creando un nuevo enlace de nombre dentro del scope de la función. Cualquiera que sea el valor varname atado antes se pierde dentro de este scope .

Cada vez que vea varname.foo() está llamando a un método en varname . El método puede alterar varname (por ejemplo, list.append ). varname (o, más bien, el objeto que varname names) puede existir en más de un ámbito, y dado que es el mismo objeto, cualquier cambio será visible en todos los ámbitos.

[note que la palabra clave global crea una excepción al primer caso]

f no modifica realmente el valor de x (que siempre es la misma referencia a una instancia de una lista). Más bien, altera los contenidos de esta lista.

En ambos casos, se pasa una copia de una referencia a la función. Dentro de la función,

  • n se le asigna un nuevo valor. Solo se modifica la referencia dentro de la función, no la que está fuera de ella.
  • x no se le asigna un nuevo valor: ni la referencia dentro ni fuera de la función se modifican. En cambio, el valor de x se modifica.

Dado que tanto la x dentro de la función como la externa se refieren al mismo valor, ambos ven la modificación. Por el contrario, n dentro de la función y fuera de ella se refieren a diferentes valores después de que n fue reasignado dentro de la función.

Cambiaré el nombre de las variables para reducir la confusión. n -> nf o nmain . x -> xf o 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() 

Cuando llamas a la función f , el tiempo de ejecución de Python hace una copia de xmain y la asigna a xf , y de manera similar asigna una copia de nmain a nf .

En el caso de n , el valor que se copia es 1.

En el caso de x, el valor que se copia no es la lista literal [0, 1, 2, 3] . Es una referencia a esa lista. xf y xmain están apuntando a la misma lista, así que cuando modifica xf también está modificando xmain .

Sin embargo, si tuvieras que escribir algo como:

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

encontrarías que xmain no ha cambiado. Esto se debe a que, en la línea xf = [“foo”, “barra”] , ha cambiado xf para apuntar a una nueva lista. Cualquier cambio que realice en esta nueva lista no tendrá ningún efecto en la lista a la que xmain aún apunta.

Espero que ayude. 🙂

Es porque una lista es un objeto mutable. No está configurando x al valor de [0,1,2,3], está definiendo una etiqueta para el objeto [0,1,2,3].

Debes declarar tu función f () así:

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

n es un int (inmutable), y se pasa una copia a la función, por lo que en la función está cambiando la copia.

X es una lista (mutable), y se pasa una copia del puntero a la función para que x.append (4) cambie el contenido de la lista. Sin embargo, usted dijo que x = [0,1,2,3,4] en su función, no cambiaría el contenido de x en main ().

Si las funciones se reescriben con variables completamente diferentes y las llamamos id , entonces ilustra bien el punto. Al principio no entendí esto y leí la publicación de jfs con la gran explicación , así que traté de entenderme / convencerme a mí mismo:

 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 y x tienen la misma id. Solo diferentes tags para la misma estructura subyacente que dice el artículo.

Python es un lenguaje de paso por valor puro si lo piensas de la manera correcta. Una variable de python almacena la ubicación de un objeto en la memoria. La variable Python no almacena el objeto en sí. Cuando pasa una variable a una función, está pasando una copia de la dirección del objeto al que apunta la variable.

Contraste estas dos funciones

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

Ahora, cuando escribes en el shell

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

Compara esto con goo.

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

En el primer caso, pasamos una copia de la dirección de cow a foo y foo modificamos el estado del objeto que reside allí. El objeto se modifica.

En el segundo caso, usted pasa una copia de la dirección de la vaca a goo. Entonces Goo procede a cambiar esa copia. Efecto: ninguno.

Yo llamo a esto el principio de la casa rosa . Si hace una copia de su dirección y le dice a un pintor que pinte la casa en esa dirección de color rosa, terminará con una casa de color rosa. Si le da al pintor una copia de su dirección y le dice que la cambie por una nueva, la dirección de su casa no cambia.

La explicación elimina mucha confusión. Python pasa el almacén de variables de direcciones por valor.

Python es copia por valor de referencia. Un objeto ocupa un campo en la memoria, y una referencia está asociada con ese objeto, pero en sí misma ocupa un campo en la memoria. Y el nombre / valor está asociado con una referencia. En la función python, siempre copia el valor de la referencia, por lo que en su código, n se copia para ser un nuevo nombre, cuando lo asigna, tiene un nuevo espacio en la stack de llamadas. Pero para la lista, el nombre también se copió, pero se refiere a la misma memoria (ya que nunca asigna un nuevo valor a la lista). ¡Eso es una magia en python!

Mi entendimiento general es que cualquier variable de objeto (como una lista o un dict, entre otras) puede modificarse a través de sus funciones. Lo que creo que no puede hacer es reasignar el parámetro, es decir, asignarlo por referencia dentro de una función que se puede llamar.

Eso es consistente con muchos otros idiomas.

Ejecute el siguiente script corto para ver cómo funciona:

 def func1(x, l1): x = 5 l1.append("nonsense") 

def func1(x, l1): x = 5 l1.append("nonsense")

y = 10
list1 = [“significado”]
func1 (y, lista1)
imprimir (y)
imprimir (lista1)

`