¿Cómo paso una variable por referencia?

2628

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

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?

David Sykes
fuente
23
Para una breve explicación / aclaración, vea la primera respuesta a esta pregunta de stackoverflow . Como las cadenas son inmutables, no se cambiarán y se creará una nueva variable, por lo que la variable "externa" aún tiene el mismo valor.
PhilS
10
El código en la respuesta de BlairConrad es bueno, pero la explicación proporcionada por DavidCournapeau y DarenThomas es correcta.
Ethan Furman
70
Antes de leer la respuesta seleccionada, considere leer este breve texto. Otros idiomas tienen "variables", Python tiene "nombres" . Piense en "nombres" y "objetos" en lugar de "variables" y "referencias" y debería evitar muchos problemas similares.
lqc
24
¿Por qué esto usa innecesariamente una clase? Esto no es Java: P
Peter R
2
otra solución es crear una 'referencia' de envoltura como esta: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
robert

Respuestas:

2831

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 deleite de su corazón, pero si vuelve a vincular la referencia en el método, el alcance externo no sabrá nada al respecto, y después Una vez hecho, la referencia externa seguirá apuntando al objeto original.

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

Para hacerlo aún más claro, tengamos 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 outer_list, no una copia, podemos usar los métodos de la lista de mutaciones para cambiarlo y hacer que los cambios se reflejen 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)

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

Como 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. El the_listera una copia de la outer_listreferencia, y tuvimos que the_listapuntan a una nueva lista, pero no había manera de cambiar el lugar donde outer_listpuntiagudo.

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

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. El the_stringera una copia de la outer_stringreferencia, y tuvimos que the_stringapuntan a una nueva cadena, pero no había manera de cambiar el lugar donde outer_stringpuntiagudo.

Espero que esto aclare un poco las cosas.

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

¿Cómo lo solucionamos?

Como muestra la respuesta de @ Andrea, puede 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 quisiera evitar el uso de un valor de retorno, podría 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.

Blair Conrad
fuente
165
Entonces lo mismo ocurre en C, cuando pasa "por referencia" en realidad está pasando por valor la referencia ... Definir "por referencia": P
Andrea Ambu
104
No estoy seguro de entender tus términos. He estado fuera del juego C por un tiempo, pero cuando estaba en él, no había "pasar por referencia": se podían pasar cosas, y siempre se pasaba por valor, así que lo que estaba en la lista de parámetros fue copiado Pero a veces la cosa era un puntero, que se podía seguir a la pieza de memoria (primitivo, matriz, estructura, lo que sea), pero no se podía cambiar el puntero que se copió del ámbito externo, cuando se terminó con la función , el puntero original todavía apuntaba a la misma dirección. C ++ introdujo referencias, que se comportaron de manera diferente.
Blair Conrad
34
@Zac Bowling Realmente no entiendo cómo lo que estás diciendo es relevante, en un sentido práctico, para esta respuesta. Si un recién llegado Python quería saber acerca de pasar por ref / val, entonces la comida para llevar de esta respuesta es: 1- Usted puede utilizar la referencia de que una función recibe como argumentos, para modificar el valor 'fuera' de una variable, siempre ya que no reasigna el parámetro para referirse a un nuevo objeto. 2- Asignar a un tipo inmutable siempre creará un nuevo objeto, que rompe la referencia que tenía a la variable externa.
Cam Jackson
11
@CamJackson, necesita un mejor ejemplo: los números también son objetos inmutables en Python. Además, ¿no sería cierto decir que cualquier asignación sin subíndice en el lado izquierdo de los iguales reasignará el nombre a un nuevo objeto si es inmutable o no? def Foo(alist): alist = [1,2,3]será no modificar el contenido de la lista desde el punto de vista que llaman.
Mark Ransom
59
-1. El código que se muestra es bueno, la explicación de cómo está completamente mal. Consulte las respuestas de DavidCournapeau o DarenThomas para obtener explicaciones correctas de por qué.
Ethan Furman
685

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

