Llamada a funciones de Python por referencia

82

En algunos idiomas, puede pasar un parámetro por referencia o valor utilizando una palabra reservada especial como ref o val . Cuando pasa un parámetro a una función de Python, nunca altera el valor del parámetro al salir de la función. La única forma de hacerlo es usando la palabra reservada global (o como la entiendo actualmente).

Ejemplo 1:

k = 2

def foo (n):
     n = n * n     #clarity regarding comment below
     square = n
     return square

j = foo(k)
print j
print k

mostraría

>>4
>>2

mostrando k sin cambios

En este ejemplo, la variable n nunca se cambia

Ejemplo 2:

n = 0
def foo():
    global n
    n = n * n
    return n

En este ejemplo se cambia la variable n

¿Hay alguna forma en Python de llamar a una función y decirle a Python que el parámetro es un valor o un parámetro de referencia en lugar de usar global?

En segundo lugar, en los exámenes de Cambridge de nivel A, ahora dicen que una función devuelve un solo valor, mientras que un procedimiento devuelve más de un valor. Me enseñaron que una función tiene una declaración de retorno y un procedimiento no, durante los años 80. ¿Por qué esto ahora es incorrecto?

Timothy Lawman
fuente
5
El mejor recurso que he encontrado para comprender el modelo de llamada de Python es este artículo sobre effbot: effbot.org/zone/call-by-object.htm
Wilduck
1
deberías leer sobre variables de Python, objetos mutables e inmutables. Además, en su primer ejemplo, por qué se ncambiaría, está utilizando una nueva variable squarepara almacenar el resultado de su cálculo.
Facundo Casco
6
No publique dos preguntas no relacionadas en una. La definición de función versus procedimiento es una cuestión de definición, y parece que su definición está sesgada hacia Pascal. No existe tal cosa como un procedimiento en la jerga de Python.
Fred Foo
2
Además, eche un vistazo a esta respuesta anterior de StackOverflow. stackoverflow.com/a/10262945/173292 Explica la llamada por modelo de referencia de objeto de forma bastante intuitiva.
Wilduck
1
Vea también esta pregunta SO para una explicación muy útil: ¿cómo paso una variable por referencia
Michael Ohlrogge

Respuestas:

78

No puede cambiar un objeto inmutable, como stro tuple, dentro de una función en Python, pero puede hacer cosas como:

def foo(y):
  y[0] = y[0]**2

x = [5]
foo(x)
print x[0]  # prints 25

Sin embargo, esa es una forma extraña de hacerlo, a menos que siempre necesite cuadrar ciertos elementos en una matriz.

Tenga en cuenta que en Python, también puede devolver más de un valor, lo que hace que algunos de los casos de uso para pasar por referencia sean menos importantes:

def foo(x, y):
   return x**2, y**2

a = 2
b = 3
a, b = foo(a, b)  # a == 4; b == 9

Cuando devuelve valores como ese, se devuelven como una tupla que a su vez se desempaqueta.

editar: Otra forma de pensar en esto es que, si bien no puede pasar variables explícitamente por referencia en Python, puede modificar las propiedades de los objetos que se pasaron. En mi ejemplo (y otros) puede modificar miembros de la lista que se pasó en. Sin embargo, no podrá reasignar la variable pasada en por completo. Por ejemplo, observe que los siguientes dos fragmentos de código parecen que podrían hacer algo similar, pero terminan con resultados diferentes:

def clear_a(x):
  x = []

def clear_b(x):
  while x: x.pop()

z = [1,2,3]
clear_a(z) # z will not be changed
clear_b(z) # z will be emptied
Dave Mankoff
fuente
2
Sería útil ver los resultados de los programas. Por ejemplo, ¿'print x [0]' devuelve 25? (Sí, pero debería mostrarse)
Ray Salemi
11
@alexey clear_arecibe una referencia zy almacena esa referencia en x. Luego cambia inmediatamente xpara hacer referencia a una matriz diferente. La zmatriz original se olvida en el alcance de clear_a, pero en realidad no se cambia. Continúa sin cambios al volver al ámbito global. Contraste eso con el clear_bque toma una referencia zy luego opera directamente sobre él, sin nunca crear una nueva matriz o señalar xalgo diferente.
Dave Mankoff
gracias dave! He intentado "crear una nueva matriz" en clear_b, y = x, y: y.pop()y luego llamé clear_b(z), z aún así nos vació ... Así que necesitamos algo como y = list(x)para crear una copia de x (lista (x) se explica aquí )
alexey
run_thread = [True] t= threading.Thread(target=module2.some_method, \ args=(11,1,run_thread)) --do something - and when you want to sop run_thread[0] =False
Alex Punnen
No hay un tipo primitivo en Python, a diferencia de Java. Todos los tipos de objetos de instancia.
Marco Sulla
181

