Paso por referencia en Python: inmutables vs mutables

1. Diferencia entre paso por valor y paso por referencia

En Python, existen dos formas de pasar argumentos a una función: “paso por valor” y “paso por referencia”.

  • Paso por valor: método que pasa una copia del valor como argumento a la función, de modo que si se modifica el argumento dentro de la función, la variable original no se ve afectada.
  • Paso por referencia: método que pasa la referencia (dirección) de la variable a la función, de modo que los cambios dentro de la función se reflejan en la variable original.

En Python, este comportamiento varía según la naturaleza del objeto. El “paso por referencia” de Python se aplica especialmente a los tipos de datos mutables, y tiene un gran impacto en el funcionamiento del código, por lo que es importante comprenderlo correctamente.

2. Características de los objetos en Python

Python, en el que todos los datos se tratan como objetos, clasifica a los objetos según sus características como inmutables (no modificables) o mutables (modificables). Esta característica hace que el comportamiento sea diferente cuando se pasan como argumentos a una función.

  • Objetos inmutables: objetos que no pueden modificarse después de su creación. Por ejemplo, enteros (int), números de punto flotante (float), cadenas (str), tuplas (tuple) etc. Estos tipos de datos, una vez creados, no pueden modificarse, por lo que incluso con paso por referencia, las operaciones dentro de la función no afectan al exterior.
  • Objetos mutables: objetos que pueden modificarse después de su creación. Por ejemplo, listas (list), diccionarios (dict), conjuntos (set) etc. Estos tipos de datos, al modificarse dentro de una función, reflejan esos cambios en el llamador, por lo que son particularmente afectados por el paso por referencia en Python.
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

3. Mecanismo de paso de argumentos en Python

En Python, al pasar argumentos a una función, se pasa una referencia al objeto. Este comportamiento también se conoce como “paso por valor de referencia”. A continuación se explica el comportamiento propio de Python según la diferencia entre objetos inmutables y mutables, con ejemplos concretos.

3.1 En caso de objetos inmutables

Cuando se pasa un objeto inmutable a una función, si dentro de la función se intenta asignar un nuevo valor se genera un nuevo objeto, sin afectar al objeto original.

Ejemplo: paso de argumento entero inmutable

def modify_number(num):
    num = num * 2
    print("Número dentro de la función:", num)

original_num = 5
modify_number(original_num)
print("Número fuera de la función:", original_num)

Salida:

Número dentro de la función: 10
Número fuera de la función: 5

ExplicaciónEn la función modify_number se duplica la variable num, pero no afecta a original_num fuera de la función. Como los enteros son inmutables, al asignarse un nuevo valor se genera un objeto diferente y original_num no se modifica.

3.2 En caso de objetos mutables

Por otro lado, cuando se pasa un objeto mutable a una función, la referencia se utiliza también dentro de la función, por lo que los cambios realizados dentro de la función se reflejan en el objeto original.

Ejemplo: paso de argumento lista mutable

def add_item(my_list):
    my_list.append("Elemento añadido")
    print("Lista dentro de la función:", my_list)

original_list = ["Elemento 1", "Elemento 2"]
add_item(original_list)
print("Lista fuera de la función:", original_list)

Salida:

Lista dentro de la función: ['Elemento1', 'Elemento2', 'Elemento añadido']
Lista fuera de la función: ['Elemento1', 'Elemento2', 'Elemento añadido']

ExplicaciónComo la lista es mutable, los cambios dentro de la función add_item se reflejan directamente en la lista fuera de la función original_list. De esta manera, los objetos mutables se comparten entre el interior y el exterior de la función, por lo que los cambios dentro de la función afectan al llamador.

4. Precauciones y contramedidas para el paso por referencia

Al pasar objetos mutables a una función en Python, pueden producirse comportamientos inesperados. A continuación se presentan medidas para evitar este tipo de problemas.

4.1 Usar copias de objetos

Si se desea modificar un objeto dentro de una función, copiar el objeto original antes de operarlo permite evitar afectar al objeto original. Se pueden crear copias superficiales (copy.copy) o copias profundas (copy.deepcopy) usando el módulo copy de Python.

Ejemplo: Estrategia para evitar objetos mutables usando copias profundas

import copy

