Saltar al contenido principal

Funciones

En el contexto de la programación, una función es una secuencia de sentencias que realizan una operación y que recibe un nombre. Cuando se define una función, se le especifica un nombre y la secuencia de sentencias. Más adelante, se puede llamar o invocar la función por ese nombre. Ya vimos ejemplos de llamada a una función en apartados anteriores:

>>> type(32)
<class 'int'>

El nombre de la función es type. La expresión entre paréntesis recibe el nombre de argumento de la función. El argumento es un valor o variable que se le pasa a la función como parámetro de entrada. El resultado de la función type es el del tipo del argumento.

Es habitual decir que una función recibe un argumento y devuelve un resultado. El resultado se llama valor de retorno.

Definición de funciones

Hasta ahora, solo usamos funciones que vienen incorporadas en Python, pero es posible añadir también funciones nuevas. Una definición de función especifica el nombre de una función nueva y la secuencia de sentencias que se ejecutan cuando esa función es llamada. Una vez definida una función, se puede reutilizar una y otra vez a lo largo de todo el programa.

Ejemplo:

def mostrar_estribillo():
print('Soy un leñador, qué alegría.')
print('Duermo toda la noche y trabajo todo el día.')

def es una palabra reservada que indica que se trata de la definición de una función. El nombre de la función es mostrar_estribillo. Las reglas para los nombres de las funciones son las mismas que para las variables. Se pueden usar letras, números y algunos signos de puntuación, pero el primer carácter no puede ser un número. No se puede usar una palabra clave como nombre de una función, y se debería evitar también una variable y una función con el mismo nombre.

Los paréntesis vacíos después del nombre indican que esta función no toma ningún argumento. Más tarde construiremos funciones que reciban argumentos de entrada.

La primera línea de definición de una función es llamada cabecera. El resto se llama cuerpo. La cabecera debe terminar con dos puntos (:), y el cuerpo debe ir tabulado. El cuerpo puede contener cualquier número de sentencias.

Al definir una función se crea una variable con el mismo nombre.

print(mostrar_estribillo) # <function mostrar_estribillo at 0xb7e99e9c>
print(type(mostrar_estribillo)) # <type 'function'>

El valor de mostrar_estribillo es function object, que tiene como tipo function.

Llamadas a funciones

La sintaxis para llamar a nuestra nueva función es la misma que usamos para las funciones internas:

mostrar_estribillo()
'''
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
'''

Una vez que se definió una función, puede usarse dentro de otra. Por ejemplo, para repetir el estribillo anterior. Podríamos escribir una función llamada repite_estribillo.

def repite_estribillo():
mostrar_estribillo()
mostrar_estribillo()

Y después llamar a esa función:

repite_estribillo()
'''
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
'''

Ejemplo de definición y llamadas en un script

Reuniendo los fragmentos de código de las secciones anteriores, el programa completo sería algo como esto:

def mostrar_estribillo():
print('Soy un leñador, qué alegría.')
print('Duermo toda la noche y trabajo todo el día.')

def repite_estribillo():
mostrar_estribillo()
mostrar_estribillo()

repite_estribillo()

Este programa contiene dos definiciones de funciones: mostrar_estribillo y repite_estribillo. Las definiciones se ejecutan exactamente igual que cualquier otra sentencia, pero su resultado consiste en crear objetos del tipo función. Las sentencias dentro de cada función se ejecutan solamente cuando se llama a esa función, y la definición de una función no genera ninguna salida.

Como ya te imaginarás, es necesario crear una función antes de que se pueda ejecutar. En otras palabras, la definición de la función debe ejecutarse antes de que se llame por primera vez.

Parámetros y argumentos

Algunas funciones internas que vimos necesitan argumentos.

Por ejemplo:

  • Cuando se llama a math.sin(90), se le pasa el número como argumento.
  • Algunas funciones necesitan más de un argumento. Por ejemplo, math.pow(6,2) toma dos: la base y el exponente.

