librutech.com, CC BY-SA 4.0, via Wikimedia Commons
En Python y en cualquier lenguaje de programación, una función es un grupo de declaraciones relacionadas que realizan una tarea específica.
Las funciones ayudan a dividir nuestro programa en partes más pequeñas y modulares. A medida que nuestro programa crece más y más, las funciones lo hacen más organizado y manejable. Además, las funciones evitan la repetición y hace que el código sea reutilizable.
Las funciones son importantes en la programación por varias razones:
Modularidad: las funciones permiten dividir un programa en bloques más pequeños y manejables. Esto hace que el código sea más fácil de entender, depurar y mantener.
Reutilización de código: al dividir un programa en funciones, podemos reutilizar el mismo código en diferentes partes del programa o incluso en diferentes programas.
Legibilidad: las funciones permiten dar nombres significativos a bloques de código, lo que mejora la legibilidad del código.
Abstracción: las funciones pueden ser usadas para representar acciones, para encapsular detalles de implementación y para proporcionar una interfaz más clara y fácil de usar para los usuarios de la función.
Testing: las funciones son fáciles de probar de forma individual, lo que facilita la identificación y corrección de errores en el código.
Una analogía con la vida diaria puede ser la siguiente:
Imagínese cuando se levanta. Lo que ocurre primero, es que uno se siente vivo, se abren los ojos (no siempre), se hace pereza un rato, se levanta de la cama y luego se inicia el día (cada persona tiene sus maneras diferentes de hacerlo).
Esta serie de pasos consecutivos conforman lo que podríamos llamar una función
levantarse.
En el común, una función es en general una secuencia de pasos o una serie de acciones que generan un resultado específico. De manera abstracta, esos pasos consecutivos se encapsulan en un objeto llamado función, lo cual entrega ventajas cuando se describe una situación compleja y por lo general de carácter repetitivo.
Para poder distinguir funciones, unas de otras, se les asignan nombres diferentes.
Por ejemplo, la función antes descrita podría nombrarse como Levantarse:
Está función se compone de los siguientes pasos:
Así, aunque cada persona puede desarrollar la actividad de levantarse de manera un poco diferente, en general tal actividad se puede ver como una función que es ejecutada en algún momento por algún individuo.
En el lenguaje de programación, esto se podría escribir como
Levantarse(x)
donde $x$ representa el individuo que hace la acción y los paréntesis significan que se habla de una función, y por lo tanto, no se confunde con una variable.
En un lenguaje de programación, una función puede entenderse como una caja negra. Como el control remoto de su televisor. Los ingenieros que desarrollan el control remoto cierran el objeto una vez lo construyen y por lo general elaboran un manual con las especificaciones para construir cada control. Adicionalmente, elaboran otro manual para enseñar a los usuario a usar el objeto. Como usuario usted solamente sabe que al presionar un botón se realiza una acción, y eso es suficiente para lo que necesita hacer. Usted desconoce qué es lo que hace o cómo está implementado el que se haga la acción, y para el caso del ejemplo eso no es un problema para lo que necesita hacer (por ejemplo, cambiar de canal o cualquier otra cosa).
CortarManzana(x)
:¶Un segundo ejemplo muy simple es el siguiente. Imagine una máquina que corta manzanas como se ilustra en la siguiente imagen.
The original uploader was Theresa knott at English Wikibooks., CC BY-SA 3.0, via Wikimedia Commons
Como podemos ver, una función es realmente es una caja negra
: la podemos usar, sin importar qué hay dentro de ella.
Sin embargo, la máquina puede tener algunos controles (parámetros) que determinan la forma como la máquina funciona. Por ejemplo, el número de cortes. Luego de parametrizada la máquina, podemos empezar a pasar manzanas por ella.
Ya hemos visto algunas funciones básicas de Python (Built-in).
print()
: es una función preconstruida que trata de "imprimir" lo que se le "de". La salida de esta función es enviada al flujo de salida (output stream). Este flujo de salida, que son una secuencia de caracteres, es enviada a la terminal o línea de comandos en que ejecutamos la instrucción.input()
: es una función preconstruida que "imprime" lo que se le "de" y luego trata de "leer" lo que el usuario escriba o "responda". La salida de esta función es la secuencia de caracteres que el usuario introduzca hasta que presiona la tecla Enter. Aquí se tiene un flujo de entrada.type()
: es una función preconstruida que tiene como resultado el tipo de la variable (la clase a la que pertenece la variable).str()
, int()
, float()
, complex()
y bool()
: son funciones preconstruidas que se pueden usar para cambiar el tipo de una variable (naturalmente el valor que tenga almacenado la variable también es transformado, en caso de que sea posible dicha transformación).En este enlace, se encuentra un manual de todas las funciones básicas de Python.
Las funciones pueden tener uno o más parámetros asociados. Estos son contenedores que recibirán los datos (argumentos) para que la función pueda hacer su trabajo.
Consideremos por ejemplo la función matemática seno (sin
en inglés)
En el ejemplo, el parámetro de la función es 'x' que representa a cualquier valor que pueda ser utilizado para calcular la función.
Un argumento válido para esta función es por ejemplo $\pi/2$. Entonces el resultado obtenido es
$$ 1.0 = \sin (\pi/2). $$aprendimos en la escuela secundaria.
sin(x)
¶Para este ejemplo utilizaremos dos librerías de Python: Numpy y Matplotlib. Puede ir a los capítulos correspondientes para los detalles.
La librería Numpy se llamará con el alias np
y se usa un punto para llamar algún objeto de la librería. Similar con Matplotlib.pyplot que llamaremos con el alias de plt
.
np.linspace
genera valores entre dos extremos. En el ejemplo genera 30 puntos entre 0 y $2\pi$.np.sin
calcula la función seno para algún conjunto de argumentos. Observe que la función calcula la función para cada uno de los argumentos dispuestos en el objeto x.np.plt
dibuja el gráfico de la función entre los valores 0 y $2\pi$.import numpy as np # importa la librería Numpy
# calcula la función sin en 30 puntos
x = np.linspace(0,2*np.pi,30)
y = np.sin(x)
# Imprime la tabla de valores.
# Usamos zip para pegar las columnas x, y
print('Estos son los valores para el gráfico de la función')
print("x y")
x_y = zip(np.round(x,2), np.round(y,2))
for values in x_y:
print(values)
import matplotlib.pyplot as plt # importa la librería matplotlib.pyplot
# crea la imagen
print('Imagen')
plt.title('Gráfico de la función $y=\sin(x)$')
plt.xlabel('x')
plt.ylabel('$\sin (x)$')
plt.plot(x,y)
plt.show()
Hay dos maneras básicas para definir funciones en Python:
def
lambda
De manera muy general, una función se define iniciando con la cláusula def()
. Dentro de los paréntesis se colocan los parámetros que usará la función.
Nuestro primer ejemplo es una función que suma dos variables y regresa el resultado.
# función suma
def suma(x, y):
s = x + y
return s
La cláusula return
se usa para retornar el resultado de la función.
Ejecutar o usar la función es bastante simple. Por ejemplo la suma entre 3 y 5 se puede calcular haciendo:
s = suma(3, 5)
print(s)
De manera formal, una función siempre retorna un único valor, pero es posible retornar "más de uno" utilizando una variable compuesta. Observe el siguiente ejemplo.
# Explicar las siguientes líneas de código
def mysum_prod(x,y):
s = x + y
p = x * y
return s, p # s y p se juntarán para formar una variable compuesta
result = mysum_prod(3,4)
print(result)
type(result)
También podemos separar la salida, usando asignación múltiple, si es lo que se requiere, de la siguiente manera:
suma, prod = mysum_prod(3,4)
print("El suma es:", suma)
print("El producto es:", prod)
Pareciera que la función estuviese dando dos valores pero sabemos que en realidad está devolviendo una variable compuesta que puede ser separada/descompuesta y recibida en más de una variable.
Recuerde que la asignación de variables en Python es dinámica. Esto facilita la programación, pero puede introducir algunos problemas.
En el caso de las funciones, es importante que el usuario (que siempre incluye al programador) sepa qué tipos de parámetros espera una función. Para hacerlo, las versiones de Python desde 2.0 introdujeron el concepto anotación o tipificación de los parámetros de una función.
Se pretende indicar al usuario de la función qué tipo de datos se espera que entren a la función y los que salen. Esto es muy útil para mejorar la lectura del código.
Por supuesto, aún es posible usar tipos de datos diferentes a los que sugerimos. Esto es debido a la flexibilidad que le quisieron dar a Python (y a la asignación dinámica que precisamente hace parte de esa flexibilidad).
Para ilustrar la anotación revise el siguiente ejemplo.
def mysum_prod(x: int, y: int) -> [int, int]:
s = x + y
p = y * x
return [s,p]
mysum_prod(2.2, 3)
En ocasiones se espera definir valores por defecto para los parámetros de una función. Esta se hace simplemente escribiendo parametro=valor
. Veamos el ejemplo
# función suma con parámetros por defecto
def suma(x, y=1):
s = x + y
return s
print(suma(3))
El cuidado que hay que tener es que los parámetros por defecto siempre van después de los parámetros obligatorios. En el ejemplo el parámetro x es obligatorio y debe ser entregado a la función. Por otro lado el parámetro y esta definida por defecto con el valor de 1.
*arg
¶Para situaciones en las cuales se desconoce de antemano el número de argumentos que se esperan para ejecutar una función, es posible nombrar los parámetros de manera genérica en la forma *arg
. Se dice estos son parámetros no nombrados (non keyword arguments
) porque no es necesario indicar un nombre para cada argumento que se pasa al función. Revise el siguiente ejemplo. El objetivo es sumar un número indeterminado de números. Por supuesto este es un ejemplo de juguete, pero sirve para ilustrar el concepto. Veamos:
# sumador
def sumador(*num):
suma = 0
for n in num:
suma = suma + n
print("Sum:",suma)
sumador(3,5)
sumador(4,5,6,7)
sumador(1,2,3,5,6)
**arg
¶En el ejemplo anterior no se diferenciaron los parámetros con un nombre. Si requiere pasar parámetros arbitrarios pero con nombre cada uno, puede usar la notacion **arg
. Estos argumentos se llaman keyword arguments
. Veamos el ejemplo:
def intro(**data):
print("\nTipo de dato del argumento:",type(data))
for key, value in data.items():
print("{} is {}".format(key,value))
intro(Firstname="Sita", Lastname="Sharma", Age=22, Phone=1234567890)
intro(Firstname="John", Lastname="Wood", Email="johnwood@nomail.com", Country="Wakanda", Age=25, Phone=9876543210)
Si desea ver un video introductorio sobre programación funcional y entender más a fondo el concepto del cálculo y funciones lambda le recomendamos este vídeo de Betta Tech.
Hay situaciones, principalmente relacionadas con la programación funcional, en la cuales no se requiere dar un nombre a una función, sino más bien definir la forma como trabaja la función.
Observe el siguiente ejemplo.
# Explique qué diferencias hay entre éste método y el anterior
sumita = lambda arg1,arg2: arg1+arg2
a = 6
b = 3
s = sumita(a,b)
print("la suma es:", s)
En este ejemplo, la función anónima lambda recibe dos argumentos y regresa su suma. Le hemos dado un nombre: sumita. Pero realmente esto no es siempre necesario. Sin embargo, muestra cómo es posible asignar una función a un objeto, para ser utilizada como una función normal.
La lógica es la siguiente:
lambda arg1,arg2: arg1+arg2
define la función anónima.def
.Observe detenidamente el siguiente fragmento de código.
# Explicar adecuadamente
def mifunc(n):
return lambda a : a * n
midoblador = mifunc(2)
print(midoblador(11))
La lógica es la siguiente:
midoblador = mifunc(2)
se ha definido una nueva función que se llamará midoblador. la cual recibirá un argumento y lo multiplicará por 2 (el valor de n)Estudie detenidamente el ejemplo anterior y construya un ejemplo propio.
Las funciones puras tienen dos propiedades.
La segunda propiedad también se conoce como inmutabilidad. El único resultado de la función pura es el valor que devuelve. Son deterministas. Los programas realizados mediante programación funcional son fáciles de depurar porque las funciones puras no tienen efectos secundarios ni E/S ocultas. Las funciones puras también facilitan la escritura de aplicaciones paralelas/concurrentes.
Cuando el código está escrito en este estilo, un compilador inteligente puede hacer muchas cosas: puede paralelizar las instrucciones, esperar a evaluar los resultados cuando los necesite y memorizar los resultados, ya que los resultados nunca cambian mientras la entrada no cambie.
Revise el siguiente ejemplo.
# Ejemplo de una función pura
def elevado_a_un_numero_entero_positivo_V1(base, exponente_natural):
if exponente_natural>=1 and isinstance(exponente_natural,int):
resultado = base
while exponente_natural > 1:
resultado = resultado * base
exponente_natural = exponente_natural - 1
return resultado
else:
raise Exception('exponente_natural debe ser de tipo entero y mayor o igual a 1: ' + str(exponente_natural))
# La función en acción:
entrada1 = -1.234
entrada2 = 3
salida = elevado_a_un_numero_entero_positivo_V1(entrada1, entrada2)
print(salida)
# Para comparar:
print(entrada1**entrada2)
En la programación funcional, no existe el concepto de bucle for
o bucle while
, sino que se utiliza la recursividad. **).
Para comprender la recursividad se deben tener en cuenta las siguientes premisas:
Una solución recursiva es aquella en donde una función se llama a sí misma. La idea es que en cada llamado el problema a resolver se reduzca y los múltiples llamados terminen cuando se alcancen los casos triviales o más pequeños posibles. Para una solución recursiva es muy importante que efectivamente haya una reducción del problema en cada llamado y que se tenga al menos una condición de parada.
Una solución recursiva es ventajosa cuando la misma naturaleza del problema a resolver es recursiva (por ejemplo, el manejo de estructuras de árbol). Una solución recursiva podría considerarse más elegante y con menos líneas de código que una solución iterativa (lo cual podría facilitar la escritura y lectura del código). Por otra parte, también es una solución más adecuada cuando se trabaja bajo el paradigma de programación funcional.
Una desventaja de la recursión es que suele ser menos eficiente, es decir, computacionalmente más costosa que la solución iterativa, especialmente bajo el paradigma de programación imperativa procedimental estructurada (bajo dicho paradigma, siempre que sea posible, se debe optar por una solución iterativa para poder lograr un menor tiempo de procesamiento y un menor uso de memoria).
# Ejemplo de una función recursiva
def elevado_a_un_numero_entero_positivo_V2(base, exponente_natural):
if exponente_natural>=1 and isinstance(exponente_natural,int):
if exponente_natural == 1:
resultado = base
else:
resultado = base*elevado_a_un_numero_entero_positivo_V2(base, exponente_natural-1)
return resultado
else:
raise Exception('exponente_natural debe ser de tipo entero y mayor o igual a 1: ' + str(exponente_natural))
# La función en acción:
entrada1 = -1.234
entrada2 = 3
salida = elevado_a_un_numero_entero_positivo_V2(entrada1, entrada2)
print(salida)
# Para comparar:
print(entrada1**entrada2)
Los objetos de primera clase se manejan uniformemente en todo momento. Pueden almacenarse en estructuras de datos, pasarse como argumentos o usarse en estructuras de control. Se dice que un lenguaje de programación admite funciones de primera clase si trata las funciones como objetos de primera clase.
Toda función que dentro de sus parámetros tienen al menos una función o que devuelven una función (en ese "o" está incluido "o ambas") es y se denomina una función de orden superior.
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
def greet(func):
# almacena la función en una variable
greeting = func("Hola, soy creada por una función pasada como argumento.")
print(greeting)
greet(shout)
greet(whisper)
La siguiente función aplico_funcion_a_valor()
es una función de orden superior que aceptará como primer parámetro a una función que tenga un solo parámetro obligatorio.
def aplico_funcion_a_valor(f, x):
return f(x)
def al_cuadrado(x):
return x*x
def mas_uno(x):
return x+1
def por_cinco(x):
return 5*x
def saludo(x):
return "Hola " + str(x)
variable = 10
print("aplico_funcion_a_valor(al_cuadrado, 10):\t", aplico_funcion_a_valor(al_cuadrado, variable))
print("aplico_funcion_a_valor(por_cinco, 10):\t\t", aplico_funcion_a_valor(por_cinco, variable))
print("aplico_funcion_a_valor(mas_uno, 10):\t\t", aplico_funcion_a_valor(mas_uno, variable))
print('aplico_funcion_a_valor(saludo, "a todos"):\t', aplico_funcion_a_valor(saludo, "a todos"))
print("aplico_funcion_a_valor(saludo, 10):\t\t", aplico_funcion_a_valor(saludo, variable))
Las funciones de orden superior permiten implementar soluciones computacionales que de cierta manera están más cerca del paradigma de programación funcional. Sin embargo, también pueden llegar a ser muy útiles bajo el paradigma imperativo procedimental estructurado o el orientado a objetos.
devuelve_funcion
?def devuelve_funcion(f, eps_0 = 1e-12):
def dnumf(x, eps = eps_0):
xpos = abs(x)
h = eps**0.5 if xpos <= 1 else (eps**0.5) * xpos
xph = x + h
dx = xph - x
y = (f(xph) - f(x)) / dx
return y
return dnumf
devuelve_funcion
en acción
devuelve_funcion
está funcionando correctamente? ¿se le ocurre algún código que le permita hacer una comparación de resultados?