Python para Ciencias de la Tierra

Capítulo 2: Conceptos Básicos de Programación en Python (Pt. 6)

Damian
13 min readMar 29, 2022

--

Índice del curso
<<
Capítulo 2: Conceptos Básicos de Programación (Pt. 5)

2.6 Funciones

Hasta este punto ya has trabajado con funciones internas de python como con funciones incluidas en paqueterías que posee python: len, max, sum, range, type, median, randint, random, sin, … Y has identificado parte de su importancia, pues algunos ejercicios te han permitido ver el código que te ahorran estas funciones cada que deseas hacer esa misma tarea. Pero también permiten estructurar y optimizar el programa conforme crezca. Como los ciclos, optimizan el código.

Pues bien, además de las muchas funciones propias que tiene python, puedes hacer las tuyas de acuerdo a tus necesidades. Ya es tiempo.

Definamos una función como un bloque de código que sólo se ejecuta cuando es llamado. Se distinguen por tener entre paréntesis al menos un argumento, también llamado parámetro. Al ejecutar el código la función toma el parámetro de entrada y regresa un valor.

Al ser un bloque, el código tiene una sintaxis similar a las estructuras de control, como una sentencia que declara el inicio. Se define con la palabra clave def. Tiene la sintaxis def nombre_funcion(parametros):

De nuevo se termina la línea con dos puntos el bloque de código debe tener sangría. Al finalizar la sangría se termina el bloque y con ello la función. El último renglón suele tener la línea return variable_resultado donde return es otra palabra reservada e indica la o las variables que deberán ‘recibirse’ en el código general: variable_receptora = funcion(parametro).

Las funciones por convención se definen al inicio del programa, pero siempre antes que el código que la utiliza.

Variables locales y globales
Se distinguen dos tipos de variables. Las locales son las propias de una función, aquéllas que le permiten realizar sus tareas, pero no interactúan con otras variables fuera de la función. En cambio las globales son aquéllas que pertenecen a todo el código, y pueden o no interactuar con las variables de una función. Si bien puede haber variables globales y locales que tengan el mismo nombre, es recomendable nombrarlas diferente. Y según sea el caso sólo las variables locales de entrada y salida de una función pueden nombrarse igual que las variables globales que las recibe, para mayor claridad.

Puedes abrir el cuaderno correspondiente a la sesión 7 en jupyter.

Parámetros y Argumentos

Formalmente se distingue lo siguiente: los argumentos son las variables globales que ingresan a una función, y los parámetros son esas variables convertidas en locales al ingresar en la función. Por ejemplo:

flotante = 1.618033988
n = 3

redondeado = round(flotante, n)
print(redondeado)

>> 1.618

La función round(flotante, ndígitos) requiere de dos parámetros: el número flotante y la cantidad de dígitos a los que queremos redondear. Pero, los argumentos que se le introdujeron fueron las variables flotante y n. ¿Por qué la distinción? Aunque parezca carente de importancia, es necesario remarcar que los argumentos son evaluados antes de convertirse en parámetros. Es decir, puedes incluso meter operaciones como argumentos, y el parámetro tomará el valor del resultado:

redondeado = round(flotante**0.5, (n-2)*5)
print(redondeado)

>> 1.27202

Definiendo funciones

Podemos clasificar a las funciones definidas por el usuario en dos grupos. Las que primero trataremos son un tipo de funciones que llamaremos de salida. El otro conjunto de funciones las podemos llamar accionables. En realidad creo que no hay un tratado formal para clasificar así a las funciones, pero es común que así se distingan en diversas fuentes.

Las funciones de salida se caracterizan por generar al menos una variable de salida, la cual es recibida por una variable global para ser utilizada más adelante en el código. El bloque que las define termina con la sentencia return, indicando la o las variables de regreso.

Se pueden hacer funciones básicas incorporando en ellas operaciones básicas. Por ejemplo, una función que calcule el área de un triángulo dadas la base b y la altura h. Recuerda, el primer renglón define y nombra a la función y a los parámetros.

def a_triangulo(b, h):
area = b*h/2
return area

Se ejecuta la celda para que la función pueda utilizarse. Si no se muestra un error, entonces la función está bien definida.