Dentro de las funciones, los argumentos son asignados a variables llamadas parámetros. A continuación mostramos un ejemplo de una función definida por el usuario que recibe un argumento:

def muestra_dos_veces(nombre):
print(nombre)
print(nombre)

Esta función asigna el argumento a un parámetro llamado nombre. Cuando la función es llamada, imprime el valor del parámetro (sea este el que sea) dos veces.

Esta función funciona con cualquier valor que pueda ser mostrado en pantalla.

muestra_dos_veces('Spam')
'''
Spam
Spam
'''
muestra_dos_veces(17)
'''
17
17
'''
muestra_dos_veces(math.pi)
'''
3.14159265359
3.14159265359
'''

Las mismas reglas de composición que se aplican a funciones internas, también se aplican a las funciones definidas por el usuario, de modo que podemos usar cualquier tipo de expresión como argumento para muestra_dos_veces.

muestra_dos_veces('Spam '*4)
'''
Spam Spam Spam Spam
Spam Spam Spam Spam
'''
muestra_dos_veces(math.cos(math.pi))
'''
-1.0
-1.0
'''

El argumento es evaluado antes de que la función sea llamada, así que en los ejemplos, la expresión Spam*4 y math.cos(math.pi) son evaluadas una vez.

También se puede usar una variable como argumento:

miguel = 'Miguel'
muestra_dos_veces(miguel)
'''
Miguel
Miguel
'''

El nombre de la variable que pasamos como argumento, miguel, no tiene nada que ver con el nombre del parámetro, nombre. No importa cómo se llame el valor en origen (en la llamada); dentro de muestra_dos_veces siempre se llamará nombre.

Funciones con más de un parámetro

Las funciones pueden tener desde ningún parámetro hasta el número que deseemos. Por lo general, las funciones no deberían tener un número muy elevado de parámetros, aunque eso depende del caso particular de cada problema.

Para definir una función con más de un parámetro, estos parámetros se definen dentro de los paréntesis separados por comas:

def funcion1(parametro1, parametro2, parametro3, ...)

Cuando se realiza la llamada a estas funciones, el orden de los parámetros es importante, dado que el primer valor dentro del paréntesis de la llamada a función corresponderá con el primer parámetro de la función. El segundo valor, con el segundo parámetro, etc.

funcion1(valor_para_parametro1, valor_para_parametro2, valor_para_parametro3, ...)

Funciones con parámetros clave

Generalmente en Python se utilizan los parámetros por posición o parámetros obligatorios, es decir, que cuando se llama a una función la posición indica qué parámetro es cada argumento. Pero también tenemos la posibilidad de no seguir ese orden si utilizamos parámetros con palabras clave o parámetros opcionales.

Consideremos la siguiente definición de función:

def funcion1(a, b, c)

Si realizamos la siguiente llamada a función:

funcion1(1, 2, 3)

Dentro de la función funcion1:

  • La variable a tomará el valor 1.
  • La variable b tomará el valor 2.
  • La variable c tomará el valor 3.

Pero podemos modificar este orden en la llamada de la función si indicamos en la llamada para todos los parámetros cuál es su valor:

funcion1(b=1, c=2, a=3)

En este caso, dentro de la función funcion1:

  • La variable a tomará el valor 3.
  • La variable b tomará el valor 1.
  • La variable c tomará el valor 2.

En la llamada a la función ya no es necesario seguir el orden de definición de los parámetros.

En la propia definición de la función podemos utilizar los parámetros por palabra clave y asignarles un valor por defecto a un parámetro:

def funcion1(a=1, b=2, c=3)

Esto hace que a los parámetros que tienen un valor por defecto, no sea necesario pasarles un valor en la llamada de la función. Realizamos esta llamada a función:

funcion1()