def add_item(my_list):
    my_list_copy = copy.deepcopy(my_list)
    my_list_copy.append("Elemento añadido")
    print("Lista dentro de la función (copia):", my_list_copy)

original_list = ["Elemento1", "Elemento2"]
add_item(original_list)
print("Lista fuera de la función:", original_list)

Salida:

Lista dentro de la función (copia): ['Elemento 1', 'Elemento 2', 'Elemento añadido']
Lista fuera de la función: ['Elemento 1', 'Elemento 2']

ExplicaciónAl usar deepcopy, se genera un nuevo objeto dentro de la función, sin afectar a la lista original. De este modo, los cambios pueden realizarse de forma independiente dentro y fuera de la función.

4.2 Usar None en los argumentos por defecto

Se recomienda evitar establecer objetos mutables como argumentos por defecto de una función; en su lugar, use None como argumento por defecto y cree el objeto dentro de la función.

Ejemplo: Forma segura de establecer el argumento por defecto a None

def add_item(my_list=None):
    if my_list is None:
        my_list = []
    my_list.append("nuevo elemento")
    print("lista dentro de la función:", my_list)
    return my_list

# Cuando no se pasa una lista
result = add_item()
print("lista devuelta:", result)

# Cuando se pasa una lista
existing_list = ["elemento1", "elemento2"]
result = add_item(existing_list)
print("lista devuelta:", result)
print("lista original:", existing_list)

Salida:

Lista dentro de la función: ['Nuevo elemento']
Lista devuelta: ['Nuevo elemento']
Lista dentro de la función: ['Elemento 1', 'Elemento 2', 'Nuevo elemento']
Lista devuelta: ['Elemento 1', 'Elemento 2', 'Nuevo elemento']
Lista original: ['Elemento 1', 'Elemento 2', 'Nuevo elemento']

ExplicaciónAl usar el argumento por defecto None y generar una nueva lista dentro de la función, se evita que la lista externa se modifique inesperadamente. Configurar None como argumento por defecto deja claro si se debe crear una nueva lista o usar la lista proporcionada.

年収訴求

5. Ejemplo práctico: Código para profundizar la comprensión del paso por referencia

Revisemos el contenido hasta aquí mediante un ejemplo práctico.

Ejemplo de cambio de claves y valores de un diccionario

Los diccionarios también son objetos mutables, y las operaciones dentro de una función afectan directamente fuera de ella. En el siguiente ejemplo se verifica el comportamiento al cambiar claves y valores de un diccionario.

def modify_dict(my_dict):
    my_dict["nueva clave"] = "nuevo valor"
    print("diccionario dentro de la función:", my_dict)

original_dict = {"clave1": "valor1", "clave2": "valor2"}
modify_dict(original_dict)
print("diccionario fuera de la función:", original_dict)

Salida:

Diccionario dentro de la función: {'clave1': 'valor1', 'clave2': 'valor2', 'nueva clave': 'nuevo valor'}
Diccionario fuera de la función: {'clave1': 'valor1', 'clave2': 'valor2', 'nueva clave': 'nuevo valor'}

ExplicaciónComo el diccionario es mutable, las operaciones de adición dentro de la función se reflejan también en original_dict fuera de la función. De esta manera, al igual que las listas, los diccionarios pueden afectar el exterior mediante paso por referencia, lo cual debemos comprender.

6. Resumen

En Python, el “paso por referencia” funciona de manera diferente según las características del objeto, por lo que es necesario comprenderlo para mejorar la fiabilidad del código. En particular, para evitar comportamientos inesperados al pasar por referencia objetos mutables, es recomendable utilizar copias y argumentos por defecto None.

Puntos clave

  • Diferencia entre paso por valor y paso por referencia: En Python, según la naturaleza del objeto, los cambios dentro de la función pueden o no afectar a la variable original.
  • Inmutable y mutable: Los objetos inmutables de Python no reflejan cambios dentro de la función, mientras que los objetos mutables se comparten entre el interior y el exterior de la función mediante paso por referencia.
  • Métodos de mitigación: Para evitar comportamientos inesperados con objetos mutables, es útil usar el módulo copy y deepcopy, o establecer los argumentos por defecto a None.

Espero que a través de este artículo haya profundizado su comprensión del paso por referencia en Python. Utilice este conocimiento para implementar código más eficiente y con menos errores.