Tabla de Contenidos
Funciones
Bloque perteneciente al curso Introducción a la programación con Python.
Una función es un elemento de un programa que agrupa sentencias de programación que luego pueden ser utilizadas desde cualquier otra parte del programa. Las funciones pueden, opcionalmente, recibir datos de entrada (llamados parámetros o argumentos) y retornar datos de salida o resutlados.
Definición de nuevas funciones
La definición de una función debe hacerse una única vez antes de ser utilizada.
Una vez definida, la función perdura hasta el final de la ejecución del código del programa.
def siguiente_numero(n): return n + 1
Declarar o definir una función no la ejecuta.
type(siguiente_numero) # function
Llamada a función
Llamar a una función es hacer uso de la misma (ejecutar su código), pasándole entre paréntesis los datos de entrada (si los necesita) y recogiendo el resultado. Dicho resultado puede almacenarse en una variable.
siguiente_nunmero(8) # 9 x = 2 y = siguiente_numero(x) x, y # (2, 3)
Definición alternativa de función
Explicitando tipos de datos en parámetros y valor devuelto:
def saludar(nombre: str) -> str: ''' Esta función genera un saludo personalizado a la persona cuyo nombre se da como parámetro ''' return "Hola " + nombre
nombre = input("¿Cómo te llamas?") resultado = saludar(nombre) print(resultado)
from math import sqrt def segundo_grado(a: float, b: float, c: float) -> tuple: ''' Busca las raíces reales de una ecuación de segundo grado con la forma a*x**2 + b*x + c = 0 ''' d = b**2 - 4 * a * c if d >= 0: x1 = (-b + sqrt(d)) / (2 * a) x2 = (-b - sqrt(d)) / (2 * a) return x1, x2 else: return None
help(segundo_grado) Help on function
Uso:
a = float(input("Coeficiente a: ")) b = float(input("Coeficiente b: ")) c = float(input("Coeficiente c: ")) resultado = segundo_grado(a, b, c) if resultado: r, s = resultado print(r, s) else: print("No tiene soluciones reales")
Versión con excepciones (en lugar de devolver 'None'):
from math import sqrt def segundo_grado(a: float, b: float, c: float) -> tuple: ''' Busca las raíces reales de una ecuación de segundo grado con la forma a*x**2 + b*x + c = 0 En caso de no tener soluciones reales, causa excepción ''' d = b**2 - 4 * a * c if d >= 0: x1 = (-b + sqrt(d)) / (2 * a) x2 = (-b - sqrt(d)) / (2 * a) return x1, x2 else: raise ValueError(f"La ecuación {a}*x**2 + {b}*x + {c} = 0 no tiene soluciones reales")
Uso:
Coeficiente a: 1 Coeficiente b: 0 Coeficiente c: 1 AQUI ERROR
Podemos capturar el error de la siguiente manera con try y except:
a = float(input("Coeficiente a: ")) b = float(input("Coeficiente b: ")) c = float(input("Coeficiente c: ")) try: resultado = segundo_grado(a, b, c) r, s = resultado print(r, s) except ValueError as error: print(error)
Cuando lo usamos:
Coeficiente a: 1 Coeficiente b: 0 Coeficiente c: 1 La ecuación 1.0*x**2 + 0.0*x + 1.0 = 0 no tiene soluciones reales.
Paso de parámetros
Parámetros posicionales
Van en el mismo orden que están definidos:
segundo_grado(1, 2, -3)
Parámetros con nombre
Se pueden indicar en cualquier orden especificando el nombre de cada uno:
segundo_grado(b = 2, a = 1, c = -3)
Parámetros con valores por defecto
Se pueden indicar valores por defecto para los parámetros, que serán usados si el valor para el parámetro en cuestión se omite.
def calcular_impuesto(base, iva=21) return base * iva / 100.0
Uso:
# Omitimos parámetro iva, así que toma un valor por defecto 21 calcular_impuesto(1000) # 210.0 # Indicamos explícitamente el valor del parámetro calcular_impuesto(1000, 4) # 40.0
Funciones con un número variable de parámetros
Las funciones pueden recibir parámetros variables:
- Si son posicionales (sin nombre), se recogen en una lista
- Si son con nombre, se recogen en un diccionario.
Con el asterisco (*) indicamos que se esperan una cantidad indeterminada de parámetros:
def calcular_promedio(*datos: float) -> float: ''' Calcula el promedio de varios datos de tipo numérico. El número de datos puede ser variable ''' # Primero comprobamos que hay datos. Si no hay, que devuelva 'None' if len(datos) == 0: return None # A continuación, declaramos una variable local que comienza con el valor 0 y en la que # vamos a ir sumando todo suma = 0.0 for d in datos: suma += d # Después de recorrer los datos uno a uno, lo dividimos entre el número de datos y ahí # tenemos el promedio return suma / len(datos)
Uso:
calcular_promedio(1, 2, 6, 5, 4, 0, 8, -3) # 2.875 calcular_promedio(5, 10) # 7.5
Con dos asteriscos (**):
def calcular_nota_final(**notas): nota = 0.0 if 'examen' in notas: nota = notas['examen'] if 'recuperacion' in notas and notas['recuperacion'] > nota: nota = notas['recuperacion'] return nota
Uso:
calcular_nota_final(examen=7.5) # 7.5 calcular_nota_final(examen=4, recuperacion=6.5) # 6.5
def vale_todo(*args, **kwargs): for d in args: print("Argumento posicional: ", d) for k, v in kwargs.items(): print(f"Argumento con nombre: {k}={v}")
Uso:
vale_todo("alpha", "bravo", c = "charlie", d = "delta")
# Argumento posicional: alpha
# Argumento posicional: bravo
# Argumento con nombre: c=charlie
# Argumento con nombre: d=delta
Otro ejemplo:
l = ["alpha", "bravo", "charlie"] d = {"d": 2, "e": -5} # Para desagregar la lista y el diccionario anteriores y pasárselos como # argumentos variables a la función: vale_todo(*l, **d)
Funciones anónimas en línea (lambda)
En Python, una función Lambda se refiere a una pequeña función anónima. Las llamamos “funciones anónimas” porque técnicamente carecen de nombre.
cubo = lambda x: x*x*x cubo(5) # 125 type(cubo) # function
palabras = ["charlie", "delta", "foxtrot", "echo"] # Ordenación según el orden por defecto para las strings: orden lexicográfico sorted(palabras) # ['charlie', 'delta', 'echo', 'foxtrot'] # Se le puede pasar una función a 'sorted' sorted(palabras, key=len) # ['echo', 'delta', 'charlie', 'foxtrot'] def logitud_inv(s: str) -> float: return -len(s) sorted(palabras, key=longitud_inv) # ['charlie', 'foxtrot', 'delta', 'echo'] # Utilizando una función anónima: sorted(palabras, key=lambda s: -len(s)) # ['charlie', 'foxtrot', 'delta', 'echo']
# Crear una lista con las palabras que contenga 'e': list(filter(lambda s: 'e' in s, palabras)) # ['charlie', 'delta', 'echo']
# Crear una lista de elementos "mapeados" con la función que convierte a mayúsculas list(map(lambda p: p.upper(), palabras)) # ["CHARLIE", "DELTA", "FOXTROT", "ECHO"]
Funciones que no retornan valor
def contar_hasta(n : int): for i in range(n): print(i+1)
Resultado de ejecución:
contar_hasta(10) 1 2 3 4 5 6 7 8 9 10
Existe una instrucción en Python que no hace nada: pass. Puede ser útil si no queremos definir una función o algo por el momento:
def do_nothing(): pass
Funciones generadoras
Funciones que no devuelven un resultado, sino un objeto que puede utilizarse como iterable y que en cada iteración provoca que la función genere un nuevo valor.
def impares(tope: int = None): i = 1 while i < tope: yield i # parecido a 'return', pero no finaliza la función i += 2 impares(20) # generator object g = impares(20) next(g) # 1 next(g) # 3 next(g) # 5 next(g) # 7 for a in impares(20): print(a) # 1 # 3 # 5 # 7 # 9 # 11 # 13 # 15 # 17 # 19
from random import randint def combinacion_loteria(): combinacion_ganadora = set() while len(combinacion_ganadora) < 6: n = randint(1, 49) if n in combinacion_ganadora: continue yield n combinacion_ganadora.add(n) # La usamos con un 'for' for b in combinacion_loteria(): print(b) # 45 # 36 # 43 # 1 # 31 # 11
Secuencia de Fibonacci. Cada término de la sucesión se define como la suma de los dos anteriores:
0 1 # 1 + 0 1 # 1 + 0 2 # 1 + 1 3 # 2 + 1 5 # 3 + 2 8 # 5 + 3 13 # 8 + 5 21 # 13 + 8
def fibonacci(tope : int): a = 0 b = 1 while not tope or a < tope: yield a c = a + b b, a = c, b # a = b; b = c
for t in fibonacci(200): print(t)
Vamos a buscar la proporción áurea (ojo, que esto no estoy seguro de que esté bien):
phi = 1 phi0 = 0 g = fibonacci() next(g) a = next(g) b = next(g) while abs(phi - phi0) > 1e-9: b = a a = next(g) phi0 = phi phi = b / a print(f"phi = {phi:.12f}")
Funciones recursivas
Funciones que se llaman a sí mismas. La recursividad es útil como método de simplificación de un problema complejo dividiendo el problema en subproblemas del mismo tipo.
Un buen ejemplo es el factorial de un número entero:
def factorial(n : int) -> int: if n > 0: return n * factorial(n - 1) else: return 1
Uso:
factorial(5) # 120 factorial(0) # 1
Ejemplos prácticos
Torres de Hanoi
Ejemplo clásico de recursividad.
El juego consiste en pasar todos los discos desde el poste ocupado (es decir, el que posee la torre) a uno de los otros postes vacíos. Para realizar este objetivo, es necesario seguir tres simples reglas:
- Solo se puede mover un disco cada vez y para mover otro los demás tienen que estar en postes.
- Un disco de mayor tamaño no puede estar sobre uno más pequeño que él mismo.
- Solo se puede desplazar el disco que se encuentre arriba en cada poste.
La fórmula para encontrar el número de movimientos necesarios para transferir n discos desde un poste a otro es 2^n - 1
def hanoi(n, origen='A', destino='C', auxiliar='B'): if n > 1: hanoi(n - 1, origen, auxiliar, destino) print(f"Mover disco de {origen} a {destino}") if n > 1: hanoi(n - 1, auxiliar, destino, origen)
Problema de las ocho reinas
El problema de las ocho reinas es un pasatiempo que consiste en poner ocho reinas en el tablero de ajedrez sin que se amenacen.
El problema de las 8 reinas tiene 92 soluciones.
https://es.wikipedia.org/wiki/Problema_de_las_ocho_reinas
def mostrar_tablero(reinas): # Dibujamos un tablero de ajedrez for columna in reinas: print("+----+----+----+----+----+----+----+----+") print("| "*columna + "| @ " + "| "*(7 - columna) + "|") print("| | | | | | | | |") print("+----+----+----+----+----+----+----+----+")
mostrar_tablero([1, 3, 5, 7, 2, 0, 6, 4])
Vamos ahora con la resolución del problema:
contador_soluciones = 0 def reinas(posiciones=[]): global contador_soluciones # Indicamos que queremos acceder a una variable externa a la función # Determinamos el número de fila donde colocar la reina # simplemente contando los elementos ya posicionados fila = len(posiciones) # En esa fila, probamos a colocar una reina en cada una # de las columnas for columna in range(8): # Comprobar si la columna está libre; si no lo está, # saltamos a la siguiente columna if columna in posiciones: continue # Comprobación de diagonales: para todas las filas anteriores, # ver si no hay reina en diagonal choque_diagonal = False for f in range(fila): if posiciones[f] + fila - f == columna or posiciones[f] - fila + f == columna: choque_diagonal = True break # Si hemos encontrado una reina en alguna diagonal, # saltamos ya a la siguiente columna if choque_diagonal: continue # Si hemos llegado hasta aquí es porque no hay reinas en # la vertical ni en las diagonales, así que colocamos una # nueva reina en el tablero posiciones.append(columna) # Y procedemos a rellenar el resto del tablero recursivamente, # salvo que estemos ya en la última fila. En este último caso, # visualizamos el tablero con la solución if len(posiciones) < 8: reinas(posiciones) else: contador_soluciones += 1 print(f"Solución {contador_soluciones}:") mostrar_tablero(posiciones)) # Después de todo esto, quitamos la reina que acabamos de colocar # para seguir explorando soluciones posiciones.pop()