La variable area debe ser ‘recibida’ por una variable global. Por ejemplo, para un triángulo con base = 5 y altura = 3, el resultado es introducido a la variable global area_tri:

area_tri = a_triangulo(5, 3)
print(area_tri)

>> 7.5

Y así podemos llamar cuantas veces sea necesario a la función.

area_tri = a_triangulo(28, 13)
print(area_tri)

>> 182.0

Las funciones pueden ser tan grandes como sea necesario y pueden recibir básicamente cualquier tipo de valor, desde numérico como cadenas de texto y estructuras como listas y tuplas. Los valores que regrese pueden ser tan variados como los que recibe como parámetros.

Un pequeño anuncio: Puedes descargar el cuaderno de esta sesión, los archivos usados en ejemplos y los ejercicios resueltos suscribiéndote a mi patreon: https://www.patreon.com/ciencias_tierra.

Ejemplo 2.7 Calcular el perímetro de un polígono en el plano, donde una función sea la encargada de calcular la distancia que hay entre un punto y otro redondeado a tres cifras significativas. Las coordenadas (x,y) de cada vértice están dadas por la lista de tuplas [(3,4), (2,8), (4,5), (7,6)].

Conocemos la ecuación que permite calcular esta distancia, la cual sólo requiere cuatro valores. Estos valores bien pueden ser los parámetros de la función.

def dist2d(x1, y1, x2, y2):
d = ((x2-x1)**2+(y2-y1)**2)**0.5
redondeo = round(d, 3)
return redondeo

Al ejecutar no marca error, por lo que está bien nuestra función. Podemos probar con el primer par de coordenadas.

x1, y1 = (3,4)
x2, y2 = (2,8)
d = dist2d(x1, y1, x2, y2)
print(d)

>> 4.123

Y podemos continuar con el otro par de coordenadas.

x1, y1 = (2,8)
x2, y2 = (4,5)
d2 = dist2d(x1, y1, x2, y2)
print(d2)

>> 3.606

x1, y1 = (4,5)
x2, y2 = (7,6)
d3 = dist2d(x1, y1, x2, y2)
print(d3)

>> 3.162

La última distancia es la que conecta el último punto con el primero de la lista.

x1, y1 = (7,6)
x2, y2 = (3,4)
d4 = dist2d(x1, y1, x2, y2)
print(d4)

>> 4.472

Y para terminar, se suman las distancias.

perimetro = d + d2 + d3 + d4
print('El perímetro es:', perimetro,'unidades.')

>> El perímetro es: 15.363 unidades

Y hemos terminado, ¿pero no se puede mejorar el código? Lo hicimos de forma un poco rudimentaria, pero si observas, justo se aprecia que se repite la estructura en cada celda. Este patrón nos dice que podemos hacer todo dentro de un ciclo.

Nota que sólo hay que cambiar las coordenadas según los índices i e i+1 en cada iteración. Definamos la lista y hagamos el ciclo for, en el cual se tiene que sumar cada distancia que se obtenga.

puntos = [(3,4), (2,8), (4,5), (7,6)]

i = -1
perimetro = 0
for punto in puntos:

x1, y1 = puntos[i]
x2, y2 = punto
i = i + 1

d = dist2d(x1, y1, x2, y2)
perimetro = perimetro + d

print('El perímetro es:', perimetro,'unidades.')

>> El perímetro es: 15.363 unidades.

Y obtenemos el mismo resultado en un código más elegante y con menos líneas.

Observaciones: la variable i es el índice de la lista, empieza en -1 porque es más fácil tomar desde la primera distancia el último punto de la lista, e incrementar el índice sin temor de salirnos del rango de la lista. Los saltos de líneas son agregados para darle una mejor estética al código.

Fin del ejemplo 2.7

Ejemplo 2.8 A partir del código obtenido en el ejemplo 2.7, hacer una función que reciba como argumento una lista de coordenadas en tuplas y obtenga el perímetro del polígono que se forma al unir tales puntos.
Probar con las listas
[(5,8), (6,9), (8,11), (6,5), (13,11), (9,7)]
[(60,67), (50,52), (66,59), (54,45), (42,53), (64,56), (24,39), (57,48)]