Básicamente, existen tres tipos de 'llamadas a funciones':

  • Pasar por valor
  • Pasar por referencia
  • Pasar por referencia de objeto

Python es un lenguaje de programación PASAR POR OBJETO-REFERENCIA.

En primer lugar, es importante comprender que una variable y el valor de la variable (el objeto) son dos cosas distintas. La variable 'apunta' al objeto. La variable no es el objeto. De nuevo:

LA VARIABLE NO ES EL OBJETO

Ejemplo: en la siguiente línea de código:

>>> x = []

[]es la lista vacía, xes una variable que apunta a la lista vacía, pero en xsí misma no es la lista vacía.

Considere la variable ( x, en el caso anterior) como un cuadro, y 'el valor' de la variable ( []) como el objeto dentro del cuadro.

PASAR POR REFERENCIA DE OBJETO (Caso en Python):

Aquí, "Las referencias a objetos se pasan por valor".

def append_one(li):
    li.append(1)
x = [0]
append_one(x)
print x

Aquí, la declaración x = [0]crea una variable x(caja) que apunta hacia el objeto [0].

En la función que se llama, lise crea una nueva caja . El contenido de lies el MISMO que el contenido de la caja x. Ambas cajas contienen el mismo objeto. Es decir, ambas variables apuntan al mismo objeto en la memoria. Por tanto, cualquier cambio en el objeto apuntado por litambién será reflejado por el objeto apuntado por x.

En conclusión, el resultado del programa anterior será:

[0, 1]

Nota:

Si la variable lise reasigna en la función, liapuntará a un objeto separado en la memoria. xsin embargo, seguirá apuntando al mismo objeto en la memoria al que apuntaba anteriormente.

Ejemplo:

def append_one(li):
    li = [0, 1]
x = [0]
append_one(x)
print x

La salida del programa será:

[0]

PASO POR REFERENCIA:

El cuadro de la función que llama se pasa a la función llamada. Implícitamente, el contenido del cuadro (el valor de la variable) se pasa a la función llamada. Por lo tanto, cualquier cambio en el contenido del cuadro en la función llamada se reflejará en la función llamada.

PASO POR VALOR:

Se crea un nuevo cuadro en la función llamada y se almacenan copias del contenido del cuadro de la función que llama en los nuevos cuadros.

Espero que esto ayude.

Shobhit Verma
fuente
3
¿Podría dar ejemplos de "pasar por referencia" y "pasar por valor" como lo hizo para "pasar por referencia de objeto"?
TJain
@TJain Pass by reference y pass by value se pueden ver en C ++. Esta respuesta da una buena explicación: stackoverflow.com/a/430958/5076583
Shobhit Verma
3
Todavía no entiendo en qué se diferencia esto del paso por referencia. Esta respuesta necesita desesperadamente un ejemplo de cómo un pseudocódigo idéntico produciría diferentes salidas para los 3 paradigmas. Vincular a una respuesta diferente que solo explica la diferencia entre paso por referencia y paso por valor no ayuda.
Jess Riedel
¿No es más simple decir que "todas las variables en Python son punteros " y "todas las variables asignadas a parámetros en una llamada a función se asignan a nuevos punteros?". Si se da cuenta de esto, verá que Python es como Java: simplemente siempre pasa punteros por valor . Vea la respuesta de Kirk Strauser
Marco Sulla
26

Bien, intentaré esto. Python pasa por referencia de objeto, que es diferente de lo que normalmente pensarías como "por referencia" o "por valor". Toma este ejemplo:

def foo(x):
    print x

bar = 'some value'
foo(bar)

Así que está creando un objeto de cadena con el valor 'algún valor' y lo "vincula" a una variable nombrada bar. En C, eso sería similar a barser un puntero a 'algún valor'.