Dentro de la función funcion1, la variable a tomará el valor 1, la variable b el valor 2, y la variable c el valor 3. Pero podemos sobrescribir estos valores. Podemos sobrescribir el número de valores que queramos realizando una llamada por parámetros con palabras clave:

funcion1(a=5, b=7)

En este caso, dentro de la función funcion1, la variable a tomará el valor 5, la variable b el valor 7, y la variable c el valor 3.

Mezclar parámetros por posición y parámetros clave

Puedes mezclar ambos tipos de parámetros (por posición y clave), asegurándote de que los parámetros posicionales estén antes de los clave.

def describir_personal(nombre, edad=25):
print(f"{nombre} tiene {edad} años.")

# Llamar a la función proporcionando solo el parámetro obligatorio
describir_personal("Ana")

# Llamar a la función proporcionando ambos parámetros
describir_personal("Juan", 30)

Paso por valor y paso por referencia

En Python, cuando hablamos de paso por valor (pass by value) o paso por referencia (pass by reference) en funciones, nos referimos a cómo se transmiten los argumentos a la función.

El paso por valor significa que se le pasa una copia del valor a la función. Esto quiere decir que los cambios hechos al parámetro dentro de la función no afectan a la variable original fuera de la función.

En Python, los tipos inmutables (como números, cadenas de texto, tuplas, etc.) se comportan como si fuesen pasados por valor. Si haces una modificación dentro de la función, estás creando un nuevo valor sin cambiar el original.

def incrementar(n):
n += 1
print("Dentro de la función:", n)

numero = 5
incrementar(numero)
print("Fuera de la función:", numero)

Salida:

Dentro de la función: 6
Fuera de la función: 5

Como int es inmutable, la variable numero no se ve afectada por la modificación dentro de la función.

En cambio, el paso por referencia significa que se le pasa a la función una referencia a la variable original. Esto quiere decir que los cambios realizados al argumento dentro de la función afectan a la variable fuera de la función.

En Python, los tipos mutables (como listas, diccionarios, conjuntos, etc.) son pasados de forma que la función puede modificar el contenido original.

Ejemplo:

def añadir_elemento(lista):
lista.append(4)
print("Dentro de la función:", lista)

numeros = [1, 2, 3]
añadir_elemento(numeros)
print("Fuera de la función:", numeros)

Salida:

Dentro de la función: [1, 2, 3, 4]
Fuera de la función: [1, 2, 3, 4]

Como las listas son mutables, cualquier modificación que haces dentro de la función también se refleja fuera.

Ámbito: Variables locales y globales

El ámbito de una variable es el contexto en el que existe esa variable. Una variable existe en dicho ámbito desde el momento en que se crea y deja de existir cuando desaparece su ámbito. Además, solo son accesibles desde su propio ámbito.

Los principales ámbitos en Python son:

  • Local: corresponde al ámbito de una función. Existe desde que se invoca la función hasta que finaliza la ejecución. No se puede acceder a estas variables desde fuera de la función.
  • Global: corresponde al ámbito que existe desde el comienzo de la ejecución de un programa. Todas las variables definidas fuera de cualquier función corresponden al ámbito global. Se puede acceder a ellas desde cualquier punto del programa, incluidas las funciones.

Variables locales en funciones

Aquellas variables que se utilizan por primera vez dentro de una función solo existen durante la ejecución de la función. Por lo tanto, una vez finalizada la ejecución de la función, no podremos acceder al valor de esa variable. Estas variables se conocen como variables locales.

def funcion1(parametro1, parametro2):
a = 2
resultado = (parametro1 + parametro2) / a

Por ejemplo, en el caso anterior, la variable a solo puede ser accesible desde dentro de la función debido a que es una variable local.

def funcion1(parametro1, parametro2):
a = 2
resultado = (parametro1 + parametro2) / a

valor = funcion1(3, 5)
print(a) # Obtenemos un error porque la variable a no está definida

Los parámetros de una función también se consideran variables locales.

def sumador(a, b):
suma = a + b
return suma