Como bien lo indica el problema, se debe recibir de entrada una lista, y de salida el perímetro. La función contendrá todo el código desde la definición del índice hasta la obtención del perímetro dentro del ciclo for.

def perim(coordenadas):

i = -1
perimetro = 0
for punto in coordenadas:

x1, y1 = coordenadas[i]
x2, y2 = punto
i = i + 1

d = dist2d(x1, y1, x2, y2)
perimetro = perimetro + d

return perimetro

Puntos importantes: una función puede llamar a otra función, una función no debe llamarse como una variable que ya exista en el código, y una función puede recibir una lista como argumento.

Primero, para evitar falsas expectativas, probemos nuestra nueva función con la lista puntos, de la cual sabemos muy bien qué resultado debemos obtener.

perimetro = perim(puntos)
print('El perímetro es:', perimetro,'unidades.')

>> El perímetro es: 15.363 unidades.

Y en efecto, funciona bien nuestra nueva función. Ahora podemos obtener el perímetro de las listas solicitadas.

puntos_1 = [(5,8), (6,9), (8,11), (6,5), (13,11), (9,7)]

perimetro = perim(puntos_1)
print('El perímetro es:', perimetro,'unidades.')

>> El perímetro es: 29.567 unidades.

puntos_2 = [(60,67), (50,52), (66,59), (54,45), (42,53), (64,56), 
(24,39), (57,48)]
perimetro = perim(puntos_2)
print('El perímetro es:', perimetro,'unidades.')

>> El perímetro es: 187.46 unidades.

Fin del ejemplo 2.8

Ahora veamos la otra clase de funciones, las accionables que no requieren regresar un valor, es decir, no necesitan el return. Se caracterizan por sólo hacer una tarea interna, cuyo resultado no se utilice en el resto del código.

En esencia son utilizadas para imprimir una cadena. Pueden hacer operaciones, pero el resultado es una cadena que sólo debe imprimirse. Por ejemplo, la función de distancia que hemos definido puede ser convertida a este tipo de función de la siguiente manera.

def dist(x1, y1, x2, y2):
d = ((x2-x1)**2+(y2-y1)**2)**0.5
redondeo = round(d, 3)
print('La distancia es', redondeo,'unidades.')

No hay return, y utilizando las primeras coordenadas del ejemplo 2.7.

x1, y1 = (3,4)
x2, y2 = (2,8)
dist(x1, y1, x2, y2)

>> La distancia es 4.123 unidades

Ya no se tuvo que igualar a otra variable. De hecho, si lo intentas, no se guardará nada y seguirá imprimiendo lo mismo.

x = dist(x1, y1, x2, y2)

>> La distancia es 4.123 unidades

Si imprimes x con la esperanza que algo se haya guardado en ella, notarás que es una variable vacía.

Tipos de parámetros

Además de los parámetros que has trabajado en los ejemplos de este capítulo, los cuales podemos llamar indispensables, existen los parámetros con valores predeterminados, es decir, un parámetro con un valor fijo. Este valor es aquél que sabemos es más común que se utilice. Pero también es un valor que puede ser modificado al momento de llamar a la función. Por ejemplo, hagamos una función que calcule el volumen de una esfera, donde pi es el parámetro con valor predeterminado.

def esfera(r, pi = 3.1416):
volumen = (4/3)*pi*r**3
return volumen

Primero utilicemos la función con el parámetro por defecto:

r = 7
volumen = esfera(r)
print(volumen)

>> 1436.7584

Ahora, si queremos un valor de pi con mayor precisión, basta con definir ese parámetro por separado:

r = 7
pi = 3.141592
volumen = esfera(r,pi)
print(volumen)

>> 1436.7547413333334

También los parámetros pueden ser opcionales. Así es, un parámetro puede o no ser utilizado en la función, según los argumentos que se le asignen. Por ejemplo, hagamos una función que resuelva ecuaciones lineales de la forma ax + b = 0 y cuadráticas de la forma ax²+ bx + c = 0.

El parámetro opcional se define con una asignación de una cadena vacía en la definición de la función:

