Pregunta ¿Cómo paso una variable por referencia?


La documentación de Python no parece clara sobre si los parámetros se pasan por referencia o valor, y el siguiente código produce el valor sin modificar 'Original'

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

¿Hay algo que pueda hacer para pasar la variable por referencia real?


2053
2018-06-12 10:23


origen


Respuestas:


Los argumentos son pasado por la asignación. La razón de esto es doble:

  1. el parámetro pasado es en realidad un referencia a un objeto (pero la referencia se pasa por valor)
  2. algunos tipos de datos son mutables, pero otros no

Asi que:

  • Si pasa un mudable Objeto en un método, el método obtiene una referencia a ese mismo objeto y puedes mutarlo para tu deleite, pero si vuelves a vincular la referencia en el método, el alcance externo no sabrá nada al respecto, y una vez que hayas terminado, la referencia externa seguirá apuntando al objeto original.

  • Si pasas un inmutable objetar a un método, aún no puede volver a enlazar la referencia externa, y ni siquiera puede mutar el objeto.

Para hacerlo aún más claro, vamos a tener algunos ejemplos.

Lista - un tipo mutable

Intentemos modificar la lista que se pasó a un método:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Salida:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Dado que el parámetro pasado es una referencia a outer_list, no una copia de ella, podemos usar los métodos de la lista de mutación para cambiarla y hacer que los cambios se reflejen en el alcance externo.

Ahora veamos qué sucede cuando tratamos de cambiar la referencia que se pasó como parámetro:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Salida:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Desde el the_list el parámetro se pasó por valor, asignarle una nueva lista no tuvo ningún efecto que pudiera ver el código fuera del método. los the_list era una copia del outer_list referencia, y tuvimos the_list señalar una nueva lista, pero no había forma de cambiar outer_list puntiagudo.

Cadena - un tipo inmutable

Es inmutable, por lo que no hay nada que podamos hacer para cambiar el contenido de la cadena

Ahora, intentemos cambiar la referencia

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Salida:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

De nuevo, ya que the_string el parámetro se pasó por valor, asignarle una nueva cadena no tuvo ningún efecto que pudiera ver el código fuera del método. los the_string era una copia del outer_string referencia, y tuvimos the_string señalar una nueva cadena, pero no había forma de cambiar donde outer_string puntiagudo.

Espero que esto aclare un poco las cosas.

EDITAR: Se ha notado que esto no responde la pregunta que originalmente le hizo @David: "¿Hay algo que pueda hacer para pasar la variable por referencia real?". Vamos a trabajar en eso.

¿Cómo lo solucionamos?

Como muestra la respuesta de @ Andrea, podrías devolver el nuevo valor. Esto no cambia la forma en que se transmiten las cosas, pero le permite volver a sacar la información que desea:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Si realmente desea evitar el uso de un valor de retorno, puede crear una clase para mantener su valor y pasarlo a la función o usar una clase existente, como una lista:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Aunque esto parece un poco engorroso.


2221
2018-06-12 11:18



El problema proviene de un malentendido de qué variables están en Python. Si está acostumbrado a la mayoría de los lenguajes tradicionales, tiene un modelo mental de lo que sucede en la siguiente secuencia:

a = 1
a = 2

Tu crees que a es una ubicación de memoria que almacena el valor 1, luego se actualiza para almacenar el valor 2. Así no es como funcionan las cosas en Python. Más bien, a comienza como una referencia a un objeto con el valor 1, luego se reasigna como una referencia a un objeto con el valor 2. Esos dos objetos pueden continuar coexistiendo aunque a ya no se refiere al primero; de hecho, pueden ser compartidos por cualquier cantidad de otras referencias dentro del programa.

Cuando llama a una función con un parámetro, se crea una nueva referencia que hace referencia al objeto pasado. Esto es independiente de la referencia que se utilizó en la llamada a la función, por lo que no hay forma de actualizar esa referencia y hacer que se refiera a un nuevo objeto. En tu ejemplo:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variable es una referencia al objeto cadena 'Original'. Cuando usted llama Change creas una segunda referencia var al objeto. Dentro de la función, reasignas la referencia var a un objeto de cadena diferente 'Changed', pero la referencia self.variable está separado y no cambia.