c = 3
d = 5
x = sumador(c, d)
print(x) # 8
print(a) # Error porque la variable no está definida.

Preferencia de variables

Si existe una variable local en una función con el mismo nombre que una variable global, sus nombres entran en conflicto.

Python busca primero en el ámbito local para ver si encuentra la variable, y si no, busca en el ámbito global. Es decir, las variables locales tienen preferencia sobre las globales.

a = 6 # Variable global a

def funcion_de_prueba():
a = 2 # Variable local a
print(a) # Se imprimirá el valor de a de la variable local

funcion_de_prueba() # Se imprimirá 2, porque a existe como variable local dentro de la función

Es preferible evitar el uso excesivo de variables globales. Es mejor pasar parámetros a funciones y devolver resultados. Ejemplo de uso:

pi = 3.1416 # Variable global pi

def area_circulo(radio):
return pi * radio**2 # En este caso, como no existe la variable local pi, se utiliza la variable global pi

def perimetro_circulo(radio):
return pi * 2 * radio # En este caso, como no existe la variable local pi, se utiliza la variable global pi

print(area_circulo(5))
print(perimetro_circulo(5))

Devolución de valores

En una función se pueden devolver valores utilizando la palabra clave return. Cuando una función llega a una línea con return, se interrumpe su ejecución y devuelve el valor especificado. Se pueden devolver uno o más valores.

Veamos un ejemplo sencillo de una función que devuelve un valor.

def sumar(a, b):
resultado = a + b
return resultado

Aquí, la función sumar recibe dos argumentos, a y b, los suma y devuelve el valor resultante con la instrucción return.

Si llamas a esta función así:

suma = sumar(5, 3)
print(suma)

La salida será 8, porque el valor retornado por la función se almacena en la variable suma.

Devolución de múltiples valores

En Python, también es posible devolver varios valores separados por comas. Estos valores devueltos normalmente se entregan como una tupla.

def operar(a, b):
suma = a + b
resta = a - b
return suma, resta

Cuando llamas a la función:

resultado_suma, resultado_resta = operar(5, 3)
print(resultado_suma) # 8
print(resultado_resta) # 2

En este caso, la función operar devuelve dos valores, que después pueden ser capturados en dos variables distintas.

Resumiendo, para devolver valores en una función, usas la palabra clave return, y puedes devolver uno o varios valores según necesites.

Early return

El patrón retorno temprano (early return) consiste en verificar ciertas condiciones al principio de la función, y si estas se cumplen, la función finaliza inmediatamente devolviendo un valor, sin seguir ejecutando el resto del código.

El early return ayuda a simplificar el código al evitar bloques anidados innecesarios y mejorar su legibilidad.

def dividir(a, b):
# Si b es 0, devolvemos un aviso y finalizamos la función
if b == 0:
return "Error: no se puede dividir por cero."

# Si b no es 0, realizamos la división
return a / b

En este ejemplo, la función dividir verifica si el valor de b es cero. Si es así, devuelve un mensaje de error inmediatamente usando el early return, evitando que se ejecute el código que haría la división. Esto ayuda a prevenir errores y a mejorar la claridad del código.

Finalización de funciones

En Python, puedes usar la palabra clave return para finalizar una función en cualquier punto de su código. Si no le especificas un valor para devolver, la función finaliza y devuelve implícitamente None.

Por ejemplo, la siguiente función utiliza return para finalizar la ejecución sin devolver ningún valor:

def verificar_numero(numero):
if numero < 0:
print("El número es negativo.")
return # La función finaliza aquí sin devolver nada.
print("El número es positivo o cero.")

# Llamadas a la función
verificar_numero(-5) # Mensaje: El número es negativo.
verificar_numero(3) # Mensaje: El número es positivo o cero.

Devolución del mismo tipo de datos