Cuando llamas foo(bar), no estás pasando por barsí mismo. Estás pasando barun valor: un puntero a "algún valor". En ese momento, hay dos "punteros" al mismo objeto de cadena.

Ahora compare eso con:

def foo(x):
    x = 'another value'
    print x

bar = 'some value'
foo(bar)

Aquí es donde radica la diferencia. En la linea:

x = 'another value'

en realidad, no está alterando el contenido de x. De hecho, eso ni siquiera es posible. En su lugar, está creando un nuevo objeto de cadena con el valor 'otro valor'. ¿Ese operador de asignación? No está diciendo "sobrescribir la cosa xque apunta con el nuevo valor". Dice "actualizar xpara apuntar al nuevo objeto en su lugar". Después de esa línea, hay dos objetos de cadena: 'algún valor' ( barapuntándolo) y 'otro valor' ( xapuntándolo).

Esto no es torpe. Cuando comprende cómo funciona, es un sistema eficiente y bellamente elegante.

Kirk Strauser
fuente
3
Pero, ¿qué pasa si quiero que la función altere el contenido de mi variable? ¿Supongo que no hay forma de hacer esto?
tortuga acuática
1
Puede modificar el objeto al que apunta el nombre , por ejemplo, agregando a una lista, si ese objeto es mutable. No hay semántica de Python para decir "alterar el espacio de nombres del ámbito que me llamó para que el nombre en mi lista de argumentos ahora apunte a algo diferente de cuando me llamó".
Kirk Strauser
2
Efectivamente, lo que estoy preguntando es si hay una manera simple de hacer que Python no cree un nuevo puntero al mismo objeto, sino que use el puntero original. Entiendo que no hay forma de hacer esto. Tomo de otras respuestas que la solución normal para lograr lo que hace pasar por referencia es solo devolver más parámetros. Si ese es el caso, veo cierta validez en el argumento de que a, b, c, d, e = foo (a, b, c, d, e) es un poco más torpe que solo foo (a, b, c, Delaware).
tortuga acuática
2
@aquirdturtle Esto no tiene nada que ver con el hecho de que se crea un nuevo puntero. Tienes que devolver un nuevo objeto porque las strentradas, los tuplesy otros objetos son inmutables . Si pasa un objeto mutable, como a list, puede cambiarlo dentro de la función, sin la necesidad de devolverlo, porque todos los parámetros de la función son punteros a los mismos objetos que pasó.
Marco Sulla
23

Espero que la siguiente descripción lo resuma bien:

Hay dos cosas a considerar aquí: variables y objetos.

  1. Si está pasando una variable, se pasa por valor, lo que significa que los cambios realizados en la variable dentro de la función son locales para esa función y, por lo tanto, no se reflejarán globalmente. Este es más un comportamiento parecido a una 'C'.

Ejemplo:

def changeval( myvar ):
   myvar = 20; 
   print "values inside the function: ", myvar
   return

myvar = 10;
changeval( myvar );
print "values outside the function: ", myvar

O / P:

values inside the function:  20 
values outside the function:  10
  1. Si está pasando las variables empaquetadas dentro de un objeto mutable, como una lista, entonces los cambios realizados en el objeto se reflejan globalmente siempre que el objeto no se reasigne.

Ejemplo:

def changelist( mylist ):
   mylist2=['a'];
   mylist.append(mylist2);
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  [1, 2, 3, ['a']]
values outside the function:  [1, 2, 3, ['a']]
  1. Ahora considere el caso en el que se reasigna el objeto. En este caso, el objeto se refiere a una nueva ubicación de memoria que es local a la función en la que esto sucede y, por lo tanto, no se refleja globalmente.

Ejemplo:

def changelist( mylist ):
   mylist=['a'];
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  ['a']
values outside the function:  [1, 2, 3]
attaboy182
fuente
2
¡Este es el mejor resumen! ^^^ MEJOR RESUMEN AQUÍ ^^^ ¡Todos leyeron esta respuesta! 1. pasar variables simples 2. pasar la referencia de objeto que se modifica 3. pasar la referencia de objeto que se reasigna en función
gaoithe
3
Esta respuesta es muy confusa. Python no pasa diferentes tipos de valores de manera diferente: todos los argumentos de función en Python reciben sus valores por asignación y se comportan exactamente de la misma manera en que la asignación se comporta en cualquier otro lugar del lenguaje. ("Aunque de esa manera puede que no sea obvio al principio a menos que seas holandés".)
Daniel Pryden
9

