Saltar al contenido

¿Cómo reasignar una variable en Python sin cambiar su identificación?

Solución:

No estoy seguro de si está confundido acerca de las variables en Python o acerca de los valores inmutables. Así que voy a explicar ambos, y la mitad de la respuesta probablemente parecerá “no duh, ya lo sabía”, pero la otra mitad debería ser útil.


En Python, a diferencia de, digamos, C, una variable no es una ubicación donde viven los valores. Es solo un nombre. Los valores viven donde quieran.1 Entonces, cuando haces esto:

a = 10
b = a

No estas haciendo b en una referencia a a. Esa idea ni siquiera tiene sentido en Python. Estas haciendo a en un nombre para 10, y luego hacer b en otro nombre para 10. Y si luego haces esto:

a = 11

… usted ha hecho a en un nombre para 11, pero esto no tiene ningún efecto en b—Sigue siendo solo un nombre para 10.


Esto también significa que id(a) no te da el ID de la variable a, porque allí es no hay tal cosa. a es solo un nombre que se busca en algún espacio de nombres (por ejemplo, el dict global de un módulo). Es el valor, 11 (o, si lo ejecutó antes, el valor diferente 10) que tiene una identificación. (Ya que estamos en eso: también son valores, no variables, los que se escriben. No es relevante aquí, pero vale la pena conocerlo).


Las cosas se ponen un poco complicadas cuando se trata de mutabilidad. Por ejemplo:

a = [1, 2, 3]
b = a

Esto todavía hace a y b ambos nombres para una lista.

a[0] = 0

Esto no se asigna a a, asi que a y b siguen siendo nombres para la misma lista. Eso lo hace asignar a a[0], que es parte de esa lista. Entonces, la lista que a y b ambos nombres ahora tienen [0, 2, 3].

a.extend([4, 5])

Esto obviamente hace lo mismo: a y b ahora nombra la lista [0, 2, 3, 4, 5].


Aquí es donde las cosas se vuelven confusas:

a += [6]

¿Es una tarea que vuelve a enlazar? ao simplemente está mutando el valor que a es un nombre para? De hecho, son ambos. Lo que esto significa, bajo las sábanas, es:

a = a.__iadd__([6])

… o, aproximadamente:

_tmp = a
_tmp.extend([6])
a = _tmp

Así que nosotros están asignar a a, pero le estamos asignando el mismo valor que ya nombró. Y mientras tanto, también estamos mutando ese valor, que sigue siendo el valor que b nombres.


Y ahora:

a = 10
b = 10
a += 1

Probablemente pueda adivinar que la última línea hace algo como esto:

a = a.__iadd__(1)

Eso no es del todo true, porque a no define un __iadd__ método, por lo que vuelve a esto:

a = a.__add__(1)

Pero eso no es lo importante.2 Lo importante es que, porque los enteros, a diferencia de las listas, son inmutables. No puedes convertir el número 10 en el número 11 de la forma en que lo harías en INTERCAL o (más o menos) Fortran o ese extraño sueño que tuviste en el que eras el X-Man más extraño. Y no hay una “variable que contenga el número 10” que pueda establecer en 11, porque esto no es C ++. Así que esto tiene para devolver un nuevo valor, el valor 11.

Entonces, a se convierte en un nombre para ese nuevo 11. Mientras tanto, b sigue siendo un nombre para 10. Es como el primer ejemplo.


Pero, después de todo esto diciéndote lo imposible que es hacer lo que quieres, te voy a decir lo fácil que es hacer lo que quieres.

¿Recuerda antes, cuando mencioné que puede mutar una lista, y todos los nombres de esa lista verán el nuevo valor? Entonces, ¿y si hicieras esto?

a = [10]
b = a
a[0] += 1

Ahora b[0] va a ser 11.


O puede crear una clase:

class Num:
    pass

a = Num()
a.num = 10
b = a
a.num += 1

Ahora, b.num es 11.


O incluso puede crear una clase que implemente __add__ y __iadd__ y todos los demás métodos numéricos, por lo que puede contener números (casi) de forma transparente, pero lo hace de forma mutante.

class Num:
    def __init__(self, num):
        self.num = num
    def __repr__(self):
        return f'type(self).__name__(self.num)'
    def __str__(self):
        return str(self.num)
    def __add__(self, other):
        return type(self)(self.num + other)
    def __radd__(self, other):
        return type(self)(other + self.num)
    def __iadd__(self, other):
        self.num += other
        return self
    # etc.

Y ahora:

a = Num(10)
b = a
a += 1

Y b es un nombre para lo mismo Num(11) como a.

Sin embargo, si realmente desea hacer esto, debería considerar hacer algo específico como Integer en lugar de un genérico Num que contiene cualquier cosa que actúe como un número, y usando el ABC apropiado en el numbers módulo para verificar que cubrió todos los key métodos, para obtener implementaciones gratuitas para muchos métodos opcionales, y para poder pasar isinstance cheques de tipo. (Y probablemente llame num.__int__ en su constructor el camino int lo hace, o al menos en un caso especial isinstance(num, Integer) para que no termine con una referencia a una referencia a una referencia … a menos que eso sea lo que desee).


1. Bueno, viven donde el intérprete quiere que vivan, como los rumanos bajo Ceaușescu. Pero si es un tipo incorporado / de extensión escrito en C y un miembro pagado del Partido, podría anular __new__ con un constructor que no se basa en super asignar, pero de lo contrario no tiene otra opción.

2. Pero no es completamente insignificante. Por convención (y, por supuesto, en todos los tipos incorporados y stdlib siguen la convención), __add__ no muta, __iadd__ lo hace. Entonces, tipos mutables como list definir ambos, lo que significa que obtienen un comportamiento en el lugar para a += b pero copiando comportamiento para a + b, mientras que tipos inmutables como tuple y int definir solo __add__, por lo que obtienen un comportamiento de copia para ambos. Python no te obliga a hacer las cosas de esta manera, pero tu tipo sería muy extraño si no eligiera uno de esos dos. Si está familiarizado con C ++, es lo mismo: generalmente implementa operator+= mutando en el lugar y devolviendo una referencia a this, y operator+ copiando y luego devolviendo += en la copia, pero el lenguaje no te obliga a hacerlo, es confuso si no lo haces.

Nos puedes añadir valor a nuestra información cooperando tu experiencia en las explicaciones.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

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