En el lenguaje Python se pueden construir funciones para ser usadas reiteradas veces. Las funciones en Python tienen que estar definidas y por lo tanto cargadas antes de ser usadas, recordar que Python no es un lenguaje compilado. Por eso es muy común que todo código comience con la definición de varias funciones o con una carga de bibliotecas que contienen esas funciones.
Cómo en otros lenguajes la idea de función es escribir una sóla vez, cualquier estructura que se repita muchas veces en un cálculo. Pero esa idea a ido evolucionando a convertirse en un código que desarrolla una tarea en particular y que puede guardarse en forma separada y luego reutilizarse en otros programas. La idea entonces es que en el caso de resolver un problema en particular, guardar el código de las funciones utilizadas y que esta solución de software quede en disponibilidad para que uno u otros puedan acceder a ese código y reutilizarlo.
El éxito de Python se debe entonces a la existencia de una cantidad de bibliotecas especializadas en una cuestión particular que contienen funciones muy útiles sobre ese tema.
Para definir una función se usa la orden def seguido de su nombre y entre paréntesis las variables o los datos que se ingresan a la función. A estos últimos también se los llama argumentos. Al final del paréntesis tiene que ir el símbolo : para indicar al intérprete Python que las líneas siguientes con una sangría similar (un bloque) pertenecen a la función.
La sangría, al igual que en el caso de las estructuras de control, determina que órdenes son de esa función en particular, es decir determina el bloque de órdenes propias de la función. Como dentro de una función puede haber estructuras de control internas, estas tendrán más espacios de sangría que las que se indican como sentencias que son parte del bloque. Pero eso no es un problema, se siguen considerando dentro del bloque que forma la función.
Las funciónes suelen terminar con la orden return, que puede o no llevar los valores resultantes del cálculo, los cuales serán enviados al lugar donde la función fue llamada. La orden return es optativa, puede incluso no estar.
Veamos un ejemplo de una función que eleva al cubo valor que se ingresa:
def cubo(x):
print(x**3)
return
cubo(5)
# pero podría no haber puesto el return
def cubo(x):
print(x**3)
cubo(5)
Notar que la función se dio por finalizada porque terminó la sangría, y no se usó la orden return. Este ejemplo demuestra la importancia de la sangría en Python para determinar inicio y fin de bloques de código.
Pero la pregunta es: ¿Devolvió la función un resultado?
def func_cubo(x):
"""
Retorno el cubo de un número.
A este comentario se la llama docstring y puede accederse desde el
notebook como una ayuda para recordar detalles del uso de la función
como por ejemplo que variables ingresan y en que orden.
Note las 3 comillas que se usan para indicarlo y cerrarlo
"""
return(x**3) # <-- El return puede hacer cuentas!!!
a = func_cubo(3)
print(a)
print("")
# pido el "help" para recordar detalles de la función y me
# devuelve el texto del docstring
help(func_cubo)
# Otra manera hacer shift tab dentro del parentesis,
# aunque en algunas computadoras no funciona
# ¿Lo hace en la tuya?
func_cubo(3)
Como en otros lenguajes puedo poner en los argumentos de la función variables o números
a=3
print(a)
print(func_cubo(a))
Estos argumentos que hemos usado hasta ahora son requeridos y son obligatorios, ya que así están en la definición de la función. Debe llamarse a la función con estos argumentos cargados con valores numéricos o variables que los contengan. La falta de uno de estos argumentos en una llamada genera un error. Debido a que su orden importa se suelen llamar argumentos de posición o positional arguments.
Pero existen otros argumentos llamados de teclado o keyword arguments donde se indica un valor default (default = predeterminado) por si no se lo indica en el momento que se llama la función.
def func_rara(x, y, z, a=0, b=1):
"""
Esta función tiene 5 argumentos, pero dos de ellos
tienen valores por default (entonces no son obligatorios)
"""
return a + b * (x**2 + y**2 + z**2)**0.5
D = func_rara(3, 4, 5)
print(D)
E = func_rara(3, 4, 5, 10, 100)
print(E)
Veamos un ejemplo que es muy bueno para mostrar el uso de este tipo de argumentos.
Supongamos que queremos hacer un programa que pase medidas hechas en el sistema imperial, es decir pies y pulgadas a centímetros. la medida puede estar en pies, o en pulgadas o en los dos sistemas.
Recorar que 1 pulgada = 2.54 cm y 1 Pie = 12 pulgadas
def cm(pies=0, pulgadas=0):
pulgadas_a_cm = pulgadas * 2.54
pies_a_cm = pies * 12 * 2.54
return pulgadas_a_cm + pies_a_cm
En esta función pies y pulgadas tienen ya prefijado valores de inicio, es decir un default que es en ambos casos un "0".
Veamos como puede correr esta función y aprovechar los valores default de las argumentos de teclado.
# Sólo tengo un valor en pies y nada en pulgadas
a = cm(pies=20)
print("20 pies son: ",a)
# Sólo tengo un valor en pulgadas y nada en pies
b = cm(pulgadas=22.5)
print("22 pulgadas son: ",b)
#Tengo parte de la medida en pies y otra parte en pulgadas
c = cm(pies=5, pulgadas=3.5)
print("5 pies y 3.5 pulgadas son: ",c)
# E incluso puedo invertir los argumentos y lo puedo hacer porque
# tiene nombres las variables
d = cm(pulgadas=3.5,pies=5)
print("3.5 pulgadas y 5 pies on: ",d)
Puedo mezclar argumentos de ambos tipos, pero los de tipo "keyword" tienen que ir al final.
Pero estos últimos no tiene un orden si los escribo como variable=dato
También puedo asignar la función a una variable con cierto nombre y utilizar ese nombre para el cálculo. Veamos:
X = func_cubo
print(X(5))
Aunque lo que sucedió a que el objeto que contiene la función tiene ahora más de un nombre. Es decir, no se copió.
Veamos el caso de una facilidad que tiene Python de hacer variable la cantidad de argumentos tanto los posicionales como los argumentos "Keywords" que envio a una función.
Si por ejemplo realizo un programa que utiliza una función que calcula el promedio por ejemplo para 3 números, lo haría de la siguente manera: Es decir le envio 3 argumentos
def prom(x,y,z):
prom = (x+y+z)/3
return prom
print(prom(2,3,4))
De esta manera funcionaría, pero no me serviría para promediar más de 3 números o menos de 2. Existe una manera de universalizar para que el programa me sirva para una cantidad no determina de números. Es decir, que yo pueda mandar una cantidad de arguemtos posicionales arbitraria.
Para realizar esta tarea tengo que indicar que la cantidad de argumentos no se conoce, sólo se sabrá al momento de la llamada a la función. Para eso uso *args como argumento al momento de la definción de la función.
Note el uso del símbolo * para indicar que se trata de una cantidad aleatoria de argumentos que se guardarán en la tupla con nombre args
Por ejemplo:
# función con una cantidad variable de argumentos
def prom(*args):
sum = 0
for i in args:
# calculo la suma total
sum = sum + i
# calculo el promedio
promedio = sum / len(args) # en vez len() podría haber puesto un contador,
# pero usar len() es más rápido.
print('Promedio =', promedio)
prom(156, 23, 23)
print()
prom(2,3,4,5,6)
Si quisiera hacer la misma operación pero con una cantidad de argumentos de teclado, tendría que usar como argumento de la función, por ejemplo, el nombre **kwargs. El doble símbolo ** indica que se trata de argumentos de teclado. Los argumentos pasarán entonces a la función la cual los verá internamente como un diccionario. Por lo cual, los datos serán accesibles a través de un sistema llave-dato. Es decir para una llave determinada corresponde cierto dato.
Ejemplo:
def porcentaje(**kwargs):
sum = 0
for sub in kwargs:
# get argument name
sub_nombre = sub
# get argument value
sub_nota = kwargs[sub]
print(sub_nombre, "=", sub_nota)
# envio varios arguemtnos de teclado
porcentaje(matemática=56, física=61, química=73)
print()
Por lo cual debemos considerar que hay 4 tipos de argumentos en la llamada de una función en Python:
Veamos ejemplos:
def add(a,b=5,c=10):
return (a+b+c)
Y podría hacer:
Por lo cual ahora los argumentos son posicionales, es decir identifico cuál es cuál por su posición.
O podría hacer lo siguiente:
print(add(b=24,c=5,a=12))
Con lo cual ahora son argumentos de teclado, donde cada variable es indicada por nombre y valor
Las funciones pueden devolver más de un resultado. En cierta manera violando la idea que uno ha aprendido en análisis matemático. Cuando son varios los resultados este es retornado en forma de tupla.
Por ejemplo:
def cuadrado_cubo(x):
x2 = x*x
x3 = x2*x
return(x2,x3)
print("Para x=3",cuadrado_cubo(3))
print("Para x=4",cuadrado_cubo(4))
print("Para x=5",cuadrado_cubo(5))
Note que los resultados están englobados por ( ) indicando que es una tupla.
La orden dir sirve para listar las funciones cargadas en la memoria. En esa lista habrá funciones del sistema y las que agregué en las celdas anteriores.
print(dir())
Puede verse que las funciones cubo, func_cubo, func_rara y X que creamos en este notebook hasta ahora, están como funciones en el sistema Python que estamos corriendo.
También podría devolver más de un valor. La construcción sería de esta forma, ya que la orden return debería indicar todos los valores a devolver
def algo():
...
return(a,b)
x,y=algo()
La función lambda es para crear funciones de una línea, y son equivalentes en todos sus aspectos a la función sentencia de Fortran.
En Python se las considera como funciones sin nombre, pero que tienen mucha utilidad en algunos casos.
por ejemplo, si defino
lambda x: x**2
El sistema me dice que es una función, pero no tiene nombre. Para poder usarla tengo que asginarla a una variable
f = lambda x: x**2
# si la llamo con un argumento
print(f(2))
Funciona!!!
Y se la puede construir con muchas entradas de datos, no solo una. Por ejemplo:
J = lambda x, y, z: (x**2 + y**2 + z**2)**0.5
J(1,2,3)
Entonces la forma general para crear una expresión Lambda sería:
lambda variables: expresión matematica en función de las variables que se ingresaron
Y el caso más extremo sería ni siquiera darle una variable
f1 = lambda: "¿Para qué sirvo?"
print(f1())
La funciones en Python son recursivas, veamos el ejemplo del factorial
$n!=n(n-1)!=n(n-1)(n-2)!=n(n-1)(n-2)....1$
def fact(n):
if n <= 0:
return 1
return n*fact(n-1)
print(fact(5))
print(fact(20))
print(fact(10))
def f1(x):
"""
Este es un ejemplo de la función que devuelve: x**2 - parameter: x
"""
return x**2
Lo voy a crear con la celda siguente, ya que al correr la orden: %%writefile ex1.py graba la celda a un archivo
%%writefile ex1.py
# Lo que está en esta celda se salvará a un archivo con nombre "ex1.py"
def f1(x):
"""
Este es un ejemplo de la función que devuelve: x**2
y parámetro es x
"""
return x**2
import ex1
# Esto importa un archivo que se llama ex1.py del directorio
# o de los directorios que indique poniendo el camino.
# El hecho de que muchos usuarios escriben y comparten funciones
# es lo que impulsó a Python en el área científica.
print(ex1.f1(4))
from ex1 import f1
print(f1(3))
#Cargo de ex1 SÓLO la función f1
from ex1 import * # Aquí cargo todas las funciones. Lo cual es una
# mala idea se cargan todas las funciones con su
# nombre y pueden sobreescribir otros nombres
# que estaba usando para las funciones
print(f1(4))
import ex1 as tt
print(tt.f1(10))
# Ahora cargo todas las funciones de ex1, pero las tengo
# que llamar con el prefijo tt en el nombre, es decir la
# función f1() es ahora tt.f1
No todas las funciones de Python que lo han hecho tan famoso y requerido están en el lenguaje, sino que muchas de las más usadas son externas a Python. Para usar estas funciones hay que "importarlas". Por lo cual hay que indicarle a nuestro programa que las cargue y de esta manera adquiera nuevas capacidades.
Incluso esto es válido para el caso de que se necesite usar funciones matemáticas trascedentes, ya que el Python en su versión original no las trae cargadas. Por ejemplo, Python no tiene funciones intrínsecas de matemática como el Fortran.
Por ejemplo:
calcular: print(sin(3.)) daría error, la funciones trigonoméricas no están construidas por default dentro de Python.
#print(sin(3.)) # si se eliminina el # y se corre la celda da error.
Pero en cambio si importo la funciones matemáticas que están en la bibliotecas math ya lo puedo usar. Lo haríamos de la siguiente manera:
import math
print(math.sin(3.))
help(math.sin)
print(math.pi)