a = 1
a = 2

Cree que aes 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, acomienza 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 aya no se refieren al primero; de hecho, pueden ser compartidos por cualquier número de otras referencias dentro del programa.

Cuando llama a una función con un parámetro, se crea una nueva referencia que se refiere 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.variablees una referencia al objeto de cadena 'Original'. Cuando llamas Change, creas una segunda referencia varal objeto. Dentro de la función, reasigna 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. Debido a que ambas referencias se refieren 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'
Mark Ransom
fuente
106
Buena explicación sucinta. Su párrafo "Cuando llama a una función ..." es una de las mejores explicaciones que he escuchado de la frase bastante críptica de que "los parámetros de la función de Python son referencias, pasadas por valor". Creo que si entiendes ese párrafo solo, todo lo demás tiene sentido y fluye como una conclusión lógica a partir de ahí. Entonces solo debe tener en cuenta cuándo está creando un nuevo objeto y cuándo está modificando uno existente.
Cam Jackson
3
Pero, ¿cómo puede reasignar la referencia? Pensé que no podía cambiar la dirección de 'var' pero que su cadena "Cambiada" ahora se iba a almacenar en la dirección de memoria 'var'. Su descripción hace que parezca que "Modificado" y "Original" pertenecen a diferentes lugares en la memoria y simplemente cambia 'var' a una dirección diferente. ¿Es eso correcto?
Glassjawed
99
@Glassjawed, creo que lo estás entendiendo. "Modificado" y "Original" son dos objetos de cadena diferentes en diferentes direcciones de memoria y 'var' cambia de señalar a uno a señalar a la otra.
Mark Ransom
El uso de la función id () ayuda a aclarar las cosas, porque deja en claro cuando Python crea un nuevo objeto (así que creo, de todos modos).
Tim Richardson
1
@MinhTran en los términos más simples, una referencia es algo que "se refiere" a un objeto. La representación física de eso es probablemente un puntero, pero eso es simplemente un detalle de implementación. Realmente es una noción abstracta en el fondo.
Mark Ransom
329

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. ingrese la descripción de la imagen aquí

Zenadix
fuente
2
encantador, hace que sea fácil detectar la diferencia sutil de que hay una tarea intermedia, que no es obvia para un espectador casual. +1
user22866
99
No importa si A es mutable o no. Si asigna algo diferente a B, A no cambia . Si un objeto es mutable, puede mutarlo, claro. Pero eso no tiene nada que ver con la asignación directa a un nombre ..
Martijn Pieters
1
@ Martijn Tienes razón. Eliminé la parte de la respuesta que menciona la mutabilidad. No creo que pueda ser más simple ahora.
Zenadix
44
Gracias por la actualización, mucho mejor! Lo que confunde a la mayoría de las personas es la asignación a una suscripción; por ejemplo B[0] = 2, frente a la asignación directa, B = 2.
Martijn Pieters
11
"A está asignado a B". ¿No es eso ambiguo? Creo que en inglés ordinario puede significar cualquiera A=Bo B=A.
Hatshepsut
240

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

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

Aquí hay una cita importante:

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

En su ejemplo, cuando Changese llama al método, se crea un espacio de nombres para él; y se varconvierte 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. A continuación, se var = 'Changed'une vara un nuevo objeto de cadena y, por lo tanto, el espacio de nombres del método se olvida 'Original'. Finalmente, ese espacio de nombres se olvida, y la cadena 'Changed'junto con él.