La única forma de evitar esto es pasar un objeto mutable. Como ambas referencias hacen referencia al mismo objeto, cualquier cambio en el objeto se refleja en ambos lugares.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

529
2017-11-15 17:45



Encontré las otras respuestas bastante largas y complicadas, así que creé este diagrama simple para explicar la forma en que Python trata las variables y los parámetros. enter image description here


242
2017-09-04 16:05



No es ni pass-by-value ni pass-by-reference, es llamada por objeto. Vea esto, por Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

Aquí hay una cita significativa:

"... variables [nombres] son no objetos; no pueden ser denotados por otras variables o referidos por objetos ".

En tu ejemplo, cuando el Change método se llama - a espacio de nombres es creado para eso; y var se convierte en un nombre, dentro de ese espacio de nombres, para el objeto de cadena 'Original'. Ese objeto tiene un nombre en dos espacios de nombres. Siguiente, var = 'Changed' se une var a un nuevo objeto de cadena, y por lo tanto, el espacio de nombres del método se olvida de 'Original'. Finalmente, ese espacio de nombre es olvidado, y la cadena 'Changed' junto con eso.


219
2018-06-12 12:55



Piensa en cosas que pasan por asignación en lugar de por referencia / por valor. De esa manera, siempre está claro, lo que está sucediendo mientras entiendas lo que sucede durante la asignación normal.

Por lo tanto, al pasar una lista a una función / método, la lista se asigna al nombre del parámetro. Agregar a la lista dará como resultado la modificación de la lista. Reasignar la lista dentro la función no cambiará la lista original, ya que:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Como los tipos inmutables no se pueden modificar, parecer como pasar por valor: pasar un int a una función significa asignar el parámetro int al parámetro functions. Solo puede reasignar eso, pero no cambiará el valor de las variables de origen.


142
2018-06-12 12:17



Effbot (alias Fredrik Lundh) ha descrito el estilo de paso variable de Python como llamada por objeto: http://effbot.org/zone/call-by-object.htm

Los objetos se asignan en el montón y los punteros a ellos se pueden pasar por cualquier lugar.

  • Cuando haces una tarea como x = 1000, se crea una entrada de diccionario que mapea la cadena "x" en el espacio de nombre actual a un puntero al objeto entero que contiene mil.

  • Cuando actualiza "x" con x = 2000, se crea un nuevo objeto entero y el diccionario se actualiza para apuntar al nuevo objeto. El antiguo objeto de mil no cambia (y puede o no estar vivo dependiendo de si algo más se refiere al objeto).

  • Cuando haces una nueva tarea como y = x, se crea una nueva entrada de diccionario "y" que apunta al mismo objeto que la entrada para "x".

  • Los objetos como cadenas y enteros son inmutable. Esto simplemente significa que no hay métodos que puedan cambiar el objeto después de que se haya creado. Por ejemplo, una vez que se crea el objeto entero mil, nunca cambiará. Las matemáticas se hacen creando nuevos objetos enteros.

  • Objetos como listas son mudable. Esto significa que el contenido del objeto puede ser cambiado por cualquier cosa que apunte al objeto. Por ejemplo, x = []; y = x; x.append(10); print y imprimirá [10]. La lista vacía fue creada. Tanto "x" como "y" apuntan a la misma lista. los adjuntar el método muta (actualiza) el objeto de la lista (como agregar un registro a una base de datos) y el resultado es visible tanto para "x" como para "y" (tal como una actualización de base de datos sería visible para cada conexión a esa base de datos).

Espero que aclare el problema para ti.


53
2018-03-29 04:41



Técnicamente, Python siempre usa pasar por valores de referencia. Voy a repetir mi otra respuesta para apoyar mi declaración.

Python siempre usa valores de paso por referencia. No hay ninguna excepción Cualquier asignación variable significa copiar el valor de referencia. Sin excepción. Cualquier variable es el nombre vinculado al valor de referencia. Siempre.

Puede pensar en un valor de referencia como la dirección del objeto de destino. La dirección se desreferencia automáticamente cuando se usa. De esta manera, trabajando con el valor de referencia, parece que trabajas directamente con el objeto de destino. Pero siempre hay una referencia en el medio, un paso más para saltar al objetivo.