En Python, es una buena práctica que una función devuelva siempre el mismo tipo de datos o None. La razón principal es mantener la coherencia y evitar comportamientos inesperados en el programa. La mezcla de tipos de datos devueltos por una función puede dificultar la comprensión, el mantenimiento y la depuración del código. Aquí están las razones más importantes:

Si una función puede devolver diferentes tipos de datos dependiendo de las condiciones, el programador que llame a la función tendrá que comprobar siempre el tipo de dato devuelto antes de usarlo. Esto hace el código más complicado. Aquí un ejemplo de mala práctica:

def ejemplo(num):
if num > 0:
return "positivo"
elif num == 0:
return 0
else:
return None

resultado = ejemplo(5)
if isinstance(resultado, str):
print("Es una cadena de texto.")
elif isinstance(resultado, int):
print("Es un número.")
else:
print("Es None.")

Devolver el mismo tipo de datos o None hace que el código sea:

  • Más previsible: Es más fácil de usar y entender.
  • Más robusto: Evitas errores inesperados cuando usas los valores devueltos.
  • Más mantenible: Facilita el desarrollo futuro y la depuración.
  • Compatible con las buenas prácticas modernas: Como el uso de anotaciones de tipo.

Flujo de ejecución

Para asegurarnos de que una función está definida antes de usarla por primera vez, es necesario conocer el orden en que las sentencias son ejecutadas, lo que llamamos flujo de ejecución.

La ejecución siempre comienza en la primera instrucción del programa. Las sentencias se ejecutan una por una, de arriba hacia abajo.

Las definiciones de funciones no alteran el flujo de ejecución del programa, pero recuerda que las sentencias dentro de una función no se ejecutan hasta que se llama a esa función.

Una llamada a una función es como un desvío en el flujo de ejecución. En lugar de pasar a la siguiente sentencia, el flujo salta al cuerpo de la función, ejecuta todas las sentencias que hay allí, y después vuelve al punto donde lo dejó.

Todo esto parece bastante sencillo, hasta que se recuerda que una función puede llamar a otra. Cuando se está en mitad de una función, el programa puede tener que ejecutar sentencias de otra función.

Python es capaz de llevar el seguimiento de dónde se encuentra en cada momento, de modo que cada vez que completa una ejecución de una función, el programa vuelve al punto donde dejó la función que llamó a esa utilizando la pila de funciones.

Pila de funciones

La pila de funciones (también llamada call stack en inglés) es una estructura de datos que gestiona la ejecución de funciones en un programa. Cada vez que una función es llamada, se añade un nuevo elemento (marco de la función o stack frame) a la pila. Cuando la función finaliza, ese elemento se elimina de la pila, y el control vuelve a la función que llamó a la actual.

Su funcionamiento es el siguiente:

  1. Llamada a una función: Cuando una función es llamada, su información (como los argumentos de la función y el punto de retorno en el código) se añade a la cima de la pila.
  2. Ejecución de la función: La función se ejecuta. Si dentro de esta función se llama a otra función, esta nueva llamada también se añade a la cima de la pila.
  3. Finalización de una función: Cuando una función finaliza, la información de esa llamada se elimina de la cima de la pila, y el programa continúa la ejecución desde el punto donde la función anterior dejó de ejecutar.

El marco de pila contiene:

  • Punto de retorno: Es la posición en el código a la que debe volver el programa una vez que la función finaliza. Básicamente, indica dónde debe continuar el flujo de ejecución después de la llamada a la función. Si no se guardase el punto de retorno, el programa no sabría dónde continuar cuando la función termine.
  • Argumentos de la función: Son los valores que se pasan a la función cuando es llamada. Estos valores son esenciales para que la función pueda trabajar con los datos correctos.
  • Variables locales: Son las variables que se definen dentro de la función, que solo existen mientras la función se está ejecutando. Las variables locales se guardan en la pila para asegurar que cada llamada a la función tenga su propio conjunto de datos, aislado de las otras llamadas.