Python no es paso por valor ni paso por referencia. Es más "las referencias de objeto se pasan por valor" como se describe aquí :

  1. He aquí por qué no se transmite por valor. Porque

    def append(list):
        list.append(1)
    
    list = [0]
    reassign(list)
    append(list)
    

devuelve [0,1] que muestra que se pasó claramente algún tipo de referencia ya que el valor de paso por valor no permite que una función altere el alcance principal en absoluto .

Entonces parece pasar por referencia, ¿eh? No

  1. He aquí por qué no se pasa por referencia. Porque

    def reassign(list):
      list = [0, 1]
    
    list = [0]
    reassign(list)
    print list
    

devuelve [0] que muestra que la referencia original se destruyó cuando se reasignó la lista. pass-by-reference habría devuelto [0,1] .

Para obtener más información, consulte aquí :

Si desea que su función no manipule fuera del alcance, necesita hacer una copia de los parámetros de entrada que crea un nuevo objeto.

from copy import copy

def append(list):
  list2 = copy(list)
  list2.append(1)
  print list2

list = [0]
append(list)
print list
Philip Dodson
fuente
4

Técnicamente, Python no pasa argumentos por valor: todos por referencia. Pero ... dado que Python tiene dos tipos de objetos: inmutables y mutables, esto es lo que sucede:

  • Los argumentos inmutables se pasan efectivamente por valor : cadena, entero, tupla son todos tipos de objetos inmutables. Aunque técnicamente se "pasan por referencia" (como todos los parámetros), dado que no puede cambiarlos en el lugar dentro de la función, se ve / se comporta como si se pasara por valor.

  • Los argumentos mutables se pasan efectivamente por referencia : las listas o diccionarios se pasan por sus punteros. Cualquier cambio en el lugar dentro de la función como (agregar o eliminar) afectará al objeto original.

Así es como está diseñado Python: no hay copias y todas se pasan por referencia. Puede pasar explícitamente una copia.

def sort(array):
    # do sort
    return array

data = [1, 2, 3]
sort(data[:]) # here you passed a copy

El último punto que me gustaría mencionar es que una función tiene su propio alcance.

def do_any_stuff_to_these_objects(a, b): 
    a = a * 2 
    del b['last_name']

number = 1 # immutable
hashmap = {'first_name' : 'john', 'last_name': 'legend'} # mutable
do_any_stuff_to_these_objects(number, hashmap) 
print(number) # 1 , oh  it should be 2 ! no a is changed inisde the function scope
print(hashmap) # {'first_name': 'john'}
Yasser Sinjab
fuente
1
"Los argumentos inmutables se pasan de manera efectiva por valor: la cadena, el entero, la tupla se pasan por referencia" esta oración tiene una contradicción propia
eral
@eral, si comprende el contexto completo, no se contradice. Lo edité para dejar esto más claro.
Watki02
2

Así que este es un punto un poco sutil, porque mientras Python solo pasa variables por valor, cada variable en Python es una referencia. Si desea poder cambiar sus valores con una llamada de función, lo que necesita es un objeto mutable. Por ejemplo:

l = [0]

def set_3(x):
    x[0] = 3

set_3(l)
print(l[0])

En el código anterior, la función modifica el contenido de un objeto List (que es mutable), por lo que la salida es 3 en lugar de 0.

Escribo esta respuesta solo para ilustrar lo que significa 'por valor' en Python. El código anterior es de mal estilo, y si realmente desea mutar sus valores, debe escribir una clase y llamar a métodos dentro de esa clase, como sugiere MPX.

Isaac
fuente
esto responde a la mitad de la pregunta, entonces, ¿cómo escribiría la misma función set_3 (x) para que no cambie el valor de l pero devuelva una nueva lista que se ha cambiado?
Timothy Lawman
Simplemente haga una nueva lista y modifíquela:y = [a for a in x]; y[0] = 3; return y
Isaac
todo implica listas y copias de listas que de alguna manera responden, pero ¿cómo puedo hacerlo sin tener que convertir un int en una lista o es esto lo mejor que tenemos?
Timothy Lawman
No puede modificar un argumento primitivo como un int. Tienes que ponerlo en algún tipo de recipiente. Mi ejemplo lo puso en una lista. También puedes ponerlo en una clase o en un diccionario. Sin embargo, en realidad, debería reasignarlo fuera de una función con algo como x = foo(x).
Isaac
0