def ec1ro2do(a, b, c = ''):
if c:
x1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
x2 = (-b - (b**2-4*a*c)**0.5)/(2*a)

return [x1,x2]

else:
x = -b/a

return [x]

El parámetro opcional es c porque es el tercer término, y una ecuación lineal no lo tiene. Una sentencia if es necesaria para decidir si se asignó o no dicho parámetro. Nota que hay un return por cada condición; y que el resultado devuelto está en una lista. Esto es por simplicidad al momento de ser recibida por la variable global. También se da por entendido que la función sólo admitirá ecuaciones cuadráticas con raíces reales.

Se introducen los coeficientes de las ecuaciones 2x² - 7x + 3 = 0 y 2x - 7 = 0 para probar la función.

a = 2
b = -7
c = 3

sol_lineal = ec1ro2do(a, b)
sol_cuadrada = ec1ro2do(a, b, c)

print('Solución de la ec. lineal:', sol_lineal,
'\nSoluciones de la ec. cuadrática:', sol_cuadrada)

>> Solución de la ec. lineal: [3.5]
>> Soluciones de la ec. cuadrática: [3.0, 0.5]

Parámetros especiales *args y **kwargs
Los *args son argumentos (Arguments) arbitrarios que se caracterizan por ser una o varias variables que puedes introducir a la función de forma opcional. Estos argumentos arbitrarios son incluidos automáticamente en una tupla para que sean tratados de forma independiente. Comúnmente son utilizados para agregar información extra a los parámetros obligatorios. Para entenderlo mejor, definamos una función que nos permite crear una lista con el nombre y las características de un mineral:

def mineral(nombre, *args):
caracteristicas = list()

caracteristicas.append(nombre)
for arg in args:
caracteristicas.append(arg)

print(caracteristicas)

Los argumentos arbitrarios son características que deseemos incluir o tengamos de un mineral obtenido en campo, por ejemplo.

nombre = 'Cuarzo'
mineral(nombre, 2, 'Fórmula SiO2', 'Dureza 7', 'Fractura concoidea')

>> [‘Cuarzo’, 2, ‘Fórmula SiO2’, ‘Dureza 7’, ‘Fractura concoidea’]

Nota que tanto cadenas como números pueden ser argumentos arbitrarios. En este caso el 2 puede ser el número de muestra.

No es necesario que se declaren estos argumentos con la palabra args, de hecho el comando que hace la magia es el asterisco. Y bien pudo definirse como *datos en vez de *args. Inténtalo.

Los **kwargs son parámetros arbitrarios identificados con una clave (Keyword Arguments). Al igual que los *args, los *kwargs tienen la misma utilidad, pero con la diferencia que se incluye una clave para cada argumento. Esto le permite a la función trabajar estos argumentos como un diccionario.

def mineral(**caracteristicas):
print(caracteristicas)

Ahora podemos introducir los mismos argumentos, pero en parejas de palabras clave-valor.

mineral(nombre = 'Cuarzo', muestra = 2, formula = 'SiO2', dureza = 7, 
fractura = 'concoidea')

>> {‘nombre’: ‘Cuarzo’, ‘muestra’: 2, ‘formula’: ‘SiO2’, ‘dureza’: 7, ‘fractura’: ‘concoidea’}

Como puedes ver, definir cuándo usar los *args y cuándo usar los **kwargs depende en gran medida de la estructura de datos que desees obtener o trabajar en la función.

Hasta ahora hemos definido cinco tipos de parámetros: indispensables, de valores predeterminados, opcionales, *args y **kwargs. Y antes de terminar, cabe señalar que todos estos parámetros es preferible que sean declarados e introducidos en la función en el siguiente orden:

def funcion(a, b, c = ‘ ’, d = ‘ ’, e = True, f = 3.2, *args, **kwargs)

  • a, b son parámetros obligatorios.
  • c, d son parámetros opcionales.
  • e, f son parámetros con valores predeterminados.
  • *args son los parámetros de argumentos arbitrarios.
  • **kwargs son los parámetros de argumentos arbitrarios con clave.

