Funciones#

Las funciones son objetos que permiten realizar tareas específicas de una manera más ordenada, sin hacer copias innecesarias del código. Esto facilita enormemente el mantenimiento del código.

Funciones como objetos#

En Python las funciones también son objetos, que pueden, por ejemplo,

  • copiarse

  • ser miembro de una colección (lista, tupla, diccionario)

  • pasarse como un argumento de otra función

type(max)
builtin_function_or_method

ser miembro de una colección#

from math import sin, cos, tan, pi

𝜋 = pi

trigonometricas = (sin, cos, tan)
[f(𝜋/3) for f in trigonometricas]
[0.8660254037844386, 0.5000000000000001, 1.7320508075688767]

copiarse#

coseno = cos
coseno(𝜋/3)
0.5000000000000001

pasarse como argumento de otra función#

from scipy import integrate

integrate.quad(cos, 0, 𝜋/2)[0]
0.9999999999999999

Definiendo funciones#

Una función se crea usando la palabra clave def (definition) seguida de un nombre de su escogencia y paréntesis ( ).

El programador puede escoger cualquier nombre para una función, excepto las palabras claves de Python y el nombre de funciones integradas existentes.

Esta línea debe terminar con un :, luego deben seguir las instrucciones que la función ejecuta cuando es llamada, en líneas indentadas.

Por tanto, la sintaxis se ve así:

def function-name( ):
    statements-to-be-executed
    statements-to-be-executed

Para averiguar las palabras claves de Python:

import keyword
print(keyword.kwlist)
['False', 'None', 'True', '__peg_parser__', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

Una función sin argumentos#

def hello( ):
    print('Hello')
    print('Welcome to the UCR!')

Para correr esta función

hello()
Hello
Welcome to the UCR!
saludar = hello
saludar()
Hello
Welcome to the UCR!

Una función con argumentos#

Supongamos que deseamos escribir una función para convertir un dato de temperatura de Celsius a Fahrenheit. Para ello, sabemos que: $\(F = \tfrac{9}{5}C + 32 = 1.8C + 32\)$

def c2f(c):
    f = 1.8 * c + 32
    print(f'{c:.1f}° Celsius es igual a {f:.1f}° Fahrenheit')

c2f(15)
15.0° Celsius es igual a 59.0° Fahrenheit
y = c2f(100)
100.0° Celsius es igual a 212.0° Fahrenheit
type(y)
NoneType

Una función que retorna un valor#

def c2f(c):
    f = 1.8 * c + 32
    return f
x = c2f(15)
print(x)
59.0

Una función con parámetros predeterminados#

def c2f(c, *, mayuscula=False, mostrar=False):
    f = 1.8 * c + 32
    if mostrar:
        if mayuscula:
            print(f'{c:.1f}° CELSIUS = {f:.1f} FAHRENHEIT')
        else:
            print(f'{c:.1f}° Celsius = {f:.1f} Fahrenheit')
    return f

c2f(15)
59.0
c2f(15, mostrar=True, mayuscula=True)
15.0° CELSIUS = 59.0 FAHRENHEIT
59.0
c2f(15, mostrar=True)
15.0° Celsius = 59.0 Fahrenheit
59.0

Una función con un número indefinido de parámetros posicionales#

Supongamos que queremos escribir una función que dependa de un número indefinido de parámetros. Por ejemplo, la función print imprime un número arbitrario de elementos:

print(3, 4, 6)
3 4 6
print(5,3,7,1,"perro")
5 3 7 1 perro

Claramente no sería práctico hacer una función distinta para cada posible número de argumentos. Para ello utilizamos *args (arguments) en la definición de la función. Por ejemplo, para escribir una operación de suma:

def imprimir_suma(*args):
    numeros = (str(x) for x in args)
    sumando = ' + '.join(numeros) 
    resultado = sum(args)
    print(sumando, " = ", resultado)
    print(f'\nEjecutando imprimir_suma con args = ', args, sep='\n')
    print(f'\nEjecutando imprimir suma con *args = ', *args, sep='\n')
imprimir_suma(3,2,5)
3 + 2 + 5  =  10

Ejecutando imprimir_suma con args = 
(3, 2, 5)

Ejecutando imprimir suma con *args = 
3
2
5
imprimir_suma(1,1,1,1,1)
1 + 1 + 1 + 1 + 1  =  5

Ejecutando imprimir_suma con args = 
(1, 1, 1, 1, 1)

Ejecutando imprimir suma con *args = 
1
1
1
1
1

Vemos que a lo interno de la función, args es una tupla, mientras que *args son los elementos individuales de esa tupla.

Una función con un número indefinido de parámetros de palabra clave#

Supongamos que queremos escribir una función que dependa de un número indefinido de parámetros de palabras clave (usualmente para especificar opciones). Para ello, agrupamos esos parámetros en la variable **kwargs (keyword arguments)

def imprimir_opciones(**kwargs):
    for parametro, valor in kwargs.items():
        print(parametro, " = ", valor)
    
    print(f'\nEjecutando imprimir_suma con kargs = ', kwargs, sep='\n')
imprimir_opciones(color='rojo', modelo='2018', marca='Honda', estilo='SUV')
color  =  rojo
modelo  =  2018
marca  =  Honda
estilo  =  SUV

Ejecutando imprimir_suma con kargs = 
{'color': 'rojo', 'modelo': '2018', 'marca': 'Honda', 'estilo': 'SUV'}

Vemos que a lo interno de la función, **kwargs es un diccionario. Esta característica de Python es muy útil para fijar un grupo de opciones en un diccionario y pasarlas repetidamente a funciones. Por ejemplo, la función print separa elementos con un espacio y termina la operación con un salto de linea:

?print
Docstring:
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file:  a file-like object (stream); defaults to the current sys.stdout.
sep:   string inserted between values, default a space.
end:   string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
Type:      builtin_function_or_method

Podemos cambiar estos valores repetidamente así:

print(1,2,3)
print(4,5,6)
1 2 3
4 5 6
OPCIONES = {'sep': ' /*\ ', 'end': '\n\n'}

print(1,2,3, **OPCIONES)
print(4,5,6, **OPCIONES)
1 /*\ 2 /*\ 3

4 /*\ 5 /*\ 6
print(3,2,5, OPCIONES)
3 2 5 {'sep': ' /*\\ ', 'end': '\n\n'}

En la práctica, podemos combinar ambas características:

def imprimir_operacion(*numeros, **print_options):
    print('Sin tomar en cuenta las opciones, la tupla de números es ')
    print(*numeros)
    print('pero tomando en cuenta las opciones, la tupla de números es ')
    print(*numeros, **print_options)
imprimir_operacion(2,5,3,6,8,1)
Sin tomar en cuenta las opciones, la tupla de números es 
2 5 3 6 8 1
pero tomando en cuenta las opciones, la tupla de números es 
2 5 3 6 8 1
OPCIONES = dict(sep = ' * ', end = ' = ¿quién sabe?!!\n')
imprimir_operacion(2,5,3,6,8,1, **OPCIONES)
Sin tomar en cuenta las opciones, la tupla de números es 
2 5 3 6 8 1
pero tomando en cuenta las opciones, la tupla de números es 
2 * 5 * 3 * 6 * 8 * 1 = ¿quién sabe?!!

Vemos que lo importante de *args y **kwargs es el número de asteriscos, no el nombre mismo de la variable.

Documentando una función#

¡Es muy importante documentar lo que hace su código!

def c2f(c, show=False):
    """
    Convierte dato de temperatura de grados Celsius a grados Fahrenheit
    
    Parámetros:
      c: un número escalar, grados en Celsius
      show: un boolean, imprime el resultado si `True`
    Resultado:
      un número escalar, grados en Fahrenheit
    Ejemplo:
      c2f(0)  # da 32.0 por resultado
    """
    f = 1.8 * c + 32
    if show:
        print(f'{c:.1f}° Celsius = {f:.1f} Fahrenheit')
    return f

z = c2f(15)
help(c2f)
Help on function c2f in module __main__:

c2f(c, show=False)
    Convierte dato de temperatura de grados Celsius a grados Fahrenheit
    
    Parámetros:
      c: un número escalar, grados en Celsius
      show: un boolean, imprime el resultado si `True`
    Resultado:
      un número escalar, grados en Fahrenheit
    Ejemplo:
      c2f(0)  # da 32.0 por resultado
?c2f
Signature: c2f(c, show=False)
Docstring:
Convierte dato de temperatura de grados Celsius a grados Fahrenheit

Parámetros:
  c: un número escalar, grados en Celsius
  show: un boolean, imprime el resultado si `True`
Resultado:
  un número escalar, grados en Fahrenheit
Ejemplo:
  c2f(0)  # da 32.0 por resultado
File:      c:\users\randa\appdata\local\temp\ipykernel_8000\3486068780.py
Type:      function

Entendiendo el ámbito de una variable#

Cuando una función encuentra una variable dentro de su definición que no hay sido definida dentro de la función, buscará su definición en el ambiente donde la función fue definida.

pi = 3.1415

def area(r):
    A = pi * r**2
    return A
pi
3.1415
print(area(10))
314.15000000000003

La siguiente línea da un error, porque A es una variable definida a lo interno de la función area, por lo que no es visible desde “fuera” de la función:

print(A)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_8000/212384544.py in <module>
----> 1 print(A)

NameError: name 'A' is not defined

Ahora cambiamos el valor de pi, y ejectamos area de nuevo:

pi = 9
area(10)
900

Vemos que el resultado cambia!!! Esto se debe a que la función area busca el valor de pi cada vez que es ejecutada, no al momento de definir la función.