Unidad II · Tema 2.4 Disponible

Subprogramas y paso de parámetros

Funciones, procedimientos y mecanismos de paso de parámetros.

Objetivo de aprendizaje

Al concluir este tema, el estudiante será capaz de:

  1. Distinguir subprograma, función y procedimiento, y parámetro formal de argumento.
  2. Explicar y comparar el paso por valor, por referencia y por referencia de objeto, y predecir el resultado de una llamada con cada uno.
  3. Reconocer los efectos secundarios de un subprograma y aplicar alternativas, como devolver varios valores.
  4. Explicar el comportamiento de los valores por omisión, en particular la trampa de los valores mutables.
  5. Describir la sobrecarga y cómo la resuelven el compilador de Java y el de C++, y cómo se obtiene un efecto análogo en Python.

Ruta de trabajo del tema

Etapa Contenido Producto del estudiante
1 Planteamiento inicial Hipótesis escrita
2 Subprogramas, parámetros y argumentos Vocabulario preciso
3 Tres mecanismos de paso de parámetros Predicciones por mecanismo
4 Objetos mutables como argumentos Regla propia sobre qué cambia
5 Efectos secundarios y alternativas Versión sin efectos secundarios
6 Valores por omisión Explicación de una trampa
7 Sobrecarga Resolución de llamadas
8 Verificación y reformulación Cuestionario y respuesta revisada

1. Planteamiento inicial

Una de las primeras funciones que se intenta escribir es la que intercambia el valor de dos variables. Su aspecto es natural:

def intercambiar(a, b):
    a, b = b, a

x = 1
y = 2
intercambiar(x, y)
print(x, y)

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Esta hipótesis se retomará al final de la sección 3.


2. Subprogramas, parámetros y argumentos

Definición — Subprograma: unidad de código con nombre, parametrizable e invocable desde distintos puntos del programa. Permite reutilizar código y descomponer un problema en partes (abstracción).

Definición — Parámetro formal y argumento (parámetro real): el parámetro formal es el nombre que aparece en la definición del subprograma (a y b en def intercambiar(a, b)); el argumento es la expresión que se escribe en la llamada (x y y en intercambiar(x, y)). Al llamar, cada parámetro formal queda ligado a un argumento.

Según su propósito se distingue entre función, que calcula y devuelve un valor, y procedimiento, que se invoca por su efecto. Lenguajes como Pascal distinguen ambas formas; Python solo tiene funciones (las que no devuelven nada devuelven None).

Definición — Efecto secundario (side effect): cualquier cambio observable que un subprograma produce fuera de su valor de retorno: modificar un argumento mutable, una variable global, un archivo o la pantalla.

Una función sin efectos secundarios, cuyo resultado depende solo de sus argumentos, se denomina pura. Son más fáciles de razonar y de probar, y son la base del paradigma funcional (Unidad IV).


3. Mecanismos de paso de parámetros

Al llamar a un subprograma hay que decidir qué se entrega al parámetro formal. Existen tres modelos principales:

Mecanismo Qué recibe el parámetro Si el subprograma reasigna el parámetro Lenguajes
Por valor Una copia independiente del valor del argumento No afecta al argumento original C; Java y C++ con tipos primitivos
Por referencia Un alias de la variable del llamador Sí afecta a la variable del llamador C++ (con &), Pascal (var), C# (ref)
Por referencia de objeto Una copia de la referencia al objeto: parámetro y argumento nombran al mismo objeto, pero son nombres distintos No afecta al argumento original; mutar el objeto sí es visible Python, Java (objetos), JavaScript

Definición — Paso por referencia de objeto (pass by object reference, o por asignación): mecanismo de Python. Equivale a ejecutar, al comenzar la llamada, parametro = argumento: es una asignación de nombres, tal como se estudió en el tema 2.1. El parámetro es un nuevo nombre para el mismo objeto.

Caso de estudio. En intercambiar(x, y), los parámetros a y b quedan ligados a los mismos objetos que x e y. La sentencia a, b = b, a religa los nombres locales a y b; no toca a x ni a y, que siguen ligados a sus objetos originales. Para que el intercambio fuera visible se necesitaría un paso por referencia (un alias de las variables del llamador) o devolver los nuevos valores.

Aplicación práctica. La corrida siguiente ejecuta el mismo programa con tres mecanismos, que se eligen con el selector. Python usa el mecanismo por referencia de objeto; los otros dos se simulan para contrastar. Active la predicción y anticipe el resultado con cada mecanismo.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Comparación guiada. En el cuadro siguiente, cada programa se ejecuta con los tres mecanismos. Anticipe, para cada uno, si el efecto buscado llega al llamador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Contraste con el planteamiento inicial

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


4. Objetos mutables como argumentos

El paso por referencia de objeto produce dos comportamientos que conviene distinguir con claridad:

Dentro de la función… Efecto visible para el llamador
Mutar el objeto recibido (lista.append(99)) Sí: parámetro y argumento nombran al mismo objeto
Reasignar el parámetro (lista = [99]) No: solo se religa el nombre local
Operar con un objeto inmutable (n = n + 1) No: se crea un objeto nuevo (los enteros no se pueden mutar)

Es la distinción entre mutar y reasignar del tema 2.1, aplicada a una llamada.

Caso de estudio. agregar(lista) ejecuta lista.append(99); la lista del llamador queda modificada. reemplazar(lista) ejecuta lista = [99]; la lista del llamador no cambia. Con el paso por valor (copia), ni siquiera append sería visible, porque se trabajaría con una copia.