En hora buena, ¡haz terminado el capítulo 2, sigue así! Te veo en el siguiente capítulo donde a manera de preludio a programas mucho más grandes y complejos, se hace un recorrido por temas que te permitirán programar de forma más clara, limpia y versátil. Lo necesitarás.

Recuerda que puedes descargar el cuaderno de esta sesión, los archivos usados en los ejemplos y los ejercicios resueltos suscribiéndote a mi patreon: https://www.patreon.com/ciencias_tierra.

Ejercicios

2.21 - Sea el punto R(Rx, Ry) un punto de reflexión. Hacer una función llamada reflexion que halle el punto reflejado Q(Qx, Qy) respecto a R, es decir, girado 180°, dado un punto P(Px, Py). Los parámetros de entrada son las coordenadas Px, Py, Rx, Ry, y la salida es el par coordenado del punto reflejado Qx, Qy. Observa la ilustración.
Prueba tu función con los puntos:
P(1,1) y R(3,3). El resultado debe ser (5, 5)
P(2,-3) y R(-5,4). El resultado debe ser (-12, 11) *

Utiliza la función reflexion para obtener los puntos reflejados U, V, W, X, Y, Z respecto a R(3, -2) de: A(-2,-3), B(-2,-2), C(0,0), D(1,1), E(3,2), F(5,2).

2.22 - En el ejercicio 2.6 obtuviste el período orbital de la Tierra en días. Ahora de forma automática obtendrás todos los períodos orbitales en días de los ocho planetas. Apóyate de la siguiente tabla con los semiejes mayores orbitales dados en km.**

Haz una función que se llame ley3_kepler, la cual será llamada cada vez que se ingrese el semieje mayor según un ciclo que itere en una estructura con los datos. Recuerda la tercera ley de Kepler.

La variable a es el semieje mayor en metros, y pi es la constante 3.1415…

Los resultados redondeados a tres cifras decimales serán guardados en una columna nueva para formar un archivo .csv, el cual tendrá las tres columnas: planeta, semieje mayor (km), período orbital (días).

2.23 - El rumbo y echado son dos conceptos geológicos de suma importancia en la caracterización estructural. Como sus nombres sugieren, miden en qué dirección se orienta una estructura, y cuál es su inclinación. El echado siempre es perpendicular al rumbo.***

Existen diversas formas de escribir el rumbo y echado. Una de ellas es escrita como SE40°NW, 60°NE. El cuadrante SE indica de donde se miden los 40° a partir del eje vertical, y al frente está el echado ubicado en el cuadrante NE, con 60° de inclinación. Incluye el Rumbo, la Inclinación y el Cuadrante, por lo que se le conoce como RIC.

Otra nomenclatura es considerando el acimut del rumbo. Imaginando que la línea de echado está al frente de nosotros y nuestros brazos son el rumbo, el acimut se mide desde el norte hasta el brazo izquierdo, dando una sintaxis como 320°, 60°NE, para el mismo ejemplo del párrafo anterior. En la figura inferior derecha está marcado con rojo el ángulo. A esta forma se le conoce como acimutal.

Una tercera forma es considerando el acimut de la línea de echado. En este caso el mismo dato se escribe como 60°, 50°; donde ahora se escribe primero el echado y luego el acimut que forma la línea de echado con el norte. En la figura inferior derecha está marcado con negro el ángulo. A esta forma se le puede llamar como acimutal de echado.

El archivo rumbos_echados.csv (que puedes descargar aquí) tiene una serie de rumbos y echados medidos en campo, pero fueron escritos en tres diversas formas: RIC, Acimutal y Acimutal de echado. Haz un programa que contenga una función llamada conversion_echados, la cual convierta las formas escritas en las otras dos formas que falten. Es decir, si está en formato RIC, que convierta a Acimutal y Acimutal de echado. Recomendación: Verifica la estructura de la forma de escribir el rumbo y echado.

El programa generará un archivo csv con tres columnas: RIC, Acimutal y Acimutal de echado. En cada columna estarán todos los datos escritos como cadena, puesto que la forma de escribirlos incluye comas como letras, además de números.

--

--

Damian

Anything I want and is related to data. Learning to become a Data Professional.