Con una cuenta desbloqueas el cuestionario, el foro de dudas y el tutor con IA de esta lección, además del registro de progreso y el certificado verificable al terminar «Python para Ingeniería Espacial».
Hasta aquí has escrito código que corre de arriba abajo: variables, condicionales, listas y bucles. Funciona, pero tiene un techo bajo. En cuanto necesitas la misma fórmula en dos sitios aparece la tentación de copiar y pegar, y con ella el peor defecto que puede tener un script de misión: dos copias de la misma verdad que pueden divergir. Si mañana corriges la constante de la fórmula de espacio libre en una copia y olvidas la otra, tendrás dos respuestas distintas a la misma pregunta y ninguna forma barata de saber cuál es la buena.
Una función resuelve eso nombrando un trozo de conocimiento. Deja de ser "estas tres líneas que calculan algo" y pasa a ser fspl_db: un nombre, un contrato de entrada y un contrato de salida. A partir de ahí el conocimiento vive en un solo lugar, se corrige en un solo lugar y se prueba en un solo lugar. Todo el curso, de aquí en adelante, consiste en acumular funciones fiables hasta que el proyecto final sea poco más que llamarlas en el orden correcto.
En esta lección construimos las dos primeras piezas de esa librería de misión: la pérdida de propagación en espacio libre y el periodo orbital. Las usarás en las lecciones 7, 9 y 10 sin volver a escribirlas.
Anatomía de def
La forma mínima de una función en Python son cuatro elementos:
def periodo_orbital_min(altitud_km): # 1) cabecera: def, nombre y parámetros
"""Periodo orbital en minutos.""" # 2) docstring
a = 6371.0 + altitud_km # 3) cuerpo indentado
return a # 4) return: el valor que sale
Los cuatro merecen precisión:
La cabecera empieza por la palabra clave def, sigue con el nombre y termina con la lista de parámetros entre paréntesis y dos puntos. El nombre es documentación: periodo_orbital_min dice qué devuelve y en qué unidad, que es exactamente lo que un lector necesita saber.
La docstring es la primera cadena del cuerpo. No es un comentario: queda guardada en funcion.__doc__ y la usan el editor, help() y las herramientas de documentación.
El cuerpo es todo lo que va indentado (por convención, cuatro espacios). La indentación no es estética en Python: es sintaxis. Sacar una línea de la indentación la saca de la función.
El return entrega el valor y termina la ejecución de la función ahí mismo. Una función sin return explícito devuelve None, y ese es el origen del error de principiante más frecuente de todos: imprimir dentro de la función en vez de devolver, y luego preguntarse por qué la variable vale None.
Parámetros, argumentos y valores por defecto
Conviene separar dos palabras que se confunden a diario. El parámetro es el nombre que aparece en la definición (altitud_km); el argumento es el valor concreto que pasas al llamar (420). Los argumentos se pueden pasar por posición o por nombre, y esta segunda forma es la que hace legible una llamada con varios números:
fspl_db(400, 437) # posicional: ¿cuál era cuál?
fspl_db(distancia_km=400, frecuencia_mhz=437) # por nombre: se lee solo
Un valor por defecto se declara en la propia cabecera y convierte un parámetro en opcional. En nuestra estación la banda de trabajo habitual es UHF a 437 MHz, así que ese es el valor natural por defecto:
def fspl_db(distancia_km, frecuencia_mhz=437.0):
...
fspl_db(400) # usa 437.0 MHz
fspl_db(400, 2200.0) # sobrescribe el valor por defecto
Dos reglas que se aprenden pronto o se sufren después. Primera: los parámetros con valor por defecto van siempre después de los que no lo tienen; al revés es un SyntaxError. Segunda: nunca uses una lista o un diccionario como valor por defecto. El valor por defecto se evalúa una sola vez, al definir la función, así que def registrar(pases=[]) comparte la misma lista entre todas las llamadas y va acumulando basura. La forma correcta es def registrar(pases=None) y dentro if pases is None: pases = [].
Docstrings: la documentación que viaja con el código
Una docstring útil responde tres preguntas en pocas líneas: qué calcula, qué unidades entran y qué unidad sale. En ingeniería espacial la tercera es la crítica: la mitad de los errores caros de este dominio son errores de unidades, y la mejor defensa barata es escribirlas en el nombre del parámetro y repetirlas en la docstring.
Pieza 1 de la librería: pérdida de espacio libre
La pérdida de propagación en espacio libre (FSPL, free-space path loss) mide cuánta potencia se diluye entre dos antenas por el simple hecho de que la onda se expande en una esfera. En la forma práctica que usa el segmento terreno, con la distancia en kilómetros y la frecuencia en megahercios:
Lfs[dB]=32.45+20log10(d[km])+20log10(f[MHz])
El 32.45 no es magia: es la constante que absorbe el cambio de unidades desde la forma canónica Lfs=(4πd/λ)2. Lo importante para nosotros es la estructura: duplicar la distancia y duplicar la frecuencia cuestan lo mismo, exactamente 20log10(2)≈6.02 dB cada una.
Ejemplo resuelto: la ISS en 437 MHz
Un pase de la ISS por el cénit desde una estación al nivel del mar: la distancia mínima es la altitud, d=420 km, y la frecuencia del transpondedor de radioaficionado es f=437 MHz.
Lfs=32.45+20log10(420)+20log10(437)
Término a término: log10(420)=2.6232, luego 20×2.6232=52.46 dB. Y log10(437)=2.6405, luego 20×2.6405=52.81 dB. Sumando:
Lfs=32.45+52.46+52.81=137.72dB
Si esa misma estación transmite con una PIRE de 30 dBm y la antena receptora aporta 12 dB de ganancia, la potencia que llega vale 30+12−137.72=−95.72 dBm. Ese número, comparado con la sensibilidad del receptor, decide si el pase se decodifica o se pierde: el enlace es una resta de decibelios, y todo lo demás es contabilidad.
Pieza 2 de la librería: periodo orbital
La tercera ley de Kepler, en la forma que usamos para órbitas terrestres, relaciona el periodo con el semieje mayor:
T=2πμa3
donde a es el semieje mayor en kilómetros y μ=398600.4418km3/s2 es el parámetro gravitacional estándar de la Tierra. Para una órbita circular, a=RT+h con RT=6371 km (radio medio terrestre) y h la altitud.
Ejemplo resuelto: ¿de verdad la ISS da una vuelta en 92.8 minutos?
Con h=420 km, el semieje mayor es a=6371+420=6791 km. Elevamos al cubo y dividimos:
La raíz cuadrada da 886.40 s, y multiplicando por 2π:
T=2π×886.40=5569.4s=92.82min
Los 92.82 minutos calculados coinciden con los ~92.8 minutos que reporta cualquier fuente de seguimiento de la ISS. Ese es el momento en que una función deja de ser un ejercicio y se convierte en una herramienta: has reproducido un dato del mundo real desde primeros principios, y ahora puedes preguntarle cosas nuevas. Con h=550 km (la altitud típica de una constelación de banda ancha en órbita baja) la misma función devuelve 95.50 min; con h=35786 km devuelve 1435.7 min, es decir, un día sidéreo: la órbita geoestacionaria.
Función
Entrada
Salida
Caso de prueba
fspl_db(d, f)
km, MHz
dB
fspl_db(420, 437) = 137.72
periodo_orbital_min(h)
km
min
periodo_orbital_min(420) = 92.82
potencia_recibida_dbm(h, f)
km, MHz
dBm
potencia_recibida_dbm(420) = −95.72
Composición: funciones que llaman a funciones
La ganancia real llega cuando una función usa a otra. potencia_recibida_dbm no vuelve a escribir la fórmula de FSPL: la llama. Eso significa que si mañana refinas fspl_db para incluir la pérdida atmosférica, el presupuesto de enlace mejora solo, sin tocar una línea del código que lo consume.
Esa es la forma de todo el resto del curso: piezas pequeñas, cada una comprobable por separado, encadenadas por sus contratos. Una regla práctica para saber si una función es demasiado grande: si su docstring necesita la palabra "y" tres veces, son tres funciones.
Probar con casos conocidos
Una función sin caso de prueba es una promesa sin testigo. La forma más barata de probar en este punto del curso es el caso conocido: un valor de entrada cuyo resultado ya conoces por otra vía. La ISS a 420 km debe dar ~92.8 min; la órbita geoestacionaria debe dar ~1436 min. Si tu función acierta esos dos puntos, muy probablemente está bien en todo el rango intermedio.
Ejecuta la librería completa y comprueba los casos conocidos tú mismo:
Playground · python
Tres experimentos que vale la pena hacer ahí mismo: cambia la altitud a 35786 km y comprueba que salen ~1436 minutos; llama a fspl_db(420) sin segundo argumento y verifica que usa 437 MHz; y añade print(periodo_orbital_min()) sin argumento para ver el TypeError que Python lanza cuando falta un parámetro obligatorio — ese error concreto lo diseccionaremos en la lección 8.
1 de 7
Comprueba el error de principiante más común:
¿Qué imprime print(x) después de ejecutar def f(a): print(a * 2) y luego x = f(21)?
Ya tienes conocimiento encapsulado. En la lección 7 lo alimentaremos con datos reales: aprenderemos a extraer el estado orbital de un satélite del formato en que realmente se publica, el TLE.