La respuesta dada es

def set_4(x):
   y = []
   for i in x:
       y.append(i)
   y[0] = 4
   return y

y

l = [0]

def set_3(x):
     x[0] = 3

set_3(l)
print(l[0])

cuál es la mejor respuesta en la medida en que hace lo que dice en la pregunta. Sin embargo, parece una forma muy torpe en comparación con VB o Pascal ¿Es el mejor método que tenemos?

No solo es torpe, sino que implica mutar el parámetro original de alguna manera manualmente, por ejemplo, cambiando el parámetro original a una lista: o copiándolo en otra lista en lugar de simplemente decir: "use este parámetro como un valor" o "use este como una referencia". ¿Podría la respuesta simple ser que no hay una palabra reservada para esto, pero estas son excelentes soluciones?

Timothy Lawman
fuente
0

Considere que la variable es un cuadro y el valor al que apunta es la "cosa" dentro del cuadro:

1. Pasar por referencia: la función comparte la misma caja y, por tanto, la cosa que está dentro también.

2. Pasar por valor: la función crea una nueva caja, una réplica de la anterior, incluyendo una copia de lo que esté dentro. P.ej. Java : las funciones crean una copia de la caja y lo que está dentro de ella, que puede ser: una primitiva / una referencia a un objeto. (tenga en cuenta que la referencia copiada en el nuevo cuadro y el original todavía apuntan al mismo objeto, aquí la referencia ES lo que está dentro del cuadro, no el objeto al que apunta)

3. Pasar por referencia de objeto : la función crea una caja, pero encierra lo mismo que la caja inicial. Entonces en Python :

a) si lo que está dentro de dicha caja es mutable , los cambios realizados se reflejarán en la caja original (por ejemplo, listas)

b) si la cosa es inmutable (como cadenas de Python y tipos numéricos), entonces el cuadro dentro de la función contendrá lo mismo HASTA que intente cambiar su valor. Una vez cambiado, lo que está en el cuadro de la función es algo totalmente nuevo en comparación con el original. Por lo tanto, id () para esa caja ahora dará la identidad de lo nuevo que encierra .

Neeraj Menon
fuente
0
class demoClass:
    x = 4
    y = 3
foo1 = demoClass()
foo1.x = 2
foo2 = demoClass()
foo2.y = 5
def mySquare(myObj):
    myObj.x = myObj.x**2
    myObj.y = myObj.y**2
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
mySquare(foo1)
mySquare(foo2)
print('After square:')
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
Zsolt Ventilla
fuente
-2

En Python, el paso por referencia o por valor tiene que ver con cuáles son los objetos reales que está pasando. Entonces, si está pasando una lista, por ejemplo, entonces en realidad hace este paso por referencia, ya que la lista es un objeto mutable. Por lo tanto, está pasando un puntero a la función y puede modificar el objeto (lista) en el cuerpo de la función.

Cuando pasa una cadena, esta transmisión se realiza por valor, por lo que se crea un nuevo objeto de cadena y cuando la función termina, se destruye. Entonces, todo tiene que ver con objetos mutables e inmutables.

jkalivas
fuente
1
Eso es completamente incorrecto. Python siempre pasa por "referencia de objeto" (que es diferente de la referencia de variable).
Kirk Strauser
Me refería a la analogía objetos mutables - pasar objetos de referencia e inmutables - pase por value.It es perfecto válido lo que soy saying.You probablemente no entendió lo que estaba diciendo
jkalivas
1
Lo atrapé y está mal. No tiene nada que ver con si un objeto es mutable o inmutable; siempre se hace de la misma manera.
Kirk Strauser
-2

Python ya llama por ref ..

tomemos un ejemplo:

  def foo(var):
      print(hex(id(var)))


  x = 1 # any value
  print(hex(id(x))) # I think the id() give the ref... 
  foo(x)

Salida

  0x50d43700 #with you might give another hex number deppend on your memory 
  0x50d43700
Fantasma de casper
fuente
No, no es ciertodef foo(var): print(id(var)) var = 1234 print(id(var)) x = 123 print(id(x)) # I think the id() give the ref... foo(x) print(id(x))
Zohaib Ijaz