¿Cómo paso una variable por referencia?

Resuelto David Sykes asked hace 15 años • 41 respuestas

Escribí esta clase para probar:

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

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

Cuando intenté crear una instancia, el resultado fue Original. Entonces parece que los parámetros en Python se pasan por valor. ¿Es eso correcto? ¿Cómo puedo modificar el código para obtener el efecto de paso por referencia, de modo que el resultado sea Changed?


A veces la gente se sorprende de que un código como x = 1, donde xes el nombre de un parámetro, no afecte el argumento de la persona que llama, pero el código como x[0] = 1sí sí. Esto sucede porque la asignación de elementos y la asignación de sectores son formas de mutar un objeto existente, en lugar de reasignar una variable, a pesar de la =sintaxis. Consulte ¿Por qué una función puede modificar algunos argumentos tal como los percibe la persona que llama, pero no otros? para detalles.

Consulte también ¿Cuál es la diferencia entre pasar por referencia y pasar por valor? para una importante discusión terminológica independiente del idioma.

David Sykes avatar Jun 12 '09 17:06 David Sykes
Aceptado

Los argumentos se pasan por asignación . La razón detrás de esto es doble:

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

Entonces:

  • Si pasa un objeto mutable a un método, el método obtiene una referencia a ese mismo objeto y puede mutarlo para su deleite, pero si vuelve a vincular la referencia en el método, el alcance externo no sabrá nada al respecto, y después Ya terminaste, la referencia externa seguirá apuntando al objeto original.

  • Si pasa un objeto inmutable a un método, aún no podrá volver a vincular la referencia externa y ni siquiera podrá mutar el objeto.

Para que quede aún más claro, veamos 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)

Producción:

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 outer_list, no una copia del mismo, podemos usar los métodos de lista mutante para cambiarlo y reflejar los cambios en el alcance externo.

Ahora veamos qué sucede cuando intentamos 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)

Producción:

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']

Dado que el the_listparámetro se pasó por valor, asignarle una nueva lista no tuvo ningún efecto que el código fuera del método pudiera ver. Era the_listuna copia de la outer_listreferencia y habíamos the_listseñalado una nueva lista, pero no había forma de cambiar el lugar outer_listseñalado.

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)

Producción:

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

Nuevamente, dado que el the_stringparámetro se pasó por valor, asignarle una nueva cadena no tuvo ningún efecto que el código fuera del método pudiera ver. Era the_stringuna copia de la outer_stringreferencia y habíamos the_stringseñalado una nueva cadena, pero no había forma de cambiar el lugar outer_stringseñalado.

Espero que esto aclare un poco las cosas.

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

¿Cómo solucionamos esto?

Como muestra la respuesta de @Andrea, podrías devolver el nuevo valor. Esto no cambia la forma en que se pasan las cosas, pero le permite recuperar 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 contener 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 parezca un poco engorroso.

Blair Conrad avatar Jun 12 '2009 11:06 Blair Conrad

El problema surge de una mala comprensión de qué variables hay en Python. Si estás acostumbrado a la mayoría de los idiomas tradicionales, tienes un modelo mental de lo que sucede en la siguiente secuencia:

a = 1
a = 2

Usted cree que aes una ubicación de memoria que almacena el valor 1y luego se actualiza para almacenar el valor 2. Así no es como funcionan las cosas en Python. Más bien, acomienza como una referencia a un objeto con el valor 1y luego se reasigna como una referencia a un objeto con el valor 2. Esos dos objetos pueden seguir coexistiendo aunque aya no se refieran al primero; de hecho, pueden ser compartidos por otras referencias dentro del programa.

Cuando llamas 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 usó en la llamada a la función, por lo que no hay forma de actualizar esa referencia y hacer que haga referencia a un nuevo objeto. En tu ejemplo:

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

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

self.variablees una referencia al objeto de cadena 'Original'. Cuando llamas, Changecreas una segunda referencia varal objeto. Dentro de la función reasignas la referencia vara un objeto de cadena diferente 'Changed', pero la referencia self.variableestá separada y no cambia.

La única forma de evitar esto es pasar un objeto mutable. Como ambas referencias hacen referencia al mismo objeto, cualquier cambio realizado 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'
Mark Ransom avatar Nov 15 '2011 17:11 Mark Ransom

Las otras respuestas me parecieron bastante largas y complicadas, así que creé este diagrama simple para explicar la forma en que Python trata las variables y los parámetros. ingrese la descripción de la imagen aquí

Zenadix avatar Sep 04 '2014 16:09 Zenadix

No es paso por valor ni paso por referencia: es llamada por objeto. Vea esto, de Fredrik Lundh:

Llamada por objeto

He aquí una cita significativa:

"...las variables [nombres] no son objetos; no pueden ser denotadas por otras variables ni referidas por objetos".

En su ejemplo, cuando Changese llama al método, se crea un espacio de nombres para él; y varse convierte en un nombre, dentro de ese espacio de nombres, para el objeto de cadena 'Original'. Ese objeto tiene entonces un nombre en dos espacios de nombres. A continuación, var = 'Changed'se vincula vara un nuevo objeto de cadena y, por lo tanto, el espacio de nombres del método se olvida 'Original'. Finalmente, se olvida ese espacio de nombres y la cadena 'Changed'junto con él.

David Cournapeau avatar Jun 12 '2009 12:06 David Cournapeau

Piense en cosas que se pasan por asignación en lugar de por referencia/por valor. De esa manera, siempre estará claro lo que está sucediendo, siempre y cuando comprenda lo que sucede durante la tarea normal.

Entonces, 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 que la lista se modifique. Reasignar la lista dentro de 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']

Dado que los tipos inmutables no se pueden modificar, parece que se pasan por valor: pasar un int a una función significa asignar el int al parámetro de la función. Solo puedes reasignar eso, pero no cambiará el valor de las variables originales.

Daren Thomas avatar Jun 12 '2009 12:06 Daren Thomas

No hay variables en Python

La clave para comprender 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 los 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 vincula el nombre aa un objeto de tipo entero que contiene el valor 1.

b = x

Esto vincula el nombre bal mismo objeto al que xestá vinculado actualmente el nombre. Después, el nombre ya bno tiene nada que ver con el nombre x.

Consulte las secciones 3.1 y 4.2 en la referencia del lenguaje Python 3.

Cómo leer el ejemplo en la pregunta.

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 asignación var = 'Changed'(en el cuerpo de la función Change) asigna ese mismo nombre nuevamente: a algún otro objeto (eso sucede sostener una cuerda también, pero podría haber sido algo completamente distinto).

Cómo pasar por referencia

Entonces, si lo que desea cambiar es un objeto mutable, no hay problema, ya que todo se pasa efectivamente por referencia.

Si es un objeto inmutable (por ejemplo, un bool, un número, una cadena), el camino a seguir es envolverlo en un objeto mutable.
La solución rápida y sencilla para esto es una lista de un elemento (en lugar de self.variable, pasar [self.variable]y en la función modificar var[0]).
El enfoque más pitónico sería introducir una clase trivial de un solo atributo. La función recibe una instancia de la clase y manipula el atributo.

Lutz Prechelt avatar Feb 11 '2014 11:02 Lutz Prechelt