Aquí está el ejemplo que demuestra que Python usa pasar por referencia:

Illustrated example of passing the argument

Si el argumento fue pasado por valor, el externo lst no pudo ser modificado El verde son los objetos objetivo (el negro es el valor almacenado en el interior, el rojo es el tipo de objeto), el amarillo es la memoria con el valor de referencia dentro, dibujado como la flecha. La flecha azul sólida es el valor de referencia que se pasó a la función (a través de la ruta de flecha azul discontinua). El amarillo oscuro feo es el diccionario interno. (En realidad podría dibujarse también como una elipse verde. El color y la forma solo dicen que es interna).

Puedes usar el id() función incorporada para conocer cuál es el valor de referencia (es decir, la dirección del objeto de destino).

En los lenguajes compilados, una variable es un espacio de memoria que puede capturar el valor del tipo. En Python, una variable es un nombre (capturado internamente como una cadena) vinculado a la variable de referencia que contiene el valor de referencia para el objeto de destino. El nombre de la variable es la clave en el diccionario interno, la parte de valor de ese elemento de diccionario almacena el valor de referencia para el destino.

Los valores de referencia están ocultos en Python. No hay ningún tipo de usuario explícito para almacenar el valor de referencia. Sin embargo, puede usar un elemento de lista (o elemento en cualquier otro tipo de contenedor adecuado) como variable de referencia, ya que todos los contenedores almacenan los elementos también como referencias a los objetos de destino. En otras palabras, los elementos en realidad no están contenidos dentro del contenedor; solo las referencias a los elementos son.


53
2017-09-15 18:53



Un truco simple que normalmente uso es simplemente envolverlo en una lista:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Sí, sé que esto puede ser un inconveniente, pero a veces es lo suficientemente simple para hacer esto).


36
2017-08-05 22:52



(Editar - Blair ha actualizado su enormemente popular respuesta para que ahora sea precisa)

Creo que es importante tener en cuenta que la publicación actual con la mayoría de los votos (por Blair Conrad), aunque es correcta con respecto a su resultado, es engañosa y está en el límite incorrecto según sus definiciones. Si bien hay muchos idiomas (como C) que permiten al usuario pasar por referencia o pasar por valor, Python no es uno de ellos.

La respuesta de David Cournapeau apunta a la respuesta real y explica por qué el comportamiento en la publicación de Blair Conrad parece ser correcto, mientras que las definiciones no lo son.

En la medida en que Python pase por el valor, todos los idiomas pasan por valor, ya que se debe enviar algún dato (ya sea un "valor" o una "referencia"). Sin embargo, eso no significa que Python pase por valor en el sentido en que un programador de C lo piense.

Si quieres el comportamiento, la respuesta de Blair Conrad está bien. Pero si quiere saber los aspectos prácticos de por qué Python no es pasar por valor o pasar por referencia, lea la respuesta de David Cournapeau.


34
2018-06-27 12:33



No hay variables en Python

La clave para entender el paso de parámetros es dejar de pensar en "variables". Hay nombres y objetos en Python y juntos aparecen como variables, pero es útil distinguir siempre las tres.

  1. Python tiene nombres y objetos.
  2. La asignación vincula un nombre a un objeto.
  3. Pasar un argumento a una función también vincula un nombre (el nombre del parámetro de la función) a un objeto.

Eso es todo lo que hay que hacer. La mutabilidad es irrelevante para esta pregunta.

Ejemplo:

a = 1

Esto une el nombre a a un objeto de tipo entero que contiene el valor 1.

b = x

Esto une el nombre b al mismo objeto que el nombre x está actualmente obligado a. Luego, el nombre b no tiene nada que ver con el nombre x nunca más.

Ver secciones 3.1 y 4.2 en la referencia de lenguaje Python 3.


Entonces, en el código que se muestra en la pregunta, la declaración self.Change(self.variable) vincula el nombre var (en el alcance de la función Change) al objeto que contiene el valor 'Original' y la tarea var = 'Changed' (en el cuerpo de la función Change) asigna el mismo nombre de nuevo: a algún otro objeto (que también contiene una cadena, pero podría haber sido algo completamente diferente).


25
2018-02-11 11:29