Herramientas de usuario

Herramientas del sitio


informatica:programacion:python:cursos:introduccion_programacion_python:funciones

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()
 
informatica/programacion/python/cursos/introduccion_programacion_python/funciones.txt · Última modificación: por tempwin