Importante: una función que muta sus argumentos tiene un efecto secundario que el llamador no ve en la llamada. Si ese efecto no está documentado, el programa se vuelve difícil de entender. La convención es documentarlo o devolver una lista nueva.

Aplicación práctica.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


5. Efectos secundarios y alternativas

Cuando se necesita que un subprograma «cambie dos valores» sin usar paso por referencia, la alternativa habitual es devolver todos los resultados y dejar que el llamador los asigne. Python lo hace cómodo mediante tuplas y asignación múltiple:

def intercambiar(a, b):
    return b, a

x, y = intercambiar(x, y)

Esta versión es pura: no modifica nada fuera de sí misma, y toda la información que produce aparece en la llamada. Su comportamiento es el mismo con cualquier mecanismo de paso de parámetros, lo cual la hace más portátil y más fácil de probar.

Aplicación práctica.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


6. Valores por omisión

Definición — Valor por omisión: valor que toma un parámetro cuando la llamada no proporciona el argumento correspondiente (def f(x, lista=[])).

En Python, los valores por omisión se evalúan una sola vez, al ejecutarse la definición de la función, y no en cada llamada. Con un valor inmutable no hay consecuencia visible; con uno mutable el mismo objeto se comparte entre todas las llamadas que lo usan.

Caso de estudio. En agregar(x, lista=[]), la lista vacía se crea al definir la función. La primera llamada añade un elemento a ese objeto, la segunda lo encuentra ya modificado, y así sucesivamente. La llamada que sí proporciona su propia lista (agregar(3, [])) no se ve afectada.

La forma habitual de evitarlo es usar None como valor por omisión y crear la lista dentro de la función:

def agregar(x, lista=None):
    if lista is None:
        lista = []
    lista.append(x)
    return lista

Aplicación práctica.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


7. Sobrecarga

Definición — Sobrecarga (overloading): existencia de varios subprogramas con el mismo nombre y distintas listas de parámetros (por el número o por los tipos). El compilador (o el intérprete) elige cuál invocar a partir de los argumentos de la llamada.

Java y C++ resuelven la sobrecarga en compilación, según los tipos declarados de los argumentos. Cuando hay varias versiones aplicables, se elige la «mejor», y si no hay una mejor, la llamada es ambigua y el compilador la rechaza. Las reglas de ambos lenguajes coinciden en lo esencial, pero no en todos los casos.

Lenguaje Sobrecarga Observación
Java Sí, resuelta al compilar Se prefiere la versión más específica; los tipos numéricos se ensanchan (int → long → float → double)
C++ Sí, resuelta al compilar Se comparan las conversiones de cada argumento: coincidencia exacta, promoción y conversión
Python No Una segunda definición con el mismo nombre reemplaza a la primera

En Python se obtiene un efecto análogo con valores por omisión, con argumentos de número variable (*args) o con functools.singledispatch, que elige la versión en ejecución según el tipo del primer argumento.

Caso de estudio. Dadas f(long) y f(double), la llamada f(5) con un int: en Java se elige f(long), porque long es más específico que double (un long cabe en un double). En C++ la llamada es ambigua, porque convertir int en long y convertir int en double son ambas «conversiones» del mismo rango.

Importante: las reglas del laboratorio son una versión simplificada de las que definen los estándares de Java y C++ (no incluye genéricos, argumentos nulos ni conversiones definidas por el usuario). Sirven para razonar sobre los casos comunes.

Aplicación práctica. Elija un conjunto de funciones y una llamada, anticipe qué ocurrirá y compruebe. La tabla final muestra qué versiones son aplicables y qué conversión necesita cada argumento.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


8. Errores conceptuales frecuentes

Error común: afirmar que «Python pasa por valor» o que «pasa por referencia». Pasa por referencia de objeto: el parámetro es un nuevo nombre para el mismo objeto.

Error común: creer que reasignar el parámetro modifica el argumento del llamador. Solo religa el nombre local.

Error común: creer que una función no puede modificar una lista recibida porque «Python pasa por valor». Si la muta, el cambio es visible.

Error común: usar una lista o un diccionario como valor por omisión sin considerar que se comparte entre llamadas.

Error común: esperar sobrecarga por tipos en Python, o esperar que Java y C++ resuelvan siempre del mismo modo una llamada ambigua.


9. Verificación del tema

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


10. Libreta de trabajo

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


11. Preguntas de reflexión

  1. ¿Por qué se dice que «Python pasa por referencia de objeto» y no simplemente por valor o por referencia? Respóndalo con un ejemplo de cada comportamiento.
  2. ¿Qué ventajas tiene una función pura frente a una que modifica sus argumentos? ¿Cuándo sería razonable modificarlos?
  3. ¿Qué criterio de la pragmática (legibilidad, facilidad de escritura, fiabilidad, costo) favorece el paso por referencia de C++? ¿Cuál se sacrifica?
  4. ¿Por qué cree que Python evalúa los valores por omisión al definir la función y no al llamarla? ¿Qué problema evitaría la otra decisión y cuál crearía?
  5. Compare la sobrecarga de Java con el uso de valores por omisión de Python para ofrecer varias formas de llamar a una función. ¿Qué detecta cada una antes de ejecutar?

12. Lecturas recomendadas

  • Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulos sobre subprogramas y sobre la implementación de subprogramas (modelos de paso de parámetros). Pearson.
  • Python Software Foundation. The Python Tutorial, sección «More on defining functions» (valores por omisión y argumentos), y The Python Language Reference, sección «Function definitions». Acceso libre en docs.python.org.
  • Scott, M. (2015). Programming language pragmatics (4th ed.), capítulo sobre abstracción del control (subrutinas y paso de parámetros). Morgan Kaufmann.