¿Python: Cómo pasar una variable por referencia?

La documentación de Python parece clara acerca de si los parámetros se pasan por referencia o valor, y el código siguiente produce el valor invariable ‘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 puedo hacer para pasar la variable por referencia real?

Entiendo que el quid es que se pasa una copia de una referencia. Si asigna esa copia, como en mi ejemplo, entonces pierdes la referencia a la original y permanece sin cambios. Si, sin embargo, ‘usa’ esa referencia, por ejemplo anexar en una lista aprobada y, a continuación, se cambia el original.

Los parámetros se pasan por valor. La razón de personas son confundidas por el comportamiento es doble:

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

Por lo tanto, si se pasa un objeto mutable en un método, el método obtiene una referencia a que mismo objeto y puede mutar para deleite de su corazón, pero si usted volver a enlazar la referencia en el método, el ámbito externo saber nada al respecto, y después de que haya terminado, la referencia externa aún pueden apuntar al objeto original. Si pasa un objeto inmutable a un método, usted todavía no puede enlazar la referencia externa, e incluso no puede mutar el objeto.

Bueno, esto es un poco confuso. Vamos a tener algunos ejemplos.

Lista – un tipo mutable

Vamos a intentar modificar la lista que se ha pasado 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 en es una referencia a outer_list , no una copia del mismo, podemos utilizar los métodos de lista mutante para cambiarlo y han reflejado los cambios en el ámbito externo.

Ahora vamos a ver lo que sucede cuando tratamos de cambiar la referencia que se pasa como un 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 la the_list parámetro se pasa por valor, asignar una nueva lista que no tuvo ningún efecto que podría ver el código fuera del método. El the_list era una copia de la outer_list referencia y tuvimos the_list apuntan a una nueva lista, pero no hay forma de cambiar donde outer_list apuntado.

Cadena – un tipo inmutable

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

Ahora, vamos a intentar 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

Una vez más, desde la the_string parámetro se pasa por valor, asignar una nueva cadena que no tuvo ningún efecto que podría ver el código fuera del método. El the_string era una copia de la outer_string referencia y tuvimos the_string apuntan a una nueva lista, pero no hay forma de cambiar donde outer_string apuntado.

Espero que esto aclara las cosas un poco.

Pero,  “hay algo que puedo hacer para pasar la variable por referencia real?”. Vamos a trabajar en ello.

¿Cómo conseguimos todo esto?

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 devuelto, puede crear una clase para mantener su valor y pasar a la función o utilizar 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.

Piense en cosas que se pasan por asignación , en lugar de referencia/por valor. De este modo, es siempre clara, lo que está sucediendo como usted entiende lo que sucede durante la asignación normal.

Así, al pasar una lista a un método de función, la lista se asigna al nombre del parámetro. Agregando a la lista resultará en la lista de modificaciones. Reasignar la lista dentro de la función no cambiará la lista original, desde:

Piense en cosas que se pasan por asignación , en lugar de referencia/por valor. De este modo, es siempre clara, lo que está sucediendo como usted entiende lo que sucede durante la asignación normal.

Así, al pasar una lista a un método de función, la lista se asigna al nombre del parámetro. Agregando a la lista resultará en la lista de modificaciones. Reasignar la lista dentro de la función no cambiará la lista original, desde:

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

Ya no pueden modificarse los tipos inmutables, parece como que se pasan por valor – pasar un int en una función significa asignar el int para el parámetro de funciones. Sólo puede reasignar, pero no cambia el valor de las variables originial.

El problema viene de una mala interpretación de lo que son variables en Python. Si usted está acostumbrado a los lenguajes más tradicionales, tiene un modelo mental de lo que sucede en la secuencia siguiente:

a = 1
a = 2

Crees que a es una localidad de memoria que almacena el valor 1 , a continuación, se actualiza para almacenar el valor 2 . Eso es no funcionan las cosas en Python. Por el contrario, a comienza como una referencia a un objeto con el valor 1 , y luego obtiene reasignado como una referencia a un objeto con el valor 2 . Esos dos objetos, podrán coexistir aunque a no se refiere a la primera ya, de hecho pueden ser compartidas por varias otras referencias dentro del programa.

Al llamar a una función con un parámetro, se crea una nueva referencia que hace referencia al objeto que se pasa en. Esto es independiente de la referencia que se utilizó en la llamada de función, por lo que hay de ninguna manera a actualización que referencia y hacen referencia a un objeto nuevo. En el ejemplo:

self.variable = 'Original'
self.Change(self.variable)

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

self.variablees una referencia al objeto string 'Original' . Cuando usted llame Change crear una segunda referencia var al objeto. Dentro de la función se reasigna la referencia var a un objeto de cadena diferentes 'Changed' , pero la referencia self.variable es independiente y no cambia.