David Cournapeau
fuente
21
Me resulta difícil comprarlo. Para mí es igual que Java, los parámetros son punteros a objetos en la memoria, y esos punteros se pasan a través de la pila o registros.
Luciano
10
Esto no es como Java. Uno de los casos en que no es lo mismo son los objetos inmutables. Piensa en la función trivial lambda x: x. Aplique esto para x = [1, 2, 3] yx = (1, 2, 3). En el primer caso, el valor devuelto será una copia de la entrada, e idéntico en el segundo caso.
David Cournapeau
26
No, es exactamente como la semántica de Java para los objetos. No estoy seguro de lo que quiere decir con "En el primer caso, el valor devuelto será una copia de la entrada, e idéntico en el segundo caso". pero esa afirmación parece ser claramente incorrecta.
Mike Graham el
23
Es exactamente lo mismo que en Java. Las referencias a objetos se pasan por valor. Cualquiera que piense diferente debe adjuntar el código de Python para una swapfunción que pueda intercambiar dos referencias, como esta: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann
24
Es exactamente lo mismo que Java cuando pasa objetos en Java. Sin embargo, Java también tiene primitivas, que se pasan copiando el valor de la primitiva. Por lo tanto, difieren en ese caso.
Claudiu
174

Piense en cosas que se pasan por asignación en lugar de por referencia / por valor. De esa manera, siempre está claro lo que está sucediendo siempre que 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. Si se agrega a la lista, se modificará la lista. La reasignación de 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 pueden modificarse, parecen pasar por valor: pasar un int a una función significa asignar el int al parámetro de la función. Solo puede reasignar eso, pero no cambiará el valor de las variables originales.

Daren Thomas
fuente
3
A primera vista, esta respuesta parece eludir la pregunta original. Después de una segunda lectura, me di cuenta de que esto deja el asunto bastante claro. Un buen seguimiento de este concepto de "asignación de nombre" se puede encontrar aquí: Code Like a Pythonista: Idiomatic Python
Christian Groleau
65

Effbot (también conocido como 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 a cualquier lugar.

  • Cuando realiza una asignación como x = 1000, se crea una entrada de diccionario que asigna la cadena "x" en el espacio de nombres 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 mil objetos no ha cambiado (y puede o no estar vivo dependiendo de si algo más se refiere al objeto).

  • Cuando realiza una nueva asignación 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 inmutables . 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á. La matemática se realiza creando nuevos objetos enteros.

  • Los objetos como las listas son mutables . Esto significa que el contenido del objeto se puede cambiar por cualquier cosa que apunte al objeto. Por ejemplo, x = []; y = x; x.append(10); print yse imprimirá [10]. La lista vacía fue creada. Tanto "x" como "y" apuntan a la misma lista. El método append 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 la base de datos sería visible para cada conexión a esa base de datos).

Espero que eso te aclare el problema.

Raymond Hettinger
fuente
2
Realmente aprecio aprender sobre esto de un desarrollador. ¿Es cierto que la id()función devuelve el valor del puntero (referencia del objeto), como sugiere la respuesta de pepr?
Honesto Abe el
44
@HonestAbe Sí, en CPython el id () devuelve la dirección. Pero en otras pitones como PyPy y Jython, el id () es solo un identificador de objeto único.
Raymond Hettinger
59

Técnicamente, Python siempre usa el paso 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 intermedia, un paso más para saltar al objetivo.

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

Ejemplo ilustrado de pasar el argumento

Si el argumento fue pasado por valor, el externo lstno podría modificarse. El verde son los objetos de destino (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 en el interior, dibujado como la flecha. La flecha sólida azul es el valor de referencia que se pasó a la función (a través de la ruta de la flecha azul discontinua). El feo amarillo oscuro es el diccionario interno. (En realidad, podría dibujarse también como una elipse verde. El color y la forma solo dicen que es interno).

Puede usar la id()función incorporada para aprender 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 del valor de ese elemento del diccionario almacena el valor de referencia en el objetivo.

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 la variable de referencia, porque todos los contenedores también almacenan los elementos como referencias a los objetos de destino. En otras palabras, los elementos en realidad no están contenidos dentro del contenedor, solo lo están las referencias a los elementos.

pepr
fuente
1
En realidad, esto se confirma su pase por valor de referencia. +1 para esta respuesta, aunque el ejemplo no fue bueno.
BugShotGG
30
Inventar una nueva terminología (como "pasar por valor de referencia" o "llamar por objeto" no es útil). "Llamar por (valor | referencia | nombre)" son términos estándar. "referencia" es un término estándar. Pasar referencias por valor describe con precisión el comportamiento de Python, Java y una gran cantidad de otros lenguajes, utilizando terminología estándar.
cayhorstmann
44
@cayhorstmann: El problema es que la variable Python no tiene el mismo significado de terminología que en otros lenguajes. De esta manera, la llamada por referencia no encaja bien aquí. Además, ¿cómo define exactamente el término referencia ? Informalmente, la forma de Python podría describirse fácilmente como pasar la dirección del objeto. Pero no encaja con una implementación potencialmente distribuida de Python.
pepr
1
Me gusta esta respuesta, pero podría considerar si el ejemplo realmente está ayudando o perjudicando el flujo. Además, si se ha sustituido 'valor de referencia' con 'referencia de objeto' que estaría usando terminología que podríamos considerar 'oficial', como se ve aquí: Definición de funciones
Abe honesto
2
Hay una nota al pie de página indicada al final de esa cita, que dice: "En realidad, llamar por referencia de objeto sería una mejor descripción, ya que si se pasa un objeto mutable, la persona que llama verá los cambios que el destinatario le hace ... " Estoy de acuerdo con usted en que la confusión se produce al tratar de ajustar la terminología establecida con otros idiomas. Dejando a un lado la semántica, las cosas que deben entenderse son: diccionarios / espacios de nombres, operaciones de enlace de nombres y la relación de nombre → puntero → objeto (como ya sabe).
Honesto Abe
56

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 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 une 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 bya no 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 instrucción self.Change(self.variable)vincula el nombre var(en el ámbito 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 diferente).

Cómo pasar por referencia

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

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

Lutz Prechelt
fuente
20
"Python no tiene variables" es un eslogan tonto y confuso, y realmente deseo que la gente deje de decirlo ... :( ¡El resto de esta respuesta es buena!
Ned Batchelder
99
Puede ser impactante, pero no es tonto. Y tampoco creo que sea confuso: es de esperar que abra la mente del receptor para la explicación que se avecina y la ponga en una útil actitud de "Me pregunto qué tienen en lugar de variables". (Sí, su millaje puede variar.)
Lutz Prechelt
13
¿Diría también que Javascript no tiene variables? Funcionan igual que Python. Además, Java, Ruby, PHP, ... Creo que una mejor técnica de enseñanza es: "Las variables de Python funcionan de manera diferente que las de C".
Ned Batchelder
99
Sí, Java tiene variables. También Python, y JavaScript, Ruby, PHP, etc. No diría en Java que intdeclara una variable, pero Integerno lo hace. Ambos declaran variables. La Integervariable es un objeto, la intvariable es una primitiva. Como ejemplo, demostró cómo funcionan sus variables mostrando a = 1; b = a; a++ # doesn't modify b. ¡Eso es exactamente cierto en Python también (usando += 1ya que no hay ++Python)!
Ned Batchelder
1
El concepto de "variable" es complejo y a menudo vago: una variable es un contenedor para un valor, identificado por un nombre. En Python, los valores son objetos, los contenedores son objetos (¿ves el problema?) Y los nombres son en realidad cosas separadas. Creo que es mucho más difícil obtener una comprensión precisa de las variables de esta manera. La explicación de nombres y objetos parece más difícil, pero en realidad es más simple.
Lutz Prechelt el
43

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 inconveniente, pero a veces es lo suficientemente simple como para hacer esto).

AmanicA
fuente
77
+1 para una pequeña cantidad de texto que proporciona la solución esencial al problema de que Python no tiene una referencia de paso. (Como un comentario / pregunta de seguimiento que se ajusta aquí, así como en cualquier parte de esta página: no está claro para mí por qué Python no puede proporcionar una palabra clave "ref" como C #, que simplemente envuelve el argumento de la persona que llama en una lista como esto, y trate las referencias al argumento dentro de la función como el elemento 0 de la lista.)
M Katz
55
Agradable. Para pasar por ref, envuelva en [] 's.
Justas
38

(editar - Blair ha actualizado su respuesta enormemente popular 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 incorrecta según sus definiciones. Si bien hay muchos lenguajes (como C) que permiten al usuario pasar por referencia o 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 se pasa por valor, todos los idiomas se 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 de que un programador de C pensaría en ello.

Si quieres el comportamiento, la respuesta de Blair Conrad está bien. Pero si desea conocer los detalles de por qué Python no es pasar por valor o pasar por referencia, lea la respuesta de David Cournapeau.

KobeJohn
fuente
44
Simplemente no es cierto que todos los idiomas se llamen por valor. En C ++ o Pascal (y seguramente muchos otros que no conozco), tiene una llamada por referencia. Por ejemplo, en C ++, void swap(int& x, int& y) { int temp = x; x = y; y = temp; }intercambiará las variables que se le pasan. En Pascal, usas en varlugar de &.
cayhorstmann
2
Pensé que había respondido a esto hace mucho tiempo, pero no lo veo. Para completar, cayhorstmann no entendió mi respuesta. No estaba diciendo que todo es llamado por valor en los términos que la mayoría de la gente aprende primero con respecto a C / C ++ . Era simplemente que se pasaba algún valor (valor, nombre, puntero, etc.) y que los términos utilizados en la respuesta original de Blair eran inexactos.
KobeJohn
24

Tienes algunas respuestas realmente buenas aquí.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )
bobobobo
fuente
2
sí, sin embargo, si lo haces x = [2, 4, 4, 5, 5], y = x, X [0] = 1, imprime x # [1, 4, 4, 5, 5] imprime y # [1 , 4, 4, 5, 5]
laycat
19

En este caso, a la variable titulada varen el método Changese le asigna una referencia self.variablee inmediatamente se le asigna una cadena var. Ya no está apuntando a self.variable. El siguiente fragmento de código muestra lo que sucedería si modifica la estructura de datos a la que apunta vary self.variable, en este caso, una lista:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

Estoy seguro de que alguien más podría aclarar esto más.

Mike Mazur
fuente
18

El esquema de paso por asignación de Python no es exactamente lo mismo que la opción de parámetros de referencia de C ++, pero resulta ser muy similar al modelo de paso de argumentos del lenguaje C (y otros) en la práctica:

  • Los argumentos inmutables se pasan efectivamente " por valor ". Los objetos como enteros y cadenas se pasan por referencia de objeto en lugar de copiar, pero de todos modos, como no puede cambiar los objetos inmutables en su lugar, el efecto es muy similar a hacer una copia.
  • Los argumentos mutables se pasan efectivamente " por puntero ". Los objetos como las listas y los diccionarios también se pasan por referencia de objeto, que es similar a la forma en que C pasa las matrices como punteros: los objetos mutables se pueden cambiar en su lugar en la función, al igual que las matrices de C.
ajknzhol
fuente
16

Como puede decir, necesita tener un objeto mutable, ¡pero permítame sugerirle que verifique las variables globales ya que pueden ayudarlo o incluso resolver este tipo de problema!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

ejemplo:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2
Nuno Aniceto
fuente
55
Tuve la tentación de publicar una respuesta similar: el interrogador original puede no haber sabido que lo que quería era utilizar una variable global, compartida entre funciones. Aquí está el enlace que habría compartido: stackoverflow.com/questions/423379/... En respuesta a @Tim, Stack Overflow no es solo un sitio de preguntas y respuestas, es un vasto depósito de conocimiento de referencia que solo se vuelve más fuerte y más matizado. como un wiki- activo con más entrada.
Max P Magee
13

Aquí hay muchas ideas sobre las respuestas, pero creo que aquí no se menciona explícitamente un punto adicional aquí. Citando de la documentación de Python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"En Python, las variables a las que solo se hace referencia dentro de una función son implícitamente globales. Si a una variable se le asigna un nuevo valor en cualquier lugar dentro del cuerpo de la función, se supone que es local. Si a una variable se le asigna un nuevo valor dentro de la función, la variable es implícitamente local, y debe declararla explícitamente como 'global'. Aunque un poco sorprendente al principio, un momento de consideración lo explica. Por un lado, requerir variables globales asignadas proporciona una barrera contra los efectos secundarios no deseados. por otro lado, si se necesitara global para todas las referencias globales, estaría usando global todo el tiempo. Tendría que declarar como global cada referencia a una función incorporada o a un componente de un módulo importado. Este desorden derrotaría la utilidad de la declaración global para identificar los efectos secundarios ".

Incluso cuando se pasa un objeto mutable a una función, esto todavía se aplica. Y para mí, explica claramente la razón de la diferencia de comportamiento entre asignar al objeto y operar en el objeto en la función.

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

da:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

La asignación a una variable global que no se declara global, por lo tanto, crea un nuevo objeto local y rompe el enlace al objeto original.

Joop
fuente
10

Aquí está la explicación simple (espero) del concepto pass by objectutilizado en Python.
Cada vez que pasa un objeto a la función, se pasa el objeto en sí (el objeto en Python es en realidad lo que llamaría un valor en otros lenguajes de programación), no la referencia a este objeto. En otras palabras, cuando llamas:

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

Se pasa el objeto real - [0, 1] (que se llamaría un valor en otros lenguajes de programación). De hecho, la función change_meintentará hacer algo como:

[0, 1] = [1, 2, 3]

que obviamente no cambiará el objeto pasado a la función. Si la función se ve así:

def change_me(list):
   list.append(2)

Entonces la llamada resultaría en:

[0, 1].append(2)

que obviamente cambiará el objeto. Esta respuesta lo explica bien.

matino
fuente
2
El problema es que la tarea hace algo más de lo que esperas. Las list = [1, 2, 3]causas reutilizan el listnombre para otra cosa y falsifican el objeto pasado originalmente. Sin embargo, puede probar list[:] = [1, 2, 3](por cierto listes un nombre equivocado para una variable Pensando. [0, 1] = [1, 2, 3]Es un completo disparate De todos modos, ¿qué te parece medios. Se pasa el objeto en sí Lo que se copia a la función en su opinión??
PEPR
Los objetos @pepr no son literales. Son objetos. La única forma de hablar sobre ellos es dándoles algunos nombres. Es por eso que es tan simple una vez que lo entiendes, pero enormemente complicado de explicar. :-)
Veky
@ Veky: Soy consciente de eso. De todos modos, el literal de la lista se convierte en el objeto de la lista. En realidad, cualquier objeto en Python puede existir sin un nombre, y puede usarse incluso cuando no se le da ningún nombre. Y puedes pensar en ellos como en objetos anónimos. Piensa en los objetos como elementos de una lista. No necesitan un nombre. Puede acceder a ellos indexando o iterando a través de la lista. De todos modos, insisto en que [0, 1] = [1, 2, 3]es simplemente un mal ejemplo. No hay nada de eso en Python.
pepr
@pepr: No necesariamente me refiero a nombres con definición de Python, solo nombres comunes. Por supuesto, alist[2]cuenta como un nombre de un tercer elemento de la lista. Pero creo que no entendí cuál era tu problema. :-)
Veky
Argh Mi inglés es obviamente mucho peor que mi Python. :-) Lo intentaré solo una vez más. Solo dije que tienes que darle algunos nombres a los objetos solo para hablar de ellos. Con esos "nombres" no quise decir "nombres definidos por Python". Conozco los mecanismos de Python, no te preocupes.
Veky
8

Además de todas las excelentes explicaciones sobre cómo funciona esto en Python, no veo una sugerencia simple para el problema. Como parece crear objetos e instancias, la forma pitónica de manejar variables de instancia y cambiarlas es la siguiente:

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

    def Change(self):
        self.variable = 'Changed'

En los métodos de instancia, normalmente hace referencia a los selfatributos de instancia de acceso. Es normal establecer atributos de instancia __init__y leerlos o cambiarlos en métodos de instancia. Esa es también la razón por la que le pasas selftambién el primer argumento def Change.

Otra solución sería crear un método estático como este:

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

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var
Dolf Andringa
fuente
6

Hay un pequeño truco para pasar un objeto por referencia, a pesar de que el lenguaje no lo hace posible. También funciona en Java, es la lista con un elemento. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

Es un truco feo, pero funciona. ;-PAGS

itmuckel
fuente
pes una referencia a un objeto de lista mutable que a su vez almacena el objeto obj. La referencia 'p' se pasa a changeRef. En el interior changeRef, se crea una nueva referencia (se llama a la nueva referencia ref) que apunta al mismo objeto de lista al que papunta. Pero debido a que las listas son mutables, ambos cambios pueden ver los cambios en la lista . En este caso, usó la refreferencia para cambiar el objeto en el índice 0 para que posteriormente almacene el PassByReference('Michael')objeto. El cambio en el objeto de la lista se realizó usando refpero este cambio es visible para p.
Minh Tran
Así que ahora, las referencias py refapuntan a un objeto de lista que almacena el objeto único, PassByReference('Michael'). Entonces se deduce que p[0].namevuelve Michael. Por supuesto, refahora está fuera de alcance y puede ser basura recolectada, pero de todos modos.
Minh Tran
Sin embargo, no ha cambiado la variable de instancia privada name, del PassByReferenceobjeto original asociado con la referencia obj. De hecho, obj.namevolveremos Peter. Los comentarios antes mencionados asumen la definición Mark Ransomdada.
Minh Tran
El punto es que no estoy de acuerdo con que sea un truco (lo que considero que se refiere a algo que funciona, pero por razones desconocidas, no probadas o no intencionadas por el implementador). Simplemente reemplazó un PassByReferenceobjeto con otro PassByReferenceobjeto en su lista y se refirió al último de los dos objetos.
Minh Tran
5

Usé el siguiente método para convertir rápidamente un par de códigos Fortran a Python. Es cierto que no se pasa por referencia como se planteó la pregunta original, pero es una solución simple en algunos casos.

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c
Brad Porter
fuente
Sí, esto resuelve el 'pasar por referencia' en mi caso de uso también. Tengo una función que básicamente limpia los valores en a dicty luego devuelve el dict. Sin embargo, mientras se limpia puede hacerse evidente que se requiere una reconstrucción de una parte del sistema. Por lo tanto, la función no solo debe devolver la limpieza, dictsino también ser capaz de señalar la reconstrucción. Traté de pasar una boolreferencia, pero ofc eso no funciona. Al descubrir cómo resolver esto, encontré que su solución (básicamente devolver una tupla) funciona mejor sin ser un truco / solución en absoluto (en mi humilde opinión).
kasimir
3

Si bien pasar por referencia no es nada que se ajuste bien a Python y debe usarse raramente, existen algunas soluciones que realmente pueden funcionar para obtener el objeto actualmente asignado a una variable local o incluso reasignar una variable local desde el interior de una función llamada.

La idea básica es tener una función que pueda hacer ese acceso y que pueda pasarse como objeto a otras funciones o almacenarse en una clase.

Una forma es usar global(para variables globales) o nonlocal(para variables locales en una función) en una función de contenedor.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

La misma idea funciona para leer y delescribir una variable.

Para solo leer, hay una forma más corta de solo usar lambda: xque devuelve un invocable que cuando se llama devuelve el valor actual de x. Esto es algo así como "llamar por nombre" usado en idiomas en el pasado distante.

Pasar 3 contenedores para acceder a una variable es un poco difícil de manejar, por lo que se pueden incluir en una clase que tiene un atributo proxy:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

El soporte de "reflexión" de Pythons hace posible obtener un objeto que sea capaz de reasignar un nombre / variable en un ámbito dado sin definir funciones explícitamente en ese ámbito:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Aquí la ByRefclase envuelve un acceso al diccionario. Por lo tanto, el acceso a atributos wrappedse traduce en un acceso a elementos en el diccionario aprobado. Al pasar el resultado de la construcción localsy el nombre de una variable local, esto termina accediendo a una variable local. La documentación de Python a partir de 3.5 informa que cambiar el diccionario podría no funcionar, pero parece funcionar para mí.

shell de texto
fuente
3

Dada la forma en que Python maneja los valores y las referencias a ellos, la única forma en que puede hacer referencia a un atributo de instancia arbitraria es por nombre:

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

en el código real, por supuesto, agregaría una comprobación de errores en la búsqueda de dict.

BLOORE MARCA
fuente
3

Como los diccionarios se pasan por referencia, puede usar una variable dict para almacenar cualquier valor referenciado dentro de él.

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
Liakos
fuente
3

Pass-By-Reference en Python es bastante diferente del concepto de pasar por referencia en C ++ / Java.

  • Java & C #: los tipos primitivos (incluyen cadena) pasan por valor (copia), el tipo de referencia se pasa por referencia (copia de dirección) para que todos los cambios realizados en el parámetro en la función llamada sean visibles para el llamante.
  • C ++: se permiten tanto el paso por referencia como el paso por valor. Si un parámetro se pasa por referencia, puede modificarlo o no dependiendo de si el parámetro se pasó como constante o no. Sin embargo, constante o no, el parámetro mantiene la referencia al objeto y no se puede asignar referencia para apuntar a un objeto diferente dentro de la función llamada.
  • Python: Python es "referencia de paso por objeto", del cual a menudo se dice: "Las referencias de objeto se pasan por valor". [Lea aquí] 1. Tanto la persona que llama como la función se refieren al mismo objeto, pero el parámetro en la función es una nueva variable que solo contiene una copia del objeto en la persona que llama. Al igual que C ++, un parámetro puede modificarse o no en función: esto depende del tipo de objeto pasado. p.ej; Un tipo de objeto inmutable no se puede modificar en la función llamada, mientras que un objeto mutable se puede actualizar o reinicializar. Una diferencia crucial entre actualizar o reasignar / reinicializar la variable mutable es que el valor actualizado se refleja en la función llamada, mientras que el valor reinicializado no. El alcance de cualquier asignación de un nuevo objeto a una variable mutable es local para la función en la pitón. Los ejemplos proporcionados por @ blair-conrad son geniales para entender esto.
Alok Garg
fuente
1
Viejo pero me siento obligado a corregirlo. Las cadenas se pasan por referencia tanto en Java como en C #, NO por valor
John
2

Dado que su ejemplo está orientado a objetos, puede realizar el siguiente cambio para lograr un resultado similar:

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

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

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'
Jesse Hogan
fuente
1
Aunque esto funciona. No se pasa por referencia. Es 'pasar por referencia de objeto'.
Bishwas Mishra
1

Simplemente puede usar una clase vacía como una instancia para almacenar objetos de referencia porque internamente los atributos de los objetos se almacenan en un diccionario de instancias. Mira el ejemplo.

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)
sergzach
fuente
1

Dado que parece que no se menciona en ninguna parte, un enfoque para simular referencias como las conocidas, por ejemplo, C ++ es usar una función de "actualización" y pasarla en lugar de la variable real (o mejor dicho, "nombre"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Esto es principalmente útil para "referencias externas" o en una situación con múltiples subprocesos / procesos (al hacer que la función de actualización de subprocesos / multiprocesamiento sea segura).

Obviamente lo anterior no permite leer el valor, solo actualizarlo.

Daniel Jour
fuente