una - python parametros por valor




¿Cómo paso una variable por referencia? (16)

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

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

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

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


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 enlaza un nombre a un objeto.
  3. Pasar un argumento a una función también vincula un nombre (el nombre del parámetro de la función) a un objeto.

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

Ejemplo:

a = 1

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

b = x

Esto une el nombre b al mismo objeto al que está vinculado actualmente el nombre x . Después, el nombre b ya no tiene nada que ver con el nombre x .

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

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


(edición - 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 incorrecto según sus definiciones. Si bien hay muchos idiomas (como C) que permiten al usuario pasar por referencia o pasar por valor, Python no es uno de ellos.

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

En la medida en que Python se pasa por valor, todos los idiomas se pasan por valor ya que algunos datos (ya sea un "valor" o una "referencia") deben enviarse. Sin embargo, eso no significa que Python se pase por valor en el sentido de que un programador de C lo pensaría.

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


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 que apunte al nuevo objeto. El antiguo mil objeto permanece sin cambios (y puede o no estar vivo dependiendo de si alguna otra cosa 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 de "x".

  • 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á. Matemáticas se realiza mediante la creación de nuevos objetos enteros.

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

Espero que te aclare el tema.


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 copiarlos, pero como no se pueden cambiar los objetos inmutables en su lugar, el efecto es muy parecido a hacer una copia.
  • Los argumentos mutables se pasan efectivamente " por puntero ". Los objetos, como listas y diccionarios, también se pasan por referencia a objetos, que es similar a la forma en que C pasa a las matrices como punteros: los objetos mutables pueden cambiarse en el lugar de la función, al igual que las matrices C .

En este caso, a la variable titulada var en el método Change se le asigna una referencia a self.variable , e inmediatamente asigna una cadena a var . Ya no está apuntando a self.variable . self.variable . El siguiente fragmento de código muestra lo que sucedería si modificara la estructura de datos apuntada por var y 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 aún más.


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.


No es ni 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 significativa:

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

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


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 y cuando entiendas lo que sucede durante la asignación normal.

Entonces, al pasar una lista a una función / método, la lista se asigna al nombre del parámetro. Anexar a la lista resultará en la modificación de 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 se pueden modificar, parece que se pasan por un valor, pasar un int a una función significa asignar el int al parámetro de funciones. Solo puedes reasignar eso, pero no cambiará el valor de las variables originales.


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

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

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

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

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


Si bien pasar por referencia no es nada que se ajuste bien a python y se debe usar raramente, hay algunas soluciones que realmente pueden funcionar para obtener el objeto asignado actualmente a una variable local o incluso reasignar una variable local desde dentro 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 envoltorio.

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 incluso una forma más corta de usar lambda: xque devuelve un llamable que cuando se llama devuelve el valor actual de x. Esto es algo así como "llamada por nombre" utilizado en idiomas en el pasado lejano.

Pasar 3 envoltorios para acceder a una variable es un poco difícil de manejar, por lo que se pueden envolver en una clase que tiene un atributo de 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 permite obtener un objeto que es capaz de reasignar un nombre / variable en un ámbito determinado 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 de atributo a wrappedse traduce a un acceso de elemento en el diccionario pasado. Al pasar el resultado de la estructura interna localsy el nombre de una variable local, esto termina por acceder 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í.


dada la forma en que Python maneja los valores y las referencias a ellos, la única forma de 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'

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


Aquí está la explicación simple (espero) del concepto pass by objectutilizado en Python.
Siempre que pase un objeto a la función, el objeto en sí se pasa (el objeto en Python es en realidad lo que llamaríamos 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 está pasando el objeto real - [0, 1] (que se llamaría un valor en otros lenguajes de programación). Así que de hecho la función change_meintentará hacer algo como:

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

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

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

Entonces la llamada resultaría en:

[0, 1].append(2)

que obviamente va a cambiar el objeto. Esta respuesta lo explica bien.


Hay muchas ideas en las respuestas aquí, pero creo que un punto adicional no se menciona aquí explícitamente. Cita 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 parte dentro del cuerpo de la función, se asume 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 usted necesita declararla explícitamente como "global". Aunque al principio es un poco sorprendente, un momento de consideración lo explica. Por un lado, la necesidad de una variable global para las asignadas proporciona una barra contra los efectos secundarios no deseados. Por otro lado, si se requiriera 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 anularía la utilidad de la declaración global para identificar efectos secundarios ".

Incluso cuando se pasa un objeto mutable a una función, esto sigue siendo válido. Y para mí, explica claramente la razón de la diferencia de comportamiento entre asignar el objeto y operar 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.


Simplemente puede usar una clase vacía como una instancia para almacenar objetos de referencia porque los atributos de objetos internos se almacenan en un diccionario de instancia. Vea 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)

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

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






pass-by-reference