La única manera de evitar esto es pasar un objeto mutable. Porque tanto las referencias al mismo objeto, cualquier cambio en el objeto se refleja en ambos lugares.

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

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

Un simple truco que utilizo normalmente es sólo envuélvalo en una lista:

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

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

(Sí sé esto puede ser incómodo, pero a veces es lo suficientemente simple como hacer esto.)

También puede modificar alguna variable haciendo global en lugar de pasar el valor por referencia

class test:
    def __init__(self):
        global variable
        variable='Original'
        print variable
        self.ChangeTheVariable()
        print variable
    def ChangeTheVariable(self):
        global variable
        variable='Changed'
 



2 thoughts on “¿Python: Cómo pasar una variable por referencia?

  1. Según tengo entendido en Python todas las variables se pasan por referencia, ahora si usted reasigna la variable en la función que la recibe, pues se crea una nueva variable local y se pierde la referencia recibida. Esto es así siempre. Ahora bien, existen tipos de datos mutables (listas, diccionarios, etc.) que si los pasamos a una función entonces podemos modificar los elementos que los componen, pero si usted reasigna la variable completa, entonces se pierde la referencia invariablemente. Por lo que concluimos que todas las variables en Python se pasan por referencia, nunca por valor.

    var1 = [1, 2, 3]
    def func(var):
    print(var)
    var=[] # Reasigno y pierdo la referencia
    print(var)

    func(var1)

    print(var1) # var1 se mantiene invariable

    def func2(var):
    print(var)
    var.append(4) # Modifico los elementos de la lista (tipo mutable)
    print(var)

    func2(var1)

    print(var1) # y no pierdo la referencia

    saludos,
    lpozo

    1. Amigo primero que todo gracias por aportar con tu comentario al post.

      En todas partes nos dicen que en Python el paso de parámetros se hace por referencia, y así es, lo que pasa es que si dentro de la función se intenta hacer una modificación (mediante una asignación) a un parámetro formal, lo que hace Python es crear una copia de dicha variable y hace el cambio sobre ésta, por lo que el cambio no se realiza realmente en el parámetro real. En el caso de las listas, por ejemplo, el manual dice que si los cambios se realizan mediante “métodos” de la clase lista, no se realiza esa copia y por tanto el cambio sí que se realiza sobre el parámetro real. La pregunta es ¿y cómo paso un entero, por ejemplo, por referencia?

      Python “siempre” pasa objetos por referencia. Nunca utiliza parámetros por valor.

      Generalizar este concepto a todo el entorno es una ventaja para la programación orientada a objetos (por ej, al modificar el atributo “altura” a una instancia de Persona todos los que “apunten” a esa instancia podrán usar la modificacion en forma automatica.)

      Es cierto. En realidad en C hay que hacer un continuo seguimiento de punteros cuando se programa para asegurarse de que uno esta haciendo lo que realmente quiere hacer. Python resulta mucho más intuitivo y legible. En definitiva más humano.

      Por otro lado para la programación “normal” sería un lío si alguna funcion modificara el valor de la instancia “5” y todo en el resto del programa cada instancia paso a ser un 42.

      Python mantiene el “todo por referencias” y agrega el concepto de objetos mutables e inmutables.
      Los tipos de datos (ej. los escalares) que en otros lenguajes se pasarían por valor en python son inmutables.
      La inmutabilidad de un tipo de datos en python implica que cualquier operación sobre una instancia de ese tipo que modifique alguno de sus atributos genera automagicamente una copia preservando el original.

      >>> c
      3030232430294L
      >>> def f(n):
      … return n

      >>> f(c)
      3030232430294L
      >>> id(c)
      8102952
      >>> id(f(c))
      8102952

      pero:

      >>> def f2(n):
      … return n+0

      >>> f2(c)
      3030232430294L
      >>> id(f2(c))
      8102712

      Tal como yo lo veo, desaparece es el clásico concepto de variable como contenedor de valores y el concepto de constante. Ambos se implementan con objetos.

      >>> id(888)
      135589468
      >>> c=888
      >>> id(c)
      135589468
      >>> c=777
      >>> id(c)
      135589516

      Engaña un poco porque el identificador de la mal llamada variable no cambia pero despues de la asignación ya no es la misma. No solo cambia su contenido, cambia el objeto en si.

      Realmente rompe un mónton de esquemas para aquellos que llevamos ya unos añitos trabajando con otros lenguajes como el C.

      Quizás sea una forma ineficiente de interpretar código, pero este coste ya resulta despreciable para un montón de aplicaciones dada la velocidad de proceso de los ordenadores actuales.

      Un saludo, yuniels

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *