¿Puedes resolver estos tres problemas (engañosamente) simples en Python?

Desde el comienzo de mi camino como desarrollador de software, realmente me encantó profundizar en el interior de los lenguajes de programación. Siempre me he preguntado cómo funciona esta o aquella construcción, cómo funciona este o aquel comando, qué hay bajo el capó del azúcar sintáctico, etc. Recientemente encontré un artículo interesante con ejemplos de cómo los objetos mutables e inmutables en Python no siempre funcionan obviamente. En mi opinión, la clave es cómo cambia el comportamiento del código según el tipo de datos utilizado, manteniendo la semántica idéntica y las construcciones de lenguaje utilizadas. Este es un gran ejemplo de lo que necesita pensar no solo al escribir, sino también al usarlo. Invito a todos a leer la traducción.







Pruebe estos tres problemas y luego verifique las respuestas al final del artículo.



Consejo : Los problemas tienen algo en común, así que repase el primer problema cuando pase al segundo o al tercero, será más fácil para usted.



Primera tarea



Hay varias variables:



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)


¿Qué se mostrará al imprimir ly s?



Segunda tarea



Definamos una función simple:



def f(x, s=set()):
    s.add(x)
    print(s)


¿Qué sucede si llamas?



>>f(7)
>>f(6, {4, 5})
>>f(2)


Tercera tarea



Definamos dos funciones simples:



def f():
    l = [1]
    def inner(x):
        l.append(x)
        return l
    return inner

def g():
    y = 1
    def inner(x):
        y += x
        return y
    return inner


¿Qué obtenemos después de ejecutar estos comandos?



>>f_inner = f()
>>print(f_inner(2))

>>g_inner = g()
>>print(g_inner(2))


¿Qué tan seguro está en sus respuestas? Revisemos su caso.



La solución al primer problema.



>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


¿Por qué la segunda lista responde a un cambio en su primer elemento a.append(5), y la primera lista ignora por completo el mismo cambio x+=5?



Solución del segundo problema.



Veamos qué pasa:



>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


Espera, ¿no debería ser el último resultado {2}?



La solución al tercer problema.



El resultado será así:



>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


¿ g_inner(2)Por qué no traicionó 3? ¿ f()Por qué la función interna recuerda el alcance externo, pero la función interna g()no? ¡Son casi idénticos!



Explicación



¿Qué pasa si te digo que todos estos comportamientos extraños tienen que ver con la diferencia entre objetos mutables e inmutables en Python?



Los objetos modificables, como listas, conjuntos o diccionarios, se pueden modificar localmente. Los objetos inmutables, como los valores numéricos y de cadena, las tuplas no se pueden modificar; su "cambio" conducirá a la creación de nuevos objetos.



Primera explicación de tarea



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


Como es xinmutable, la operación x+=5no cambia el objeto original, sino que crea uno nuevo. Pero el primer elemento de la lista todavía se refiere al objeto original, por lo que su valor no cambia.



Porque un objeto mutable, el comando a.append(5)modifica el objeto original (en lugar de crear uno nuevo) y la lista s"ve" los cambios.



Explicación de la segunda tarea.



def f(x, s=set()):
    s.add(x)
    print(s)

>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


Con los dos primeros resultados, todo está claro: el primer valor se 7agrega al conjunto inicialmente vacío y resulta {7}; entonces el valor se 6agrega al conjunto {4, 5}y se obtiene {4, 5, 6}.



Y entonces comienzan las rarezas. El valor no se 2agrega al conjunto vacío, sino a {7}. ¿Por qué? El valor inicial del parámetro opcional se sevalúa solo una vez: en la primera llamada, s se inicializará como un conjunto vacío. Y como es mutable, f(7)cambiará en su lugar después de ser llamado . La segunda llamada f(6, {4, 5})no afectará el parámetro predeterminado: se reemplaza por un conjunto {4, 5}, es decir, es {4, 5}una variable diferente. La tercera llamada f(2)usa la misma variablesque se usó en la primera llamada, pero no se reinicializa como un conjunto vacío, sino que se toma de su valor anterior {7}.



Por lo tanto, no debe usar argumentos mutables como argumentos predeterminados. En este caso, la función necesita ser cambiada:



def f(x, s=None):
    if s is None:
        s = set()
    s.add(x)
    print(s)


Explicación de la tercera tarea.



def f():
   l = [1]
   def inner(x):
       l.append(x)
       return l
   return inner

def g():
   y = 1
   def inner(x):
       y += x
       return y
   return inner

>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


Aquí estamos tratando con cierres: las funciones internas recuerdan cómo se veía su espacio de nombres externo cuando se definieron. O al menos deberían recordarlo, pero la segunda función hace que la cara del póker se comporte como si no hubiera oído hablar de su espacio de nombres externo.



¿Por qué está pasando esto? Cuando ejecutamos l.append(x), el objeto mutable creado cuando se define la función cambia. Pero la variable ltodavía se refiere a la dirección anterior en la memoria. Sin embargo, tratar de cambiar una variable inmutable en la segunda función y += xhace que y comience a referirse a una dirección de memoria diferente: se olvidará la y original, lo que dará como resultado un UnboundLocalError.



Conclusión



La diferencia entre objetos mutables e inmutables en Python es muy importante. Evite el comportamiento extraño descrito en este artículo. Especialmente:



  • .
  • - .



All Articles