# Programación modular

## <mark style="color:blue;">1. ¿Qué es la programación modular?</mark>

La programación modular se refiere al proceso de dividir una tarea de programación grande y difícil de manejar en subtareas o módulos separados, más pequeños y manejables. Los módulos individuales se irán uniendo posteriormente para crear un programa más grande.

La modularización del código en una aplicación grande tiene varias ventajas:

* **Simplicidad**: En lugar de centrarse en todo el problema, un módulo suele centrarse en una parte relativamente pequeña del problema. Si trabajas en un solo módulo, tendrás un dominio del problema más pequeño en el que centrarte. Esto hace que el desarrollo sea más fácil y menos propenso a errores.
* **Mantenimiento**: Los módulos se diseñan normalmente de manera que imponen límites lógicos entre los diferentes dominios del problema. Si los módulos se escriben de forma que se minimice la interdependencia, se reduce la probabilidad de que las modificaciones en un solo módulo tengan un impacto en otras partes del programa.
* **Reutilización**: La funcionalidad definida en un solo módulo puede ser fácilmente reutilizada (a través de una interfaz bien definida) por otras partes de la aplicación. Esto elimina la necesidad de duplicar el código.
* **Alcance**: Los módulos suelen definen un namespace (namespace global) separado, lo que ayuda a evitar colisiones entre nombres en diferentes áreas de un programa.

**Las funciones, las clases, los módulos y los paquetes son construcciones de Python que promueven la modularización del código.**

## <mark style="color:blue;">2. ¿Qué es un módulo?</mark>

Hay tres formas diferentes de definir un módulo en Python:

* Un módulo puede estar escrito en Python.
* Un módulo puede estar escrito en C y cargado dinámicamente en tiempo de ejecución.
* Un módulo puede estar incorporado por defecto en el intérprete.

En los tres casos se accede al contenido de un módulo de la misma manera: con la sentencia `import`.

El tipo de módulo más importante y utilizado un 99% de las veces es el escrito en Python, que será en el que nos centraremos en este tema.

Un módulo escrito en Python no es más que un archivo que contiene código en Python y una extensión `.py`. Esto es todo. No se requiere ningún tipo de sintaxis especial para definirlos.

```python
# Este código vamos a ejecutarlo en PyCharm
cadena_texto = "Hola mundo"
lista = ["azul", "verde", "rojo"]

def funcion(arg):
    print(arg)
    
class Coche:
    pass
```

```python
>>> import modulo
>>> print(modulo.cadena_texto)
>>> print(modulo.lista)
>>> modulo.funcion("Hola mundo")
>>> bmw = modulo.Coche()
```

Cuando el intérprete ejecuta la sentencia `import`, busca el `mimodulo.py` en una lista de directorios que obtiene a partir de las siguientes fuentes:

* El directorio desde el que se ejecutó el script o el directorio actual si el intérprete se está ejecutando de forma interactiva
* La lista de directorios contenida en la variable de entorno `PYTHONPATH`, si está configurada.
* Una lista de directorios dependiente de la instalación configurada en el momento de instalar Python

La ruta de búsqueda resultante es accesible en la variable de Python `sys.path`, que se obtiene de un módulo llamado sys:

```python
import sys
sys.path
```

Por lo tanto, para asegurarnos de que el módulo es encontrado, se requiere que hagamos alguna de las siguientes acciones:

* **Guardar el `modulo.py` en el directorio donde se encuentra el script o en el directorio actual si estamos utilizando el interprete interactivo**
* Modificar la variable de entorno `PYTHONPATH` para que contenga el directorio donde se encuentra el módulo, o bien, guardar el módulo en uno de los directorios ya contenidos en esta variable
* Guardar el módulo en uno de los directorios dependientes de la instalación, a los que puede o no tener acceso de escritura, dependiendo del sistema operativo

## <mark style="color:blue;">3. Sentencia</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`import`</mark>

La sentencia `import` nos permite incluir un módulo dentro de nuestro programa actual y utilizar las estructuras que estén definidas en él.

La manera más sencilla de utilizar la sentencia `import` es la que se mostraba en el apartado anterior:

```python
import <nombre_modulo>
```

Debemos tener en cuenta que utilizando esta sintaxis el contenido del módulo no es directamente accesible por nuestro programa. Esta sentencia incorpora el nombre del módulo en el namespace global del programa que lo esta importando, pero no incorpora el namespace del módulo dentro del actual.

Por esta razón, los objetos del módulo sólo son accesibles cuando se les antepone `<nombre_módulo>` al nombre del objeto.

```python
>>> import modulo
>>> print(modulo.cadena_texto)
```

Para evitar tener que invocar todos los objetos definidos en el módulo utilizando el nombre del módulo, Python nos proporciona una sintaxis alternativa:

```python
from <nombre_modulo> import <nombre(s)>
```

Esto nos permite importar cualquier objeto del módulo en el namespace del programa actual.

```python
>>> from modulo import funcion
>>> funcion()
```

Podemos importar varios objetos separándolos entre `,`

```python
>>> from modulo import funcion, Coche
>>> funcion()
>>> mercedes = Coche()
```

Incluso podemos llegar a importar todos los objetos que se encuentre en el módulo utilizando el carácter `*`

```python
>>> from modulo.py import *
```

## <mark style="color:blue;">4. Alias</mark>

Una de las cosas interesantes que nos proporciona Python a nivel sintáctico cuando importamos elementos de un módulo es asignarles un alias. De esta manera, los objetos se almacenan en el namespace del programa con otro diferente al original. Para ello podemos utilizar la siguiente sintaxis:

```python
import <nombre_modulo> as <nombre_alias>
from <nombre_modulo> import <nombre_objeto> as <nombre_alias>
```

```python
>>> import modulo as mimodulo
>>> mimodulo.function()
>>> from modulo import function as func
>>> func()
```

