Funciones Métodos Numéricos
Funciones Métodos Numéricos
September 5, 2024
[ ]: import math
# Raíz cuadrada
print(math.sqrt(2.3)) # 1.5166
# Seno de x
print(math.sin(2.3)) # 0.7457
# Logaritmo natural de x
print(math.log(2.3)) # 0.8329
# Potencia
print(math.pow(2, 3)) # 8.0
1
1.51657508881031
0.7457052121767203
0.8329091229351039
8.0
Funciones Definidas por el Usuario En ocasiones, las funciones predefinidas no son suficientes
para nuestros propósitos. En esos casos, podemos definir nuestras propias funciones. Las funciones
personalizadas en Python se definen con la palabra clave def, seguida del nombre de la función y
los parámetros entre paréntesis.
Formato para definir una función:
def nombre_de_la_funcion(parametro_1, ..., parametro_n):
# INSTRUCCIONES
return variable
Puntos importantes al definir una función:
• La función debe comenzar con la palabra clave def.
• El nombre de la función sigue a la palabra clave def.
• Los parámetros de la función se colocan entre paréntesis y están separados por comas. Estos
también se llaman argumentos.
• Después de los paréntesis, se coloca dos puntos :.
• El cuerpo de la función contiene las instrucciones que forman la funcionalidad de la función,
con sangría.
• La función finaliza con la instrucción return seguida de la variable que deseamos devolver
como resultado.
[ ]: import math
print(f"f(4) = {evaluacion_4}")
print(f"f(pi) = {evaluacion_pi}")
print(f"f(e) = {evaluacion_e}")
f(4) = 17
f(pi) = 10.869604401089358
2
f(e) = 8.389056098930649
[ ]: # Cálculo de f(f(2))
f_f_2 = f(f(2))
# Cálculo de f(f(f(3)))
f_f_f_3 = f(f(f(3)))
print(f"f(f(2)) = {f_f_2}")
print(f"f(f(f(3))) = {f_f_f_3}")
f(f(2)) = 26
f(f(f(3))) = 10202
[ ]: # Verificación si f(3) = 5 * 2
es_igual = f(3) == 5 * 2
print(f"¿f(3) == 5 * 2? : \n {es_igual}")
¿f(3) == 5 * 2? :
True
[ ]: def primo(numero):
# Dado un número entero positivo, determina si es primo o no.
3
from math import sqrt
es_primo = True # Suponemos que es primo.
divisor = 2 # Comenzamos a probar desde el número 2.
return es_primo
[ ]: # Ejemplo de uso
numero = int(input("Introduce un número: "))
if primo(numero):
print(f"{numero} es un número primo.")
else:
print(f"{numero} no es un número primo.")
Introduce un número: 23
23 es un número primo.
En este ejemplo, la función primo toma un número entero como argumento y determina si es primo.
Para optimizar el proceso, el algoritmo solo verifica los divisores hasta la raíz cuadrada del número
dado, ya que si un número no tiene divisores menores o iguales a su raíz cuadrada, entonces no
tendrá divisores mayores a ella.
[ ]: import numpy as np
4
# Redondeamos las soluciones a dos decimales
x1 = round(x1, 2)
x2 = round(x2, 2)
print(f"La ecuación tiene dos raíces reales diferentes: x1 = {x1} y x2␣
↪= {x2}")
elif discriminante == 0:
# Si el discriminante es cero, hay una raíz real doble
x = -b / (2*a)
# Redondeamos la solución a dos decimales
x = round(x, 2)
print(f"La ecuación tiene una raíz real doble: x = {x}")
else:
# Si el discriminante es negativo, hay dos raíces complejas
parte_real = -b / (2*a)
parte_imaginaria = np.sqrt(-discriminante) / (2*a)
# Redondeamos las partes real e imaginaria a dos decimales
parte_real = round(parte_real, 2)
parte_imaginaria = round(parte_imaginaria, 2)
print(f"La ecuación tiene dos raíces complejas: x1 = {parte_real} +␣
↪{parte_imaginaria}i y x2 = {parte_real} - {parte_imaginaria}i")
# Programa principal
# Solicitamos al usuario que ingrese los coeficientes 'a', 'b' y 'c'
a = float(input("Ingresa el valor de a (coeficiente de x^2): "))
b = float(input("Ingresa el valor de b (coeficiente de x): "))
c = float(input("Ingresa el valor de c (término independiente): "))
0.0.6 Explicación:
1. Uso de numpy: El cálculo de la raíz cuadrada y otras operaciones matemáticas se realizan
con numpy, que es más robusto y eficiente para manejo de datos numéricos.
2. Función calcular_raices: Esta función reemplaza la función de discriminante original.
Calcula las raíces dependiendo del valor del discriminante (positivo, cero o negativo).
3. Estructura Condicional: Se añaden condicionales para determinar si las raíces son reales
diferentes, reales dobles o complejas.
4. Redondeo: Se usa round para redondear las soluciones a dos decimales para una salida más
legible.
Este código hace uso de numpy para manejar las raíces complejas y se mantiene fiel a la lógica del
5
cálculo cuadrático.
0.0.7 Nota:
un docstring, es un comentario explicativo que se coloca al principio de una función para describir
lo que hace. En Python, los docstrings se utilizan para documentar el propósito y funcionamiento
de una función, clase o módulo.
En el ejemplo anterior incluimos el docstring:
"""
Calcula las raíces de una ecuación cuadrática de la forma ax^2 + bx + c = 0.
Utiliza numpy para manejar los cálculos de discriminante y raíces.
"""
[ ]: help(calcular_raices)
calcular_raices(a, b, c)
Calcula las raíces de una ecuación cuadrática de la forma ax^2 + bx + c = 0.
Utiliza numpy para manejar los cálculos de discriminante y raíces.
En Python, las funciones pueden comportarse de manera muy flexible, permitiendo una amplia
gama de funcionalidades. Dos de estas características son:
1. Funciones que no reciben argumentos
2. Funciones que retornan múltiples valores
6
[ ]: def mostrar_mensaje():
"""
Esta función imprime un mensaje de bienvenida.
No recibe argumentos y no depende de entradas externas.
"""
print("¡Bienvenido a nuestro programa!")
# Llamada a la función
mostrar_mensaje()
"""
suma = a + b
resta = a - b
multiplicacion = a * b
division = a / b if b != 0 else "Indefinida" # Evitar división por cero
return suma, resta, multiplicacion, division
7
# Mostrar los resultados
print(f"Suma: {resultado_suma}, Resta: {resultado_resta}, Multiplicación:␣
↪{resultado_multiplicacion}, División: {resultado_division}")
0.0.11 Conclusión
Las funciones sin argumentos y las que retornan múltiples valores son poderosas herramientas en
Python que facilitan el diseño de programas más organizados, flexibles y fáciles de entender. Estas
características permiten manejar operaciones de manera eficiente y reutilizar código de manera
efectiva.
Ejemplo: Lista de 20 Enteros Se desea elaborar un algoritmo que genere una lista de 20
enteros ordenados de manera vertical. Entre cada entero, se desea escribir una línea formada por
tres guiones bajos ___. Esto se puede lograr utilizando un procedimiento en Python que imprima
esta línea repetidamente.
8
Procedimiento que dibuja una línea de tres guiones bajos.
"""
for i in range(3):
print("_", end=" ")
print() # Para salto de línea
1
_ _ _
2
_ _ _
3
_ _ _
4
_ _ _
5
_ _ _
6
_ _ _
7
_ _ _
8
_ _ _
9
_ _ _
10
_ _ _
11
_ _ _
12
_ _ _
13
_ _ _
14
_ _ _
15
_ _ _
16
_ _ _
17
9
_ _ _
18
_ _ _
19
_ _ _
20
_ _ _
Al ejecutar este programa, se mostrará una lista de enteros del 1 al 20, cada uno separado por una
línea de guiones bajos.
#Programa principal
0.0.13 Conclusión
Los procedimientos en Python son útiles cuando necesitamos realizar tareas repetitivas dentro de
un programa, sin la necesidad de retornar un valor. Siguiendo la estructura básica presentada, se
10
pueden implementar diferentes tipos de procedimientos que faciliten la organización y claridad del
código.
Ejemplo del Uso de main() en Python En el siguiente ejemplo, un programa utiliza la función
main() para calcular el Índice de Masa Corporal (BMI). Aquí, asumimos que el código se guarda en
un archivo y que este archivo se ejecuta, por ejemplo, en una sesión de IDLE. La última declaración
es una llamada a la función main(), que gestiona las operaciones necesarias para el cálculo del BMI.
[ ]: def get_wh():
"""
Solicita al usuario el peso en kilogramos (kg) y la altura en metros (m).
Retorna el peso y la altura.
"""
weight = float(input("Ingrese el peso [kg]: "))
height = float(input("Ingrese la altura [m]: "))
return weight, height
def show_bmi(bmi):
"""
Muestra el BMI calculado.
"""
print("Su índice de masa corporal (BMI) es:", round(bmi, 2))
def main():
"""
Función principal que llama a las otras funciones para obtener entrada,
calcular el BMI y mostrar el resultado.
"""
w, h = get_wh()
11
bmi = calc_bmi(w, h)
show_bmi(bmi)
Ejemplo:
[ ]: # mi_modulo.py
def funcion_saludo():
print("¡Hola desde una función en mi_modulo.py!")
12
if __name__ == "__main__":
print("¡Este mensaje solo se muestra cuando mi_modulo.py es ejecutado␣
↪directamente!")
funcion_saludo()
0.0.19 Ejemplo:
Volvamos a considerar el siguiente ejemplo: un programa utiliza la función main() para calcular el
Índice de Masa Corporal (BMI). Aquí, asumimos que el código se guarda en un archivo y que este
archivo se ejecuta, por ejemplo, en una sesión de IDLE. La última declaración es una llamada a la
función main(), que gestiona las operaciones necesarias para el cálculo del BMI.
[ ]: def get_wh():
"""
Solicita al usuario el peso en kilogramos (kg) y la altura en metros (m).
Retorna el peso y la altura.
"""
weight = float(input("Ingrese el peso [kg]: "))
height = float(input("Ingrese la altura [m]: "))
return weight, height
def show_bmi(bmi):
"""
Muestra el BMI calculado.
"""
print("Su índice de masa corporal (BMI) es:", round(bmi, 2))
def main():
"""
Función principal que llama a las otras funciones para obtener entrada,
calcular el BMI y mostrar el resultado.
"""
w, h = get_wh()
bmi = calc_bmi(w, h)
13
show_bmi(bmi)
if __name__ == "__main__":
main() # Solo se ejecutará si el archivo es ejecutado directamente.
Ejemplo de Prueba:
[ ]: # mi_calculadora.py
if __name__ == "__main__":
print("Probando las funciones de mi_calculadora.py")
print("2 + 3 =", suma(2, 3))
print("5 - 3 =", resta(5, 3))
14
0.0.22 Conclusión
El uso de if __name__ == "__main__" es una práctica esencial en Python para crear scripts
modulares, reutilizables y bien organizados. No solo facilita la depuración y las pruebas, sino que
también permite que los módulos sean utilizados por otros scripts sin necesidad de modificar el
código.
0.0.24 Ejemplo
Hello World
Hello--World
why Hello
World!
why--Hello^v^World!
En la línea 2, print() se llama con dos argumentos. Dado que no se dan argumentos opcionales,
la salida subsiguiente tiene un espacio en blanco que separa los argumentos y la salida termina con
una nueva línea. En la línea 5, el argumento opcional sep se establece en “–” que aparece entre los
argumentos en la salida, como se muestra en la línea 6. En la línea 10, se dan dos declaraciones
15
print() (recuerde que múltiples declaraciones pueden aparecer en una sola línea si están separadas
por punto y coma). La salida de cada una de estas declaraciones termina con los caracteres de
nueva línea predeterminados como se muestra implícitamente en la salida subsiguiente en las líneas
11 y 12. La línea 15 vuelve a contener dos declaraciones print(), pero la primera declaración
print() utiliza los parámetros opcionales para establecer el separador y el terminador de línea en
las cadenas “–” y “v ”, respectivamente.
[ ]: def square(x):
return x * x
square(10)
# Salida: 100
[ ]: 100
16
power(3, 0.5) # Raíz cuadrada de 3, es decir, 3 ** 0.5.
# Salida: 1.7320508075688772
power(3, 4) # 3 al cubo
# Salida: 81
[ ]: 81
0.0.27 Función para Calcular el Valor de 𝑦 en una Línea Recta con Parámetros Op-
cionales
Consideremos otro ejemplo en el que una función calcula el valor de 𝑦 para una línea recta. Recorde-
mos que la ecuación general para una línea es:
𝑦 = 𝑚𝑥 + 𝑏
10
30
34
14
70
En las líneas 4, 6 y 8, la función se llama con uno, dos y tres argumentos, respectivamente. En estas
tres llamadas, los argumentos no están nombrados; por lo tanto, la asignación a los parámetros
17
formales se basa únicamente en la posición. Cuando hay un solo argumento (real), este se asigna
al parámetro formal 𝑥, mientras que 𝑚 y 𝑏 toman los valores predeterminados de 1 y 0, respecti-
vamente. Cuando hay dos argumentos no nombrados (reales), como en la línea 6, se asignan a los
dos primeros parámetros formales, es decir, 𝑥 y 𝑚. Sin embargo, si uno de los argumentos está
nombrado, como en las líneas 10 y 12, la asignación de parámetros reales a parámetros formales ya
no está dictada por el orden.
En la línea 10, el primer argumento (10) se asigna a 𝑥. El segundo argumento dicta que el parámetro
formal 𝑏 se asigne con un valor de 4. Como no se ha especificado nada para el valor de 𝑚, se le
asigna el valor predeterminado.
0.0.28 Ejemplo: Generación del n-ésimo Número de Fibonacci con Valores Iniciales
Personalizados
En este ejemplo, se crea una función que permite al usuario calcular el n-ésimo número de Fibonacci
con valores iniciales específicos para 𝑎 y 𝑏. Además, si el usuario no proporciona estos valores
iniciales, la función utilizará los valores por defecto de 𝑎 = 1 y 𝑏 = 1.
Esta función toma tres parámetros: - N: El índice del número de Fibonacci a calcular. - a: El
primer número de la secuencia de Fibonacci. - b: El segundo número de la secuencia de Fibonacci.
Ejemplo de Uso:
55
Con esta versión de la función, si el usuario no proporciona los valores para 𝑎 o 𝑏, la función usará
𝑎 = 1 y 𝑏 = 1 por defecto.
Ejemplo de Uso:
18
print(fibonacci(10)) # Salida: 55
55
16
𝑛! = 𝑛 × (𝑛 − 1)!
0! = 1
# Ejemplo de uso
print(factorial(5)) # Salida: 120
120
19
En el código anterior, la función factorial() se llama a sí misma con un valor decreciente de
𝑛 hasta que 𝑛 = 0. Este es un ejemplo típico de recursividad donde el problema se resuelve
reduciéndolo a instancias más pequeñas de sí mismo.
Fibonacci(0) = 0, Fibonacci(1) = 1
# Ejemplo de uso
print(fibonacci(7)) # Salida: 13
13
Sin embargo, la solución recursiva para Fibonacci no es eficiente. La cantidad de llamadas recursivas
aumenta exponencialmente, resultando en un problema de rendimiento. Para mejorar esto, se
pueden usar técnicas como la memorización o la programación dinámica.
Args:
n (int): La posición del número en la secuencia de Fibonacci.
Returns:
int: El n-ésimo número de Fibonacci.
20
"""
if n == 0:
return 0
elif n == 1:
return 1
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
# Ejemplo de uso
print(fibonacci_iterativo(13)) # Salida: 8
233
Args:
n (int): La posición del número en la secuencia de Fibonacci.
memo (dict): Diccionario para almacenar los resultados calculados.
Returns:
int: El n-ésimo número de Fibonacci.
"""
if n in memo:
return memo[n]
if n <= 1:
return n
return memo[n]
# Ejemplo de uso
21
print(fibonacci_memorizacion(6)) # Salida: 8
8
En Python, podemos usar la biblioteca functools con el decorador lru_cache para implementar
memorización fácilmente.
@lru_cache(maxsize=None)
def fibonacci_mem(n):
"""
Calcula el n-ésimo término de la serie de Fibonacci de forma recursiva con␣
↪memorización.
"""
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fibonacci_mem(n - 1) + fibonacci_mem(n - 2)
# Ejemplo de uso
print(fibonacci_mem(50)) # Salida: 12586269025
12586269025
22
para problemas que pueden beneficiarse de la estructura recursiva y tienen muchos subproblemas
repetidos, la recursividad con memorización también es una excelente opción.
En este caso, la memorización evita que la función recursiva vuelva a calcular los mismos valores
múltiples veces, mejorando significativamente el rendimiento.
# Ejemplo de uso
arr = [2, 3, 4, 10, 40]
x = 10
resultado = busqueda_binaria(arr, 0, len(arr)-1, x)
if resultado != -1:
print(f"Elemento encontrado en el índice {resultado}")
else:
print("Elemento no encontrado en el array")
23
0.0.37 Conclusión
La recursividad permite soluciones limpias y elegantes para muchos problemas algorítmicos. Sin
embargo, debe usarse con precaución, asegurando siempre la presencia de casos base y considerando
técnicas de optimización como la memorización para evitar problemas de rendimiento.
Definición Básica de una Función Lambda Podemos definir una función lambda que eleva
un número al cuadrado y le suma 4:
[ ]: lambda x: x**2 + 4
[ ]: <function __main__.<lambda>(x)>
104
Asignar una Función Lambda a una Variable Aunque las funciones lambda son anónimas,
se pueden asignar a una variable para usarlas más tarde:
24
# Usar la función lambda con el valor 1
print(f(1)) # Salida: 4
Funciones Lambda con Múltiples Argumentos Las funciones lambda también pueden acep-
tar múltiples argumentos:
5
6
Funciones Lambda con Valores por Defecto Al igual que las funciones tradicionales, las
funciones lambda pueden tener valores predeterminados para sus parámetros:
Uso de Funciones Lambda con Argumentos de Diferentes Tipos Puedes pasar diferentes
tipos de argumentos a una función lambda siempre y cuando la expresión dentro de la función sea
válida para esos tipos:
Uso de Funciones Lambda con Argumentos Variables Las funciones lambda también
pueden manejar un número variable de argumentos usando *args o **kwargs:
(3, 2, 1)
[ ]: doblar = lambda x: 3 * x ** 2 +1
print(doblar(5)) # Salida: 76
76
Otra forma de evaluarla en 𝑥 = 5
25
[ ]: (lambda x: 3 * x ** 2 +1)(5)
[ ]: 76
Definición Básica de una Función Lambda Podemos definir una función lambda que eleva
un número al cuadrado y le suma 4:
lambda x: x**2 + 4
Si queremos utilizar esta función inmediatamente, podemos hacerlo de la siguiente manera:
# Evaluar la función lambda para x = 10
print((lambda x: x**2 + 4)(10)) # Salida: 104
Asignar una Función Lambda a una Variable Aunque las funciones lambda son anónimas,
se pueden asignar a una variable para usarlas más tarde:
# Definir una función lambda que sume 3 a un número
f = lambda x: x + 3
Funciones Lambda con Múltiples Argumentos Las funciones lambda también pueden acep-
tar múltiples argumentos:
# Función lambda que suma dos números
print((lambda x, y: x + y)(2, 3)) # Salida: 5
Funciones Lambda con Valores por Defecto Al igual que las funciones tradicionales, las
funciones lambda pueden tener valores predeterminados para sus parámetros:
# Función lambda con un valor predeterminado para z
print((lambda x, y, z = 3: x * y * z)(1, 2)) # Salida: 6
Uso de Funciones Lambda con Argumentos de Diferentes Tipos Puedes pasar diferentes
tipos de argumentos a una función lambda siempre y cuando la expresión dentro de la función sea
válida para esos tipos:
# Función lambda que concatena cadenas
print((lambda x, y = ' prepared', z = ' pizza': x + y + z)('You')) # Salida: 'You prepared piz
Uso de Funciones Lambda con Argumentos Variables Las funciones lambda también
pueden manejar un número variable de argumentos usando *args o **kwargs:
# Función lambda que invierte el orden de los argumentos
print((lambda *z: z[::-1])(1, 2, 3)) # Salida: (3, 2, 1)
26
0.1.3 ¿Cuándo usar Funciones Lambda?
Las funciones lambda son más útiles cuando necesitas una función pequeña y desechable que se
usará solo una vez o en un contexto limitado. Algunas de las situaciones comunes donde las
funciones lambda son útiles incluyen:
1. Funciones anónimas como argumentos: A menudo se usan con funciones que esperan
otra función como argumento, como map(), filter() y sorted().
2. Operaciones simples: Cuando tienes operaciones que son tan simples que no justifican la
definición de una función completa con def.
3. Mejorar la legibilidad: Aunque pueden hacer el código más conciso, el uso excesivo de
funciones lambda puede afectar la legibilidad. Úsalas cuando realmente hagan el código más
claro y directo.
0.1.5 Ejemplo:
[ ]: numeros = [1, 2, 3, 4, 5]
doblados = list(map(lambda x: x * 2, numeros))
print(doblados) # Salida: [2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
0.1.6 Ejemplo
Uso de map con Funciones Lambda
[ ]: from numpy import sin
Ejemplo:
[ ]: from math import sin, cos, pi
27
#### Visualización Usando `matplotlib`
### Podemos combinar funciones lambda con `matplotlib` para crear␣
↪visualizaciones rápidamente:
28
Ejemplo:
[ ]: import numpy as np
2. Usando lambda con filter() La función filter() se utiliza para crear un nuevo iterable
que contiene solo los elementos que cumplen con una condición dada. Aquí hay un ejemplo que
utiliza lambda para filtrar números pares de una lista:
[2, 4, 6, 8, 10]
3. Usando lambda con sorted() La función sorted() ordena una lista u otro iterable. Puedes
usar lambda como una función clave para especificar el criterio de ordenación. Aquí hay un ejemplo
que ordena una lista de tuplas por el segundo elemento:
[ ]: def cuadrado(x):
return x * x
• Definición de función lambda: Se usa para definir funciones anónimas que son típicamente
de una sola línea.
lambda x: x * x
29
1. Una sola expresión: Una función lambda solo puede contener una expresión. No puede
tener múltiples líneas de código o declaraciones de control de flujo como if o for de manera
explícita.
2. Sin nombre: Las funciones lambda son anónimas, lo que significa que no tienen un nombre
explícito. Aunque esto es útil para funciones desechables, puede hacer que el código sea más
difícil de depurar si se utilizan excesivamente.
3. Legibilidad: Aunque pueden hacer que el código sea más conciso, un uso excesivo de fun-
ciones lambda puede llevar a un código que es difícil de leer y mantener.
Para aplicar una función lambda a un array de NumPy, puedes simplemente pasar el array como
argumento de la función lambda, y NumPy se encargará de aplicar la operación a cada elemento
del array.
0.1.9 Uso de np.vectorize para Aplicar una Función Lambda a un Array de NumPy
Aunque las funciones lambda se pueden aplicar directamente a arrays de NumPy como mostramos
anteriormente, otra forma más eficiente es usar np.vectorize. Esta función “vectoriza” la op-
eración de modo que la función lambda puede ser aplicada a cada elemento del array de manera
más eficiente.
[ 1 4 9 16 25]
30
0.1.11 Llamado por Valor vs. Llamado por Referencia en Python
Python utiliza un modelo que a veces se describe como “llamado por asignación” o “llamado por
objeto-referencia”. La diferencia clave es cómo se comportan los objetos mutables (listas, dic-
cionarios, conjuntos, etc.) y los objetos inmutables (enteros, flotantes, cadenas, tuplas, etc.)
cuando se pasan a funciones.
1. Llamado por Valor Simulado (para objetos inmutables) Cuando se pasa un objeto
inmutable (por ejemplo, un entero o una cadena) a una función, Python en realidad está pasando
una referencia a ese objeto. Sin embargo, como los objetos inmutables no se pueden modificar,
parece que se están pasando “por valor”.
Ejemplo:
[ ]: def modificar_valor(x):
x = 10 # Intentamos cambiar el valor de x
print("Dentro de la función, x =", x)
# Variable original
a = 5
print("Antes de la función, a =", a)
# Llamamos a la función
modificar_valor(a)
# Después de la función
print("Después de la función, a =", a)
Antes de la función, a = 5
Dentro de la función, x = 10
Después de la función, a = 5
En este ejemplo, a sigue siendo 5 después de llamar a la función modificar_valor(). Esto se debe
a que x es una variable local dentro de la función que apunta a una copia del valor de a, no al
objeto original.
2. Llamado por Referencia Simulado (para objetos mutables) Cuando se pasa un objeto
mutable (por ejemplo, una lista o un diccionario) a una función, la función recibe una referencia
al mismo objeto. Por lo tanto, los cambios realizados en el objeto dentro de la función afectan al
objeto original.
Ejemplo:
[ ]: def modificar_lista(lista):
lista.append(4) # Modificamos la lista agregando un nuevo elemento
print("Dentro de la función, lista =", lista)
# Lista original
mi_lista = [1, 2, 3]
print("Antes de la función, mi_lista =", mi_lista)
31
# Llamamos a la función
modificar_lista(mi_lista)
# Después de la función
print("Después de la función, mi_lista =", mi_lista)
[ ]: def modificar_lista(lista):
lista.append(4)
print("Dentro de la función, lista =", lista)
mi_lista = [1, 2, 3]
print("Antes de la función, mi_lista =", mi_lista)
# Después de la función
print("Después de la función, mi_lista =", mi_lista)
32
Antes de la función, mi_lista = [1, 2, 3]
Dentro de la función, lista = [1, 2, 3, 4]
Después de la función, mi_lista = [1, 2, 3]
En este caso, mi_lista no se ve afectada porque pasamos una copia de la lista a la función.
Variables Locales Las variables locales son aquellas que se definen dentro de una función y
solo son accesibles dentro de esa función. No se pueden usar fuera de su ámbito (scope). Esto
significa que cualquier variable creada dentro de una función es una variable local por defecto y es
destruida después de que la función ha terminado de ejecutarse.
Ejemplo de Variable Local:
[ ]: def funcion_ejemplo():
x = 5 # Variable local
print("Valor de x dentro de la función:", x)
# Programa principal
x = 10 # Variable global
print("Valor de x fuera de la función:", x)
funcion_ejemplo()
print("Valor de x fuera de la función después de llamar a la función:", x)
Variables Globales Las variables globales son aquellas que se definen fuera de cualquier
función y están disponibles para cualquier parte del código, tanto dentro como fuera de las funciones.
Para hacer que una variable dentro de una función sea global, se debe usar la palabra clave global.
Ejemplo de Variable Global:
[ ]: x = 10 # Variable global
def funcion_ejemplo():
global x # Declaración de la variable como global
x = x + 5 # Modificación de la variable global
33
print("Valor de x dentro de la función:", x)
# Programa principal
print("Valor de x antes de la función:", x)
funcion_ejemplo()
print("Valor de x después de la función:", x)
Ejemplo Adicional: Listas y Paso por Referencia En Python, las listas son mutables y se
pasan por referencia, lo que significa que cualquier cambio realizado en una lista dentro de una
función afecta a la lista original fuera de la función.
[ ]: def modificar_lista(lista):
lista.append(4) # Modificación de la lista
# Programa principal
mi_lista = [1, 2, 3]
print("Lista antes de la función:", mi_lista)
modificar_lista(mi_lista)
print("Lista después de la función:", mi_lista)
34
Veamos un ejemplo donde se define una función para dividir una cadena de texto en dos partes,
utilizando una posición específica indicada en el parámetro:
# Ejemplo de uso
resultado = ssplit('Always remember us this way', 15)
print(resultado) # Salida: ('Always remember', ' us this way')
Valores por Defecto Así como en la definición habitual de funciones, cuando usamos anotaciones
de tipos también podemos indicar un valor por defecto para los parámetros.
Por ejemplo, podemos definir un valor por defecto en la función ssplit:
# Ejemplo de uso
resultado = ssplit('Always remember us this way')
print(resultado) # Salida: ('Always rememb', 'er us this way')
Ventajas de las Anotaciones de Tipos Las anotaciones de tipos son una herramienta
poderosa que, cuando se usan de manera adecuada, pueden complementar la documentación de
nuestro código y aclarar ciertos aspectos que pueden ser confusos a primera vista. Su aplicación
depende de la necesidad detectada por el equipo de desarrollo y su uso puede mejorar significati-
vamente la legibilidad y mantenibilidad del código.
35
El siguiente código define una función llamada surface_area_of_cube que calcula el área de la
superficie de un cubo dado su longitud de arista. La función utiliza anotaciones de tipos para
especificar el tipo de datos esperado de los parámetros y el tipo de datos devuelto por la función.
A continuación, se explica cada parte del código y los tipos usados.
Definición de la Función
def surface_area_of_cube(edge_length: float) -> str:
1. Nombre de la función: surface_area_of_cube.
2. Parámetro de entrada:
• edge_length: float: La función espera un parámetro llamado edge_length de tipo
float. Esto significa que el valor pasado a este parámetro debe ser un número de punto
flotante (por ejemplo, 3.5 o 4.0). Este parámetro representa la longitud de la arista del
cubo.
3. Valor de retorno:
• -> str: La flecha -> seguida de str indica que la función devolverá un valor de tipo
str (cadena de texto). Es una anotación de tipo que especifica que el resultado de la
función será una cadena.
Cuerpo de la Función
return f"The surface area of the cube is {6 * edge_length ** 2}."
• Cálculo:
– 6 * edge_length ** 2: El área de la superficie de un cubo se calcula como 6 veces el
cuadrado de la longitud de su arista. El operador ** se utiliza para elevar edge_length
al cuadrado.
– Por ejemplo, si edge_length es 3.0, entonces 6 * 3.0 ** 2 resulta en 54.0.
• Formato de cadena:
– f"The surface area of the cube is {6 * edge_length ** 2}.": Utiliza una f-
string (cadena formateada) para combinar el texto con el cálculo del área de la su-
perficie. Las f-strings permiten insertar expresiones dentro de llaves {} y evaluar esas
expresiones en tiempo de ejecución.
– El resultado del cálculo 6 * edge_length ** 2 se inserta en la cadena dentro de las
llaves {}.
surface_area_of_cube(3)
36
Este uso de las anotaciones de tipos mejora la legibilidad del código y facilita la detección de
errores, ya que proporciona información adicional sobre lo que se espera como entrada y salida de
la función.
¿Cómo Funcionan las Funciones que Devuelven Múltiples Valores? Cuando queremos
que una función devuelva varios valores, podemos separarlos con comas en la instrucción return.
Por defecto, Python empaqueta estos valores en una tupla, y podemos desempaquetar la tupla en
varias variables al llamar a la función.
Ejemplo 1: Retornar Múltiples Valores desde una Función En el siguiente ejemplo, creare-
mos una función llamada calcula_suma_y_producto que toma dos números como argumentos y
devuelve su suma y su producto.
Returns:
tuple: Una tupla que contiene la suma y el producto de los números.
"""
suma = a + b
producto = a * b
return suma, producto
# Ejemplo de uso
resultado_suma, resultado_producto = calcula_suma_y_producto(3, 5)
print(f"Suma: {resultado_suma}, Producto: {resultado_producto}")
Suma: 8, Producto: 15
37
• Desempaquetado: Cuando llamamos a la función, la tupla devuelta se desempaqueta en
dos variables resultado_suma y resultado_producto.
Args:
lista (List[int]): Lista de números enteros.
Returns:
Tuple[int, int, float, int]: Una tupla que contiene el mínimo, el máximo,
el promedio y la suma de la lista.
"""
minimo = min(lista)
maximo = max(lista)
promedio = sum(lista) / len(lista)
suma = sum(lista)
return minimo, maximo, promedio, suma
# Ejemplo de uso
numeros = [4, 7, 1, 12, 9, 3]
min_valor, max_valor, promedio_valor, suma_valor = estadisticas(numeros)
print(f"Mínimo: {min_valor}, Máximo: {max_valor}, Promedio: {promedio_valor:.
↪2f}, Suma: {suma_valor}")
38
• Tuple: De manera similar, Tuple[int, int, float, int] se utiliza para especificar
que la función estadisticas devuelve una tupla con cuatro elementos en el siguiente
orden: un int (mínimo), otro int (máximo), un float (promedio) y otro int (suma).
Esto aclara para cualquiera que lea el código o lo use en un proyecto, qué tipo de valores
se esperan y en qué orden.
3. Ventajas de Usar typing:
• Claridad y Mantenimiento: Facilita la comprensión del código al dejar claro qué
tipos de datos se esperan y qué se devuelve. Esto es crucial en proyectos grandes donde
muchas personas trabajan en el mismo código.
• Análisis Estático: Herramientas como mypy pueden analizar el código antes de eje-
cutarse para detectar errores relacionados con los tipos de datos, lo que ayuda a evitar
errores en tiempo de ejecución.
• Documentación Automática: Ayuda a generar documentación automáticamente me-
diante herramientas como Sphinx, lo que mejora la calidad de la documentación.
4. Cálculos Realizados por la Función:
• minimo: Calcula el mínimo de la lista usando la función incorporada min().
• maximo: Calcula el máximo de la lista usando la función incorporada max().
• promedio: Calcula el promedio de los elementos de la lista dividiendo la suma total
(sum(lista)) por el número de elementos (len(lista)).
• suma: Calcula la suma total de los elementos de la lista usando la función incorporada
sum().
5. Devolución de Múltiples Valores:
• Todos estos valores calculados se devuelven como una tupla. La tupla es una estructura
de datos que agrupa varios valores, y es perfecta para devolver múltiples resultados sin
necesidad de empaquetar/desempaquetar manualmente.
𝑥 𝑥2 𝑥𝑛
1+ + +⋯+
1 2! 𝑛!
donde 𝑥 es un número real y 𝑛 es un número entero positivo dado por el usuario. Esta serie es
una forma truncada de la expansión de Taylor para la función exponencial 𝑒𝑥 . La función recibirá
39
como argumentos el valor de 𝑥 y el valor de 𝑛, y retornará el resultado de la suma de la serie hasta
el término 𝑛.
[ ]: import math
Args:
x (float): El valor de x.
n (int): El número de términos en la serie.
Returns:
float: El resultado de la suma de la serie.
"""
suma = 1.0 # Empezamos la suma en 1 para incluir el primer término 1
for i in range(1, n + 1):
suma += (x ** i) / math.factorial(i)
return suma
# Ejemplo de uso
# Solicitamos al usuario que ingrese los valores de x y n
x = float(input("Ingrese el valor de x: "))
n = int(input("Ingrese el número de términos n: "))
# Mostramos el resultado
print(f"La suma de la serie para x = {x} y n = {n} es: {resultado}")
Ingrese el valor de x: 4
Ingrese el número de términos n: 6
La suma de la serie para x = 4.0 y n = 6 es: 48.55555555555555
40
• Utilizamos un bucle for que itera desde 1 hasta 𝑛. En cada iteración, se añade el término
𝑥𝑖
𝑖! a suma.
[ ]: def suma(*args):
return sum(args)
# Ejemplo de uso
print(suma(1, 2, 3, 4)) # Salida: 10
10
[ ]: # Ejemplo de uso
print(suma(1, 2, 3, 4, 6, 8)) # Salida: 24
24
En este ejemplo, *args empaqueta todos los argumentos proporcionados en una tupla llamada
args.
41
En este caso, *argumentos desempaqueta la tupla (10, 3) en dos argumentos posicionales para la
función resta.
Usaremos *args para permitir que la función reciba un número variable de componentes de un
vector.
Código en Python
[ ]: import math
def norma_vector(*args):
"""
Calcula la norma de un vector en R^n.
Args:
*args: Componentes del vector.
Returns:
float: La norma del vector.
"""
suma_cuadrados = sum(x**2 for x in args) # Calcula la suma de los␣
↪cuadrados de los componentes
return math.sqrt(suma_cuadrados)
# Ejemplo de uso
vector = (3, 4) # Un vector en R^2
print(f"La norma del vector {vector} es: {norma_vector(*vector)}") #␣
↪Desempaquetar la tupla vector
42
• *args permite que la función reciba un número arbitrario de componentes del vector.
• Se calcula la suma de los cuadrados de los componentes con una expresión generadora:
sum(x**2 for x in args).
• Retornamos la raíz cuadrada de la suma usando math.sqrt().
3. Uso de la Función:
• Desempaquetamos las tuplas vector y vector3D cuando llamamos a la función
norma_vector.
0.1.26 Ejemplo 2:
Ahora, definiremos una función que calcule la siguiente expresión:
√𝑥 + √𝑥 + √𝑥 + ⋯ + √𝑥
1 2 3 𝑛
Esta función también utilizará *args para manejar un número variable de argumentos.
Código en Python
[ ]: import math
def nested_sqrt(*args):
"""
Calcula sqrt(x1 + sqrt(x2 + sqrt(x3 + ... + sqrt(xn)))).
Args:
*args: Una secuencia de números a los que se aplicará la raíz anidada.
Returns:
float: El resultado de la expresión.
"""
if not args: # Si no hay argumentos, retornamos 0
return 0
# Comenzamos desde el último número y aplicamos sqrt de manera anidada
resultado = args[-1]
for x in reversed(args[:-1]): # Recorremos los elementos de derecha a␣
↪izquierda
# Ejemplo de uso
print(nested_sqrt(2, 3, 4)) # sqrt(2 + sqrt(3 + sqrt(4)))
print(nested_sqrt(5, 7, 11, 13)) # sqrt(5 + sqrt(7 + sqrt(11 + sqrt(13))))
2.1554004989942337
2.9068006025152773
43
Explicación del Código
1. Función nested_sqrt(*args):
• La función recibe un número arbitrario de argumentos gracias a *args.
• Se empieza desde el último número y se aplica la raíz cuadrada de manera anidada,
trabajando de derecha a izquierda.
• Usamos reversed() para iterar en orden inverso y aplicar la raíz cuadrada anidada.
2. Uso de la Función:
• Se llama a nested_sqrt con diferentes conjuntos de argumentos para calcular las raíces
anidadas.
1
𝑓(𝑐0 , 𝑐1 , … , 𝑐𝑚 ) = 𝑐0 + 1
𝑐1 + 𝑐2 +⋯+ 𝑐1
𝑚
Solución La solución implica comenzar con la última fracción, 𝑐1 , y trabajar de regreso hasta
𝑚
llegar a 𝑐0 . En cada paso, agregamos la fracción correspondiente. Este proceso puede capturarse
en el siguiente fragmento de código:
s = 0
for i in c[::-1]:
s = i + 1/s
Aquí, usualmente, en la primera iteración del bucle, s comenzaría con 0. Sin embargo, eso causaría
un error ya que estaríamos usando 1/𝑠 con 𝑠 = 0. Para evitar este error, definimos una declaración
if dentro del código para verificar si el valor de s es cero, y en ese caso ignoramos 1/𝑠.
Args:
*c: Una secuencia de números enteros no negativos, donde el último número␣
↪no debe ser cero.
Returns:
float: El resultado de la función recursiva.
"""
s = 0
# Recorremos los elementos de c de derecha a izquierda
for i in c[::-1]:
44
s = i + (1/s if s != 0 else 0) # Verificamos si s es cero para evitar␣
división por cero
↪
return s
# Ejemplo de uso
print(f(1, 2, 3, 4)) # Salida: 1.4333333333333333
1.4333333333333333
1.4331274267223117
45
Código en Python
[ ]: import time
import math
# Resultados
print(f"Factorial calculado con la función personalizada:␣
↪{factorial_result_custom}")
# Comparación de tiempos
if execution_time_custom > execution_time_builtin:
print("La función math.factorial es más rápida.")
else:
print("La función personalizada es más rápida.")
46
Explicación de los Resultados
• El código imprime el tiempo de ejecución de ambas funciones (la personalizada y la de la
biblioteca estándar).
• Al comparar los tiempos de ejecución, podemos ver cuál de las dos funciones es más eficiente
para calcular el factorial de un número dado.
• En general, la función math.factorial() de la biblioteca estándar será más rápida y eficiente
porque está optimizada a nivel de implementación en C, mientras que la función personalizada
es recursiva y tiene más sobrecarga de llamadas a función.
Este ejemplo ilustra cómo medir y comparar tiempos de ejecución en Python utilizando la librería
time.
0.1.30 Ejemplo:
El método de Newton-Raphson es un algoritmo numérico muy eficaz para encontrar aproximaciones
de las raíces (o ceros) de una función real. Este método utiliza la tangente de la curva de la función
para encontrar el punto en el cual corta al eje 𝑥, y usa este punto como nueva aproximación para
la raíz de la función.
El método comienza con un valor inicial 𝑥0 cercano a la raíz de la función 𝑓(𝑥). A continuación,
se utiliza la siguiente fórmula iterativa para encontrar una mejor aproximación 𝑥1 :
𝑓(𝑥0 )
𝑥1 = 𝑥0 −
𝑓 ′ (𝑥0 )
Este proceso se repite, utilizando 𝑥1 para encontrar 𝑥2 , 𝑥2 para encontrar 𝑥3 , y así sucesivamente.
De manera general, la fórmula iterativa es:
𝑓(𝑥𝑛 )
𝑥𝑛+1 = 𝑥𝑛 −
𝑓 ′ (𝑥𝑛 )
#Ejemplo
Este código muestra cómo encontrar una raíz de la función $ f(x) = x^2 - 4 $ utilizando el método
de Newton-Raphson en Python. Además, se incluye una gráfica que muestra la función y la raíz
encontrada.
[ ]: import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from IPython.display import display
47
def df(x):
return 2 * x # Derivada
# Parámetros iniciales
x0 = 9 # valor inicial
tolerance = 1e-7 # tolerancia
max_iter = 50 # número máximo de iteraciones
# Algoritmo de Newton-Raphson
for i in range(max_iter):
x1 = x0 - f(x0) / df(x0) # Nueva aproximación
error = abs(x1 - x0) # Error absoluto
xs.append(x1)
xs1.append(x1)
errors.append(error)
# Crear una tabla de pandas para mostrar los resultados de las iteraciones
df_results = pd.DataFrame({
'Iteración': range(1, len(xs) + 1),
'Aproximación x': xs,
'Error Absoluto': errors
})
plt.figure(figsize=(10, 6))
48
# Graficar la función
plt.plot(x_vals, y_vals, label='f(x) = x^2 - 4', color='blue')
49
0.1.31 Ejemplo en Python
Para ilustrar cómo funciona el método de Newton-Raphson, vamos a encontrar una raíz de la
función $ f(x) = x^2 - 4 $ usando Python. Utilizaremos la función root_scalar de la biblioteca
SciPy para aplicar el método de Newton-Raphson.
Primero, instala las bibliotecas necesarias:
pip install scipy matplotlib
A continuación, el código para implementar el método:
[ ]: import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.misc import derivative
from IPython.display import display
# Definir la función
def f(x):
return x**2 - 4 # Función
# Parámetros iniciales
x0 = 9 # valor inicial
tolerance = 1e-7 # tolerancia
max_iter = 50 # número máximo de iteraciones
50
# Listas para almacenar el progreso
xs = [x0] # Lista de aproximaciones, incluyendo el valor inicial
errors = [] # Lista de errores absolutos
break
# Crear una tabla de pandas para mostrar los resultados de las iteraciones
df_results = pd.DataFrame({
'Iteración': range(1, len(xs)),
'Aproximación x': xs[1:], # Excluye el valor inicial x0 para mostrar las␣
↪iteraciones
plt.figure(figsize=(10, 6))
51
# Graficar la función
plt.plot(x_vals, y_vals, label='f(x) = x^2 - 4', color='blue')
52
0.1.32 Ejemplo: Resolución de una Ecuación No Lineal Usando el Método de Newton
Supongamos que necesitamos resolver una ecuación no lineal compleja que aparece en el contexto
de la termodinámica. Esta ecuación está relacionada con el cálculo de la presión de vapor de
una sustancia dada mediante la ecuación de Clausius-Clapeyron. La ecuación de Clausius-
Clapeyron se puede expresar de la siguiente manera:
Δ𝐻𝑣𝑎𝑝 1
ln(𝑃 ) = − ( )+𝐶
𝑅 𝑇
Planteamiento del Problema Dado: - Δ𝐻𝑣𝑎𝑝 = 40, 650 J/mol - 𝑅 = 8.314 J/(mol K) - 𝐶 =
18.3 - 𝑃 = 101325 Pa (presión atmosférica estándar)
Queremos encontrar la temperatura 𝑇 en Kelvin que satisfaga la ecuación. Reordenando la ecuación
de Clausius-Clapeyron, podemos escribir:
53
40, 650 1
ln(101325) + ( ) − 18.3 = 0
8.314 𝑇
# Función a resolver
return np.log(P) + (delta_H_vap / R) * (1 / T) - C
# Derivada de la función
return - (delta_H_vap / R) * (1 / T**2)
# Parámetro inicial
T0 = 350 # Temperatura inicial en Kelvin
# Mostrar la solución
print(f"La temperatura para la cual la presión de vapor es 101325 Pa es␣
↪aproximadamente: {T_solution:.2f} K")
54
𝑇 . La derivada es necesaria para el método de Newton-Raphson, ya que este método utiliza
el gradiente de la función para aproximar la raíz.
3. Uso del Método de Newton-Raphson (scipy.optimize.newton): Se usa la función
newton de la biblioteca scipy.optimize para resolver la ecuación no lineal. Se le proporciona
la función f(T), su derivada df(T), una aproximación inicial T0, la tolerancia deseada tol,
y el número máximo de iteraciones maxiter.
4. Solución: El resultado es la temperatura 𝑇 en Kelvin para la cual la presión de vapor es
igual a 101325 Pa. Este es el valor de 𝑇 que satisface la ecuación planteada.
𝑃
𝐴= ((1 + 𝑖)𝑛 − 1)
𝑖
55
# Resolver la ecuación usando el método de Newton
i_solution = newton(f, i0, fprime=df, tol=1e-7, maxiter=50)
Ejemplo: Cálculo del Monto Necesario para Pagar una Hipoteca Descripción del
Problema:
Queremos encontrar la tasa de interés máxima 𝑖 que un prestatario puede pagar para un prés-
tamo de hipoteca dado un valor máximo de pago mensual. La fórmula para el monto del dinero
requerido para pagar una hipoteca durante un período de tiempo fijo viene dada por la ecuación
de anualidad ordinaria:
𝑃
𝐴= (1 − (1 + 𝑖)−𝑛 )
𝑖
def f(i):
A = 135000 # Monto de la hipoteca en USD
P = 1000 # Pago mensual máximo en USD
n = 360 # Número de pagos
return (P / i) * (1 - (1 + i)**-n) - A
56
i0 = 0.01
21
1+𝑝 𝑝
𝑃 = ( )
2 1 − 𝑝 + 𝑝2
Objetivo:
Determinar el valor de 𝑝 tal que la probabilidad de que A gane sea mayor o igual a una cantidad
dada.
Resolución del Problema:
Usamos el método de Newton para encontrar el valor de 𝑝.
try:
denominator = (1 - p + p**2)**22
if np.isclose(denominator, 0):
return np.inf # Manejo de divisiones por cero
numerator = p**20 * (-20 * p**3 - 22 * p**2 + 22 * p + 21)
return numerator / (2 * denominator)
57
except OverflowError:
return np.inf # Manejo de sobrecargas
except RuntimeError as e:
print(f"Error: {e}")
58