Pasando un entero por referencia en Python

97

¿Cómo puedo pasar un número entero por referencia en Python?

Quiero modificar el valor de una variable que le estoy pasando a la función. He leído que todo en Python se pasa por valor, pero tiene que haber un truco fácil. Por ejemplo, en Java que podría pasar a los tipos de referencia de Integer, Long, etc.

  1. ¿Cómo puedo pasar un número entero a una función por referencia?
  2. ¿Cuáles son las mejores prácticas?
CodeKingPlusPlus
fuente
vea esto para una manera agradable aunque ligeramente complicada de envolver sus entradas en una clase anónima (que es mutable) que se comportará como una 'referencia': stackoverflow.com/a/1123054/409638 es decir, ref = type ('', () , {'n': 1})
robert

Respuestas:

107

No funciona de esa manera en Python. Python pasa referencias a objetos. Dentro de su función tiene un objeto: puede mutar ese objeto (si es posible). Sin embargo, los números enteros son inmutables . Una solución es pasar el número entero en un contenedor que se puede mutar:

def change(x):
    x[0] = 3

x = [1]
change(x)
print x

Esto es feo / torpe en el mejor de los casos, pero no le irá mejor en Python. La razón es porque en Python, la asignación ( =) toma cualquier objeto que sea el resultado del lado derecho y lo une a lo que esté en el lado izquierdo * (o lo pasa a la función apropiada).

Al comprender esto, podemos ver por qué no hay forma de cambiar el valor de un objeto inmutable dentro de una función: no puede cambiar ninguno de sus atributos porque es inmutable y no puede simplemente asignar a la "variable" una nueva value porque entonces en realidad está creando un nuevo objeto (que es distinto del anterior) y le está dando el nombre que tenía el objeto anterior en el espacio de nombres local.

Por lo general, la solución es simplemente devolver el objeto que desea:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* En el primer caso de ejemplo anterior, en 3realidad se pasa a x.__setitem__.

mgilson
fuente
11
"Python pasa objetos". No. Python pasa referencias (punteros a objetos).
user102008
6
@ user102008 - Punto justo. La semántica en este punto se vuelve muy turbia, me siento. En última instancia, tampoco son "indicadores". Al menos, ciertamente no en el mismo sentido que lo ha hecho en C. (No es necesario desreferenciarlos, por ejemplo). En mi modelo mental, es más fácil pensar en las cosas como "Nombres" y "Objetos". La asignación vincula un "Nombre" (s) a la izquierda a un "Objeto" (s) a la derecha. Cuando llamas a una función, pasas el "Objeto" (enlazándolo efectivamente a un nuevo nombre local dentro de la función).
mgilson
Esta es, por supuesto, una descripción de lo que son las referencias de Python , pero no es fácil encontrar una manera de describirlo sucintamente para aquellos que no están familiarizados con la terminología.
mgilson
2
No es de extrañar que nos confundamos con la terminología. Aquí lo tenemos descrito como llamada por objeto y aquí se describe como paso por valor . En otros lugares se llama "pasar por referencia" con un asterisco sobre lo que realmente significa ... Básicamente, el problema es que la comunidad no ha descubierto cómo llamarlo
mgilson
1
Las referencias de Python son exactamente las mismas que las de Java. Y las referencias de Java están de acuerdo con los punteros de JLS a objetos. Y básicamente hay una biyección completa entre las referencias de Java / Python y los punteros de C ++ a los objetos en semántica, y nada más describe esta semántica también. "No es necesario desreferenciarlos" Bueno, el .operador simplemente elimina la referencia del lado izquierdo. Los operadores pueden ser diferentes en diferentes idiomas.
user102008
31

La mayoría de los casos en los que necesitaría pasar por referencia son aquellos en los que debe devolver más de un valor a la persona que llama. Una "mejor práctica" es usar múltiples valores de retorno, lo cual es mucho más fácil de hacer en Python que en lenguajes como Java.

He aquí un ejemplo sencillo:

def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once
Gabe
fuente
9

No exactamente pasando un valor directamente, sino usándolo como si fuera pasado.

x = 7
def my_method():
    nonlocal x
    x += 1
my_method()
print(x) # 8

Advertencias:

  • nonlocal fue introducido en Python 3
  • Si el ámbito adjunto es global, utilice en globallugar de nonlocal.
Uri
fuente
7

Realmente, la mejor práctica es dar un paso atrás y preguntarse si realmente necesita hacer esto. ¿Por qué quiere modificar el valor de una variable que está pasando a la función?

Si necesita hacerlo para un truco rápido, la forma más rápida es pasar un listnúmero entero y quedarse con [0]cada uso, como lo demuestra la respuesta de mgilson.

Si necesita hacerlo para algo más significativo, escriba un classque tenga intcomo atributo, para que pueda configurarlo. Por supuesto, esto te obliga a pensar en un buen nombre para la clase y para el atributo; si no puedes pensar en nada, vuelve a leer la oración varias veces y luego usa el list.

De manera más general, si está intentando portar algún modismo de Java directamente a Python, lo está haciendo mal. Incluso cuando hay algo directamente correspondiente (como con static/ @staticmethod), todavía no desea usarlo en la mayoría de los programas de Python solo porque lo usaría en Java.

abarnert
fuente
JAVA no pasa enteros por referencia (enteros o cualquier objeto. Los objetos en caja también se reemplazan en la asignación).
Luis Masuelli
@LuisMasuelli Bueno, las primitivas en caja se tratan como objetos, lo único que impide su uso como lo quiere el OP es el hecho de que las cajas son inmutables (y dado que las primitivas en sí mismas también son inmutables, todo es inmutable y solo se puede cambiar en nivel variable)
Kroltan
Un buen caso de uso para esto es contar una cantidad de llamadas (un total, no una profundidad) dentro de una función recursiva. Necesita un número que se pueda incrementar en todas las llamadas ramificadas. Un int estándar simplemente no es
suficiente
4

En Python, cada valor es una referencia (un puntero a un objeto), al igual que los no primitivos en Java. Además, como Java, Python solo tiene paso por valor. Entonces, semánticamente, son más o menos lo mismo.

Dado que menciona Java en su pregunta, me gustaría ver cómo logra lo que desea en Java. Si puede mostrarlo en Java, puedo mostrarle cómo hacerlo exactamente de manera equivalente en Python.

newacct
fuente
4

Una matriz de un solo elemento numpy es mutable y, sin embargo, para la mayoría de los propósitos, se puede evaluar como si fuera una variable numérica de Python. Por lo tanto, es un contenedor de números por referencia más conveniente que una lista de un solo elemento.

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

salida:

3
Trisoloriansprotector solar
fuente
3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     
Shruti
fuente
3

Tal vez no sea una forma pitónica, pero puedes hacer esto

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref
Sergey Kovalev
fuente
Hombre que es inteligente.
xilpex
2

Quizás un poco más autodocumentado que el truco de la lista de longitud 1 es el viejo truco de tipo vacío:

def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)
mheyman
fuente
O envuelva el número en una clase: github.com/markrages/python_mutable_number
markrages
0

En Python, todo se pasa por valor, pero si desea modificar algún estado, puede cambiar el valor de un entero dentro de una lista u objeto que se pasa a un método.

recursivo
fuente
3
Creo que porque everything is passed by valueno es realmente cierto. arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Documentos de la
1
Las listas, los objetos y los diccionarios se pasan por referencia
AN88
2
Si eso fuera cierto, la asignación a un parámetro en una función se reflejaría en el sitio de la llamada. Como Astery citó, el paso es por valor y esos valores son referencias de objeto.
recursivo
0

La respuesta correcta es usar una clase y poner el valor dentro de la clase, esto le permite pasar por referencia exactamente como lo desee.

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)

Chris Pugmire
fuente