Funciones#

Motivación#

Para evaluar el polinomio \(f(x) = 2 x^2 - 5x + 2\) en \(x=2\), habíamos escrito el siguiente código:

x = 2
2 * x**2 - 5 * x + 2
0

Al parametrizarlo con la variable x, es más fácil cambiar el punto donde evaluamos el polinomio. ya que hay una única fuente para su valor: la linea que declara x=2.

Pero, ¿cómo podemos evaluar el polinomio en más de un punto?

Una opción es copiar y pegar el código dos veces:

x = 2
y2 = 2 * x**2 - 5 * x + 2

x = 3
y3 = 2 * x**2 - 5 * x + 2

y2, y3
(0, 5)

donde guardamos cada valor en una variable distinta (y2 para x=2 e y3 para x=3).

El problema con esta solución es similar al que teníamos cuando no usabamos la variable x: no hay una única fuente para el polinomio.

Por ejemplo, si queremos cambiar el polinomio por uno de otro grado, tenemos que editar todas las lineas donde dice: ... = 2 * x**2 - 5 * x + 2.

Entonces, queremos definir en un único lugar el polinomio, y después poder evaluarlo en distintos x.

Para eso, necesitamos poder definir una variable x «indeterminada», es decir, que aún no tome un valor concreto.

Esto se puede lograr definiendo una función.

Definiendo una función#

La sintaxis para definir una función es la siguiente:

def duplicar(n):
    y = 2 * n
    return y

donde definimos una función llamada duplicar, con un parámetro n.

La primera linea de la función se compone de:

  • def, una palabra clave para que Python sepa que estamos definiendo una función,

  • duplicar, que es el nombre de la variable donde se va a guardar esta función,

  • (n), donde ponemos el nombre de la «variable indeterminada» o parámetro que toma la función,

  • :, al final, para indicar que comienza el código de la función.

En las siguientes lineas, está el bloque de código que se va a ejecutar cuando se corra la función.

Para que Python pueda distinguir que código pertenece a la función, el código debe estar «indentado», tabulado, o con sangría, respecto a la palabra clave def.

La convención es usar 4 espacios, pero cualquier número de espacios funciona (mientras sea la misma cantidad en un mismo bloque).

Finalmente, está el return, que es otra palabra clave y será el resultado que nos «devolverá» función al ejecutarla.

Llamando una función#

Ya definimos la función duplicar(n).

Pero, si tratamos de acceder a la variable n, nos dice que aún no existe:

n
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 n

NameError: name 'n' is not defined

Y, por lo tanto, tampoco existe y.

Solo existe la «receta» que está guardada en la variable duplicar:

duplicar
<function __main__.duplicar(n)>

Para usar la receta, necesitamos llamarla (del inglés, call) pasándole los parámetros, que se hace de la siguiente manera:

z = duplicar(n=3)

z
6

donde guardamos el resultado en la variable z.

La linea z = duplicar(n=3) es equivalente* a haber ejecutado el siguiente código:

n = 3
y = 2 * n
z = y
del n
del y

*aunque no exactamente igual.

Es decir, ni n, el parámetro, ni y, una variable intermedia de la función, existen después de ejecutarse duplicar(n=3):

n
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 n

NameError: name 'n' is not defined

Ambas, n e y, son variables internas a la función.

Al correr esta linea,

z = duplicar(n=3)

Python corre el código de duplicar en una hoja borrador, y después reemplaza el llamado, duplicar(n=3), por su resultado, 6:

z = 6

Al llamar una función, también se le puede pasar una variable, que no tiene porque coincidir en nombre con el parámetro:

mi_numero = 21

duplicar(n=mi_numero)
42

Y tampoco hace falta especificar el nombre del parámetro:

duplicar(mi_numero)
42

aunque a veces hace el código más legible. Más adelante veremos mas ejemplos.

Ejercicio 1#

Definir una función que tome un parámetro x y devuelva el resultado de evaluar el polinomio \(f(x) = 2 x^2 - 8x + 6\).

Evaluar en \(x=2\) y \(x=3\).

# Escriba su solución aquí

Solución#

Hide code cell content
def mi_polinomio(x):
    return 2 * x**2 - 8 * x + 6


y2 = mi_polinomio(2)
y3 = mi_polinomio(3)

y2, y3
(-2, 0)

Noten que, dentro de la función, no hace falta guardar el resultado en una variable intermedia.

Se puede poner directamente return de una expresión.

Múltiples parámetros#

Para definir una función con múltiples parámetros, estos se separan con comas (,). Por ejemplo:

def dividir(x, y):
    return x / y


dividir(8, 4)
2.0

Llamando funciones de múltiples parámetros#

Vimos que hay dos formas de llamar a las funciones:

  • por posición

  • por nombre o palabra clave (keyword, en inglés).

Por posición, se asignan los valores en el mismo orden en el que se definió en la función:

dividir(8, 4)
2.0

Es decir, para dicha ejecución de dividir, x=8 e y=4.

Cuando se pasa por nombre, se puede cambiar el orden de los parámetros:

dividir(y=4, x=8)
2.0

También, se pueden combinar ambas formas:

dividir(8, y=4)
2.0

Pero una vez que pasamos un parámetro por nombre, es decir, no posicionalmente, todos los parámetros restantes también tienen que ser por nombre:

dividir(x=2, 3)
  Cell In[16], line 1
    dividir(x=2, 3)
                  ^
SyntaxError: positional argument follows keyword argument

El error nos dice que un argumento posicional sigue a un argumento por palabra clave (keyword).

Para esta función dividir(x, y), es confuso cambiar el orden de los parámetros llamándolos por nombre.

Si hubiésemos definido dividir(numerador, denominador), sería un poco menos confuso hacer dividir(denominador=2, numerador=8).

Ejercicio 2#

Escribir una función que permita evaluar un polinomio cuadrático:

\[ f(x) = ax^2 + bx + c \]

y usarla para evaluar el polinomio \(f(x) = 2 x^2 - 8x + 6\) en \(x = 2\).

Usar la cantidad de parámetros que hagan falta.

# Escriba su solución aquí

Solución#

Hide code cell content
def evaluar_cuadratica(x, a, b, c):
    return a * x**2 + b * x + c


evaluar_cuadratica(2, a=2, b=-8, c=6)
-2

Noten que usar keyword arguments permite leer más fácilmente cuáles son los coeficiente del polinomio y cuál el valor donde lo evaluamos.

Si escribimos evaluar_cuadratica(2, 2, -8, 6), llegamos al mismo resultado. Pero necesitaríamos ir a ver la definición de la función para saber si x es el primero o el último de los argumentos.

Múltiples return#

Una función solo puede devolver (return) una única variable. Pero eso no significa que no pueda haber más de un return dentro de una función.

La función se ejecuta secuencialmente, linea por linea, y termina apenas encuentre el primer return.

Por ejemplo:

def triplicar(x):
    z = 3 * x
    return z
    w = x / 0
    return w


triplicar(3)
9

¿Cómo sabemos que no corrió el resto?

Si se hubiese ejecutado w = x / 0, se habría producido un error. Pongámoslo antes del return:

def triplicar(x):
    z = 3 * x
    w = x / 0
    return z
    return w


triplicar(3)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[20], line 8
      4     return z
      5     return w
----> 8 triplicar(3)

Cell In[20], line 3, in triplicar(x)
      1 def triplicar(x):
      2     z = 3 * x
----> 3     w = x / 0
      4     return z
      5     return w

ZeroDivisionError: division by zero

Veremos más adelante que puede ser útil tener más de un return dentro de una función.

Múltiples resultados#

Si queremos devolver múltiples resultados, una forma es separarlos con comas (,) en el return:

def sumar_y_restar(x, y):
    suma = x + y
    resta = x - y
    return suma, resta


z = sumar_y_restar(5, 3)

z
(8, 2)

Lo que estamos haciendo es crear una «tupla», que veremos más adelante.

Podemos «desempacar» el resultado asignándolo a variables separadas por comas:

a, b = sumar_y_restar(5, 3)

a
8

donde a y b guardaron suma y resta, respectivamente.

Ojo, si no usamos la misma cantidad de variables, nos va a devolver el siguiente error:

a, b, c = sumar_y_restar(5, 3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[23], line 1
----> 1 a, b, c = sumar_y_restar(5, 3)

ValueError: not enough values to unpack (expected 3, got 2)

Si leen el error, dice que no pudo desempacar los valores. Esperaba 3 valores (a, b, c), pero la función solo devolvió 2.

Ejercicio 3#

Definir una función para calcular las raíces de una cuadrática, y calcule las raíces del polinomio \(f(x) = 2 x^2 - 5x + 2\).

# Escriba su solución aquí

Solución#

Hide code cell content
def raices_cuadratica(a, b, c):
    disc = (b**2 - 4 * a * c) ** (1 / 2)
    raiz_1 = (-b - disc) / (2 * a)
    raiz_2 = (-b + disc) / (2 * a)
    return raiz_1, raiz_2


raices_cuadratica(2, -5, 2)
(0.5, 2.0)

Recordemos los beneficios de encapsular código en una función:

  1. Permite ejecutar fácilmente una receta para diferentes parámetros (a, b, c).

  2. Si queremos mejorar la receta, o encontramos un error en esta, solo tenemos que editar código en un lugar.

  3. Elimina las variables intermedias, que solo son útiles dentro de la receta (disc).

  4. Es más fácil de leer el código. Comparen

    disc = (b**2 - 4 * a * c) ** (1 / 2)
    x_1 = (-b - disc) / (2 * a)
    x_2 = (-b + disc) / (2 * a)
    

    contra

    x1, x2 = raices_cuadratica(a, b, c)
    

    En el primer caso, tenemos que entender que está haciendo el código para saber que son x_1 y x_2.

    En el segundo caso, está explicito en el nombre de la función.

Probando la función#

Es importante probar la función después de definirla, para estar seguros que la definimos bien.

Hay, al menos, dos formas:

Probar casos conocidos#

Una forma es probar casos donde conozcamos la respuesta.

Por ejemplo:

  • Para los polinomios de la forma \(x^2 + c\), las raíces son \(\pm \sqrt{-c}\):

raices_cuadratica(1, 0, -4)
(-2.0, 2.0)
  • Para polinomios de la forma \(x^2 + bx = x(x + b)\), las raíces son \(0\) y \(-b\):

raices_cuadratica(1, 2, 0)
(-2.0, 0.0)

Evaluar propiedades#

Otra forma es evaluar propiedades de la solución.

Por definición, si evaluamos el polinomio en las raíces, tiene que dar 0.

Entonces, podemos aprovechar la otra función que definimos, y probar para un polinomio más general:

a, b, c = 1, 5, -10

raiz_1, raiz_2 = raices_cuadratica(a, b, c)
y1 = evaluar_cuadratica(raiz_1, a, b, c)
y2 = evaluar_cuadratica(raiz_2, a, b, c)

y1, y2
(0.0, -1.7763568394002505e-15)

Nota: y2 da un número distinto, pero muy cercano a 0. Esto se debe a errores de redondeo, ya que la computadora tiene una precisión finita.