Los mecanismos de pasaje de parámetros hacen relación a los acuerdos internos que maneja un lenguaje de programación, para compartir información (parámetros) almacenada en variables, y permitir que la misma sea utilizada en funciones (tras lo cual pueden ser simplemente consultadas, o transformadas de algún modo).Si tienes experiencia en otros lenguajes de programación (o incluso si únicamente has leído nuestro artículo anterior, referido a VBA ByRef y ByVal: Pasaje de Argumentos entre Procedimientos), probablemente estés familiarizado con que existen dos mecanismos principales para compartir dicha información:
- Por valor (cuando la información se accede únicamente para ser leída, y no puede ser modificada cuando se invoca: la función recibe una copia de los objetos del argumento y los almacena en una nueva ubicación de memoria)
- Por referencia (cuando la función que recibe el argumento puede modificarlo, y con ello devolver un nuevo valor, en la misma ubicación de memoria)
Esto es una distinción común que vale la pena tener en cuenta en lenguajes como C o Java, o incluso en VBA. Teniendo esto en mente, tal vez alguna vez te hayas preguntado cómo actúan dichos mecanismos en Python. Esto abre un debate interesante, y antes de empezar, analicemos lo que ocurre en un código como el siguiente:
- lista_a = [0]
- lista_b = lista_a
- lista_b.append(1)
- print(lista_a)
>> [0, 1]
Si no hemos añadido (append) elementos a la lista_a, ¿por qué estamos viendo que se ha modificado? Intentaremos hacer una introducción al abordaje de este misterio.
Sígueme en la siguiente reflexión: Albert Einstein (la persona) y el nombre Albert Einstein, ¿son lo mismo? Si le preguntáramos a Python, la respuesta sería no: una persona y un nombre no son la misma cosa, a pesar de que apunten o "refieran" a lo mismo. Puesto que en Python (casi) todo son objetos, podemos reformularlo ligeramente: un objeto, y el nombre que le damos a dicho objeto, son cosas diferentes. El proceso mediante el cual se crea una variable en realidad está creando una referencia (o "nombre") a un objeto.
Si retomamos nuestro ejemplo anterior, podemos empezar a intuir lo que ha ocurrido a partir de este conocimiento: el objeto en la memoria ([0]) ha sido accedido a través de dos nombres diferentes (lista_a, y lista_b), se ha modificado al ser invocado con el nombre lista_b, pero podemos acceder a esta nueva versión del objeto a través del otro nombre: lista_a. ¡El objeto es el mismo, pero tenemos dos referencias para llegar a él!
Entonces, ¿es Python un lenguaje que invoca por valor o por referencia? Aún no estamos listos para responder esta pregunta, necesitamos incorporar un elemento más.
Objetos mutables e inmutables: cuando comenzamos a aprender los diferentes tipos de datos en Python, una de las primeras cosas que conocemos es que existen dos categorías principales: los objetos mutables (integrados, entre otros, por listas, diccionarios y sets), y los objetos inmutables (int, floats, strings, tuples, bytes, etc.). Si tenemos presente esto, resultará más sencillo evitar la confusión: cuando pasamos argumentos mutables, podemos llegar a considerar que los invocamos por referencia, y cuando pasamos argumentos inmutables, que lo hacemos por valor. Sin embargo, esto sería un error, ya que no obedece al mecanismo de invocación de la función, sino a algo más fundamental, que se relaciona con la forma en que se administran en memoria distintos tipos de datos.
En nuestro ejemplo anterior, el objeto [0] fue modificado (mutó), debido a que el propio tipo de dato lo permite. Por lo cual, el valor modificado del objeto está disponible por fuera de la función que lo modificó, y aún puede ser accedido de esa manera a través de las diferentes referencias a este objeto.
Entonces, mientras que en lenguajes como Java y C, el concepto de llamada por valor es importante (lo que significa que la función llamada recibe una copia local de la información a la que se refiera la invocación), en Python realmente este concepto no es aplicable: los valores se envían a las funciones por vías de referencias a objetos. Lo que ocurra luego con dicho objeto, estará determinado por su tipo (mutable/inmutable).
Si bien es una discusión sobre la que podríamos ahondar mucho más desde las ciencias de la computación y palabrerío técnico, puede que esta primera aproximación te sea de utilidad para empezar a comprender y prever la forma en la que tus variables se verán (o no) modificadas a medida que tu código se va ejecutando.