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?
python
reference
parameter-passing
pass-by-reference
David Sykes
fuente
fuente
Respuestas:
Los argumentos se pasan por asignación . La razón detrás de esto es doble:
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:
Salida:
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:
Salida:
Como el
the_list
parámetro se pasó por valor, asignarle una nueva lista no tuvo ningún efecto que el código fuera del método pudiera ver. Elthe_list
era una copia de laouter_list
referencia, y tuvimos quethe_list
apuntan a una nueva lista, pero no había manera de cambiar el lugar dondeouter_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
Salida:
Nuevamente, dado que el
the_string
parámetro se pasó por valor, asignarle una nueva cadena no tuvo ningún efecto que el código fuera del método pudiera ver. Elthe_string
era una copia de laouter_string
referencia, y tuvimos quethe_string
apuntan a una nueva cadena, pero no había manera de cambiar el lugar dondeouter_string
puntiagudo.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:
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:
Aunque esto parece un poco engorroso.
fuente
def Foo(alist): alist = [1,2,3]
será no modificar el contenido de la lista desde el punto de vista que llaman.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:
Cree que
a
es una ubicación de memoria que almacena el valor1
, luego se actualiza para almacenar el valor2
. Así no es como funcionan las cosas en Python. Más bien,a
comienza como una referencia a un objeto con el valor1
, luego se reasigna como una referencia a un objeto con el valor2
. Esos dos objetos pueden continuar coexistiendo aunquea
ya 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:
self.variable
es una referencia al objeto de cadena'Original'
. Cuando llamasChange
, creas una segunda referenciavar
al objeto. Dentro de la función, reasigna la referenciavar
a un objeto de cadena diferente'Changed'
, pero la referenciaself.variable
está 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.
fuente
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.
fuente
B[0] = 2
, frente a la asignación directa,B = 2
.A=B
oB=A
.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:
En su ejemplo, cuando
Change
se llama al método, se crea un espacio de nombres para él; y sevar
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. A continuación, sevar = 'Changed'
unevar
a 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.fuente
swap
función que pueda intercambiar dos referencias, como esta:a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
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:
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.
fuente
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 y
se 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.
fuente
id()
función devuelve el valor del puntero (referencia del objeto), como sugiere la respuesta de pepr?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:
Si el argumento fue pasado por valor, el externo
lst
no 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.
fuente
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.
Eso es todo lo que hay que hacer. La mutabilidad es irrelevante para esta pregunta.
Ejemplo:
Esto une el nombre
a
a un objeto de tipo entero que contiene el valor 1.Esto vincula el nombre
b
al mismo objeto al quex
está vinculado actualmente el nombre . Después, el nombreb
ya no tiene nada que ver con el nombrex
.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 nombrevar
(en el ámbito de la funciónChange
) al objeto que contiene el valor'Original'
y la asignaciónvar = 'Changed'
(en el cuerpo de la funciónChange
) 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.variable
pasar[self.variable]
y modificar la funciónvar[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.
fuente
int
declara una variable, peroInteger
no lo hace. Ambos declaran variables. LaInteger
variable es un objeto, laint
variable es una primitiva. Como ejemplo, demostró cómo funcionan sus variables mostrandoa = 1; b = a; a++ # doesn't modify b
. ¡Eso es exactamente cierto en Python también (usando+= 1
ya que no hay++
Python)!Un truco simple que normalmente uso es simplemente envolverlo en una lista:
(Sí, sé que esto puede ser inconveniente, pero a veces es lo suficientemente simple como para hacer esto).
fuente
(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.
fuente
void swap(int& x, int& y) { int temp = x; x = y; y = temp; }
intercambiará las variables que se le pasan. En Pascal, usas envar
lugar de&
.Tienes algunas respuestas realmente buenas aquí.
fuente
En este caso, a la variable titulada
var
en el métodoChange
se le asigna una referenciaself.variable
e inmediatamente se le asigna una cadenavar
. Ya no está apuntando aself.variable
. El siguiente fragmento de código muestra lo que sucedería si modifica la estructura de datos a la que apuntavar
yself.variable
, en este caso, una lista:Estoy seguro de que alguien más podría aclarar esto más.
fuente
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:
fuente
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:
fuente
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.
da:
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.
fuente
Aquí está la explicación simple (espero) del concepto
pass by object
utilizado 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:
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_me
intentará hacer algo como:que obviamente no cambiará el objeto pasado a la función. Si la función se ve así:
Entonces la llamada resultaría en:
que obviamente cambiará el objeto. Esta respuesta lo explica bien.
fuente
list = [1, 2, 3]
causas reutilizan ellist
nombre para otra cosa y falsifican el objeto pasado originalmente. Sin embargo, puede probarlist[:] = [1, 2, 3]
(por ciertolist
es 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??[0, 1] = [1, 2, 3]
es simplemente un mal ejemplo. No hay nada de eso en Python.alist[2]
cuenta como un nombre de un tercer elemento de la lista. Pero creo que no entendí cuál era tu problema. :-)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:
En los métodos de instancia, normalmente hace referencia a los
self
atributos 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 pasasself
también el primer argumentodef Change
.Otra solución sería crear un método estático como este:
fuente
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. ;-)
Es un truco feo, pero funciona. ;-PAGS
fuente
p
es una referencia a un objeto de lista mutable que a su vez almacena el objetoobj
. La referencia 'p' se pasa achangeRef
. En el interiorchangeRef
, se crea una nueva referencia (se llama a la nueva referenciaref
) que apunta al mismo objeto de lista al quep
apunta. Pero debido a que las listas son mutables, ambos cambios pueden ver los cambios en la lista . En este caso, usó laref
referencia para cambiar el objeto en el índice 0 para que posteriormente almacene elPassByReference('Michael')
objeto. El cambio en el objeto de la lista se realizó usandoref
pero este cambio es visible parap
.p
yref
apuntan a un objeto de lista que almacena el objeto único,PassByReference('Michael')
. Entonces se deduce quep[0].name
vuelveMichael
. Por supuesto,ref
ahora está fuera de alcance y puede ser basura recolectada, pero de todos modos.name
, delPassByReference
objeto original asociado con la referenciaobj
. De hecho,obj.name
volveremosPeter
. Los comentarios antes mencionados asumen la definiciónMark Ransom
dada.PassByReference
objeto con otroPassByReference
objeto en su lista y se refirió al último de los dos objetos.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.
fuente
dict
y luego devuelve eldict
. 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,dict
sino también ser capaz de señalar la reconstrucción. Traté de pasar unabool
referencia, 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).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) ononlocal
(para variables locales en una función) en una función de contenedor.La misma idea funciona para leer y
del
escribir una variable.Para solo leer, hay una forma más corta de solo usar
lambda: x
que 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:
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:
Aquí la
ByRef
clase envuelve un acceso al diccionario. Por lo tanto, el acceso a atributoswrapped
se traduce en un acceso a elementos en el diccionario aprobado. Al pasar el resultado de la construcciónlocals
y 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í.fuente
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:
en el código real, por supuesto, agregaría una comprobación de errores en la búsqueda de dict.
fuente
Como los diccionarios se pasan por referencia, puede usar una variable dict para almacenar cualquier valor referenciado dentro de él.
fuente
Pass-By-Reference en Python es bastante diferente del concepto de pasar por referencia en C ++ / Java.
fuente
Dado que su ejemplo está orientado a objetos, puede realizar el siguiente cambio para lograr un resultado similar:
fuente
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.
fuente
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"):
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.
fuente