Veamos cómo sería una pila de funciones cuando tenemos varias llamadas de funciones encadenadas.

def suma(a, b):
resultado = a + b # Variable local 'resultado'
print(f"Suma de {a} + {b} = {resultado}")
return resultado

def multiplicar(c, d):
resultado = c * d # Variable local 'resultado'
print(f"Multiplicación de {c} * {d} = {resultado}")
return resultado

def calcular(a, b):
suma_resultado = suma(a, b) # Llama a 'suma'
multiplicar_resultado = multiplicar(a, b) # Llama a 'multiplicar'
total = suma_resultado + multiplicar_resultado # Variable local 'total'
print(f"Total de suma + multiplicación = {total}")
return total

x = 5 # Variable global x
y = 3 # Variable global y
resultado_final = calcular(x, y) # Llama a 'calcular' y asigna el valor de retorno a la variable global resultado_final
print(f"Resultado final: {resultado_final}")

Recursividad

La recursividad es un concepto de programación donde la función se llama a sí misma.

Estas son sus partes:

  • Caso base: es la condición en la que se detiene la recursividad. Es decir, donde deja de llamarse a sí misma y devuelve un valor. Es necesario tener un caso base en una función para evitar un bucle infinito de llamadas recursivas. Puede existir más de un caso base.
  • Llamada recursiva: es la llamada a sí misma dentro de su definición. Se deben pasar valores a los argumentos diferentes a los recibidos, si no el resultado producido sería el mismo y se produciría un bucle infinito de llamadas recursivas.

En cada llamada recursiva, el problema a resolver debe ser más simple o pequeño. Esto se conoce como reducción del problema. Por lo tanto, los valores de los parámetros cada vez que se hace una llamada deben ser más simples, hasta que se llegue a un caso base y se detenga la recursividad.

Además, cada llamada recursiva debe devolver un valor. En el caso de un caso base sería un resultado final. En el caso de una llamada recursiva, un valor que sirve para la evaluación de llamadas recursivas anteriores.

def factorial(n):
# Caso base: el factorial de 0 es 1.
if n == 0:
return 1
else:
# Llamada recursiva
# Como vemos se devuelve un valor, en este caso n por el resultado
# de una nueva llamada recursiva
return n * factorial(n-1)

numero = 5
resultado = factorial(numero)
print(f"El factorial de {numero} es {resultado}")

Recursividad con más de un caso base

Función recursiva que calcula el número de términos de una secuencia de Fibonacci hasta un índice dado.

def fibonacci(n):
# Caso base 1: si n es 0, la secuencia de Fibonacci tiene 0 términos
if n == 0:
return 0
# Caso base 2: si n es 1, la secuencia de Fibonacci tiene 1 término
elif n == 1:
return 1
else:
# Llamada recursiva para calcular el término n
return fibonacci(n - 1) + fibonacci(n - 2)

Organización y uso de funciones

Organización de las funciones

En general, las funciones se colocan tras la cabecera y descripción de un programa. A continuación se escribe el programa principal. Podemos decir que es donde comienza la ejecución propiamente del programa y en el que se controla el flujo de ejecución.

# Definición de variables globales

...

# Funciones: en este apartado se definen las funciones

...

# Programa principal: la ejecución del programa comienza en este punto

...

Uso de las funciones

Además, es conveniente que las funciones internamente no pidan datos (input) al usuario ni tampoco los muestren por pantalla (print):

  • Los datos de entrada se pasan mediante parámetros.
  • Los valores se devuelven mediante un return.

Esto es para que el proceso de la interfaz con el usuario y el algoritmo sean independientes, lo que favorece la reutilización, que es una de las principales características de la programación estructurada.

Imagínate que deseamos hacer un programa que reciba por teclado el precio de un producto y el IVA que se le aplica en tanto por ciento.

El siguiente ejemplo es una buena práctica en la realización de la función haciéndola reutilizable:

def precio_con_iva(precio_sin_iva, iva=21):
return precio_sin_iva * (1.0 + iva/100)

precio = float(input("Introduce el precio sin IVA: "))
iva = float(input("Introduce el IVA: "))

pvp = precio_con_iva(precio, iva=iva)
print(pvp)

Por el contrario, el siguiente ejemplo es una mala práctica porque la función no es reutilizable:

def precio_con_iva():
precio = float(input("Introduce el precio sin IVA: "))
iva = float(input("Introduce el IVA: "))
pvp = precio * (1.0 + iva/100)
print(pvp)

precio_con_iva()

En una futura versión del programa, este tendrá que leer un archivo .csv donde se reciben productos con su respectivo precio sin IVA y el IVA que se le aplica.

producto,precio,iva
portátil,1500,21
teclado,30,21
tarjeta gráfica,300,21
disco duro,50,18
...

En el caso de la función del primer ejemplo, no sería necesario modificarla para poder utilizarla en este caso. En cambio, la función del segundo ejemplo no nos serviría.

import csv

# Abrir el archivo csv
with open('datos.csv', mode='r', newline='', encoding='utf-8') as file:
# Crear el lector CSV
reader = csv.DictReader(file)

# Leer los datos fila por fila
for fila in reader:
producto = fila['producto']
precio = float(fila['precio'])
iva = float(fila['iva'])

# Utilizamos la función creada para calcular el precio con IVA
pvp = precio_con_iva(precio, iva=iva)

# Aquí puedes trabajar con los datos
print(f"Producto: {producto}, Precio con IVA: {pvp}")

Como excepciones, se pueden realizar funciones que pidan datos al usuario únicamente (sin cálculos) o funciones que muestren datos con un determinado formato.

Ejemplos:

def pedir_entero(mensaje):
while True:
try:
return int(input(mensaje))
except:
print("No introduciste un valor válido.")

def imprimir_numero_dos_decimales(numero):
print(f"{numero:.2f}")

Funciones internas

Python proporciona un número importante de funciones internas, que pueden ser usadas sin necesidad de tener que definirlas previamente. Los creadores de Python escribieron un conjunto de funciones para resolver problemas comunes e incluyeron en Python para que las podamos utilizar.

Las funciones max y min devuelven el valor más grande o más pequeño de una lista:

max("¡Hola, mundo!") # 'u'
min("¡Hola, mundo!") # ' '

La función max nos dice cuál es el carácter más grande de la cadena de texto, mientras que la función min nos muestra el carácter más pequeño.

ASCII

Cada carácter tiene asociado un valor numérico. Estos valores numéricos están establecidos por los códigos ASCII.

Para obtener el código ASCII de un carácter utilizamos la función ord.

caracter = 'a'
codigo = ord(caracter)

Para realizar la operación inversa, es decir, pasar de número ASCII a carácter utilizamos la función chr.

codigo = 97
caracter = chr(codigo)

Otra función interna muy común es len, que nos dice cuántos elementos hay en el argumento. Si el argumento de len es una cadena, nos devuelve el número de caracteres que hay en la cadena.

len("¡Hola, mundo") # 12

Estas funciones no se limitan a buscar en cadenas. Pueden operar con cualquier conjunto de valores.

Documentación de funciones

Anotación de tipos

La anotación de tipos en Python (type hints) es una forma de especificar los tipos de datos que se espera que tengan los parámetros de una función y el tipo de dato que la función devolverá. Se introdujeron en Python 3.5 (PEP 484).

Aunque estas anotaciones no son obligatorias y Python no las impone en tiempo de ejecución (Python sigue siendo tipado dinámico), son útiles para la documentación, la comprensión del código y el uso de herramientas de análisis estático como mypy que pueden verificar si los tipos están siendo usados correctamente.

Las anotaciones de tipos se hacen usando la siguiente sintaxis:

  • Parámetros de una función: Para anotar el tipo de un parámetro, se coloca dos puntos : después del nombre del parámetro, seguido por el tipo.
  • Valor de retorno: Para anotar el tipo de retorno, se utiliza una flecha -> después de los paréntesis que contienen los parámetros, seguida por el tipo de retorno esperado.
def sumar(a: int, b: int) -> int:
return a + b
  • a: int indica que el parámetro a debería ser un entero (int).
  • b: int indica que el parámetro b también debería ser un entero.
  • -> int indica que la función devolverá un entero.

Anotar varios tipos posibles

A veces un parámetro (o el valor de retorno) puede admitir más de un tipo de dato válido. Por ejemplo, una función que calcula el doble de un número debería aceptar tanto un int como un float. Para expresar esto se separan los tipos posibles con el operador |:

def doblar(numero: int | float) -> int | float:
return numero * 2

numero: int | float indica que el parámetro numero puede ser un int o un float; la anotación -> int | float indica que el valor de retorno también puede ser de cualquiera de esos dos tipos.

Versión de Python

La sintaxis con | está disponible desde Python 3.10. En versiones anteriores hay que importar Union del módulo typing y escribir Union[int, float] en su lugar:

from typing import Union

def doblar(numero: Union[int, float]) -> Union[int, float]:
return numero * 2

DocString

Un DocString es una cadena de texto que se encuentra en la parte superior de una función (o también en una clase, método o módulo) que proporciona documentación sobre su propósito y uso. Estos son una forma de documentación en línea y se utilizan para describir la funcionalidad del componente.

La convención más común para escribir los DocStrings es utilizar triples comillas (''' o """) para delimitar el texto.

def funcion_documentada(parametro1, parametro2):
"""
Esta es la descripción de la función.

Args:
parametro1 (int): Descripción del parámetro.
parametro2 (float): El denominador de la división.

Returns:
str: Descripción del valor de retorno.

"""
# Cuerpo de la función
return str(parametro1 + parametro2)

En este ejemplo, el DocString está colocado justo debajo de la definición de la función y contiene información sobre el propósito de la función, los parámetros que acepta y el valor que devuelve.

Cuenta con los siguientes componentes:

  • Descripción de la función: proporciona una descripción concisa del propósito de la función.
  • Sección de parámetros (Args): enumera los parámetros que acepta la función, junto con sus descripciones.
  • Sección de valor de retorno (Returns): describe el valor que devuelve la función. Si no devuelve ningún valor, esta sección puede omitirse.
  • Otros componentes opcionales: dependiendo de la complejidad, puedes incluir otras secciones como :Raises para las excepciones que puede generar la función, o :Example para proporcionar ejemplos de uso.
Extensión para VSC

Puedes utilizar la extensión "autoDocstring - Python Docstring Generator" para Visual Studio Code para generar automáticamente los DocStrings.

https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring

Ejemplo: Cálculo de cualificación con funciones

Adapta el programa de cálculo de calificaciones utilizando una función llamada calcula_calificacion, que reciba la puntuación como parámetro y devuelva una calificación como cadena.

def calcula_calificacion(nota):
"""
Función que devuelve la calificación en texto a partir de la nota numérica

Args:
nota (float): valor numérico de la nota

Returns:
str: cadena de texto con la calificación en texto, None si la nota de entrada no es válida

"""
# Según la nota devolvemos la calificación en texto
if nota > 10 or nota < 0:
return None
elif nota >= 9:
return "Sobresaliente"
elif nota >= 7:
return "Notable"
elif nota >= 6:
return "Bien"
elif nota >= 5:
return "Suficiente"
else:
return "Insuficiente"


# Pedimos al usuario la nota del alumno
nota_str = input("¿Cuál es la nota del alumno? ")


# Intentamos pasar la nota a un float
nota_numero = float(nota_str)

# Imprimimos la calificación correspondiente según la nota
nota_texto = calcula_calificacion(nota_numero)
print(nota_texto)