Introducción a la programación en Python
clase 10

Módulos

A medida que se desarrolla una aplicación y crece su complejidad, es conveniente dividirla en diversos archivos de código. Por otro lado, cuando uno escribe una cierta función útil para diferentes programa, uno quisiera poder usarla sin necesidad de copiar el código cada vez. Para esto, exsite una forma de poner funciones en archivos y usarlas desde un programa o desde el intérprete. Este tipo de archivo recibe el nombre de módulo.

Un módulo es un archivo que contiene instrucciones y definiciones. El nombre del archivo es el nombre del módulo con el sufijo .py. Veamos un ejemplo de un módulo definido en el archivo fibo.py. (nota: las instrucciones %load no son parte del código, se usan para desplegarlo)

In [1]:
%load ./fibo.py
In []:
# Modulo de números de Fibonacci

def fib(n):    # escribe la serie de Fibonacci hasta el número n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n): # devuelve la serie de Fibonacci hasta el número n
    resultado = []
    a, b = 0, 1
    while b < n:
        resultado.append(b)
        a, b = b, a+b
    return resultado

Para hacer disponible el módulo se debe ejecutar la instrucción import en el intérprete o en un programa. Es recomendado poner las instrucciones de importación de módulos al inicio del código. Cabe notar que un módulo puede a su vez importar otros módulos.

In [2]:
import fibo

fibo.fib(1000)
fibo.fib2(100)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 

Out[2]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

También se pueden importar funciones específicas del módulo.

In [3]:
from fibo import fib, fib2

fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

El nombre del módulo está disponible a través de la variable global __name__

In [4]:
import fibo

fibo.__name__
Out[4]:
'fibo'

Ejecutando módulos como scripts

Si el módulo se invoca como si fuera un programa, de la siguiente forma:

In [5]:
!python3 fibo.py

se ejecuta el código dentro del módulo (tal como al importarlo), pero con el __name__ como __main__.

Esto quiere decir que si se agrega el siguiente código al final del módulo:

In [6]:
%load fibo_exec.py
In []:
# Modulo de números de Fibonacci

def fib(n):    # escribe la serie de Fibonacci hasta el número n
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n): # devuelve la serie de Fibonacci hasta el número n
    resultado = []
    a, b = 0, 1
    while b < n:
        resultado.append(b)
        a, b = b, a+b
    return resultado

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

se logra que el módulo sea además un programa ejecutable, porque la última parte del código solo se ejecuta si el módulo es ejecutado como programa principal. Esto se usa en ocasiones para dar una interfaz de usuario conveniente para el módulo o para ejecutar funciones de comprobación. Veamos a continuación como se comporta el nuevo módulo al ser importado como módulo y al ser ejecutado como programa principal.

In [7]:
import fibo_exec

fibo_exec.fib(50)
1 1 2 3 5 8 13 21 34 

In [8]:
!python3 fibo_exec.py 50
1 1 2 3 5 8 13 21 34 

Directorios para búsqueda de módulos

Cuando se importa un cierto módulo de nombre ritmo, el intérprete busca en primer lugar un módulo incoroporado (built-in) del lenguaje. Si no lo encuentra busca un archivo ritmo.py en una lista de directorios definidos en la variable sys.path. La variable se inicializa desde las siguientes ubicaciones:

  • el directorio que contiene al programa ejecutado (o el directorio actual si no hay un programa ejecutado)
  • la variable de entorno PYTHONPATH (una lista de nombres de directorio, con la misma sintaxis que la variable de entorno PATH)
  • un valor por defecto definido en el momento de compilación del lenguaje
In [9]:
!echo $PYTHONPATH
:/home/martin/investigacion/doctorado/software/ra/

Módulos estándar

Python tiene una biblioteca de módulos estándar que está descripta en la Python Library Reference. Por ejemplo, el módulo sys está incorporado en todo intérprete de python. Un módulo puede definir variables, como sys.path que establece la lista de directorios dónde el intérprete busca un módulo al momento de importarlo.

In [10]:
import sys
sys.path
Out[10]:
['',
 '/home/martin/eMe/curso_python',
 '/home/martin/investigacion/doctorado/software/ra',
 '/usr/lib/python3.2',
 '/usr/lib/python3.2/plat-linux2',
 '/usr/lib/python3.2/lib-dynload',
 '/usr/local/lib/python3.2/dist-packages',
 '/usr/lib/python3/dist-packages',
 '/usr/lib/python3/dist-packages/IPython/extensions']

Es interesante notar que la variable sys.path se puede modificar en tiempo de ejecución con operaciones de listas.

La función dir()

Permite consultar los nombres (funciones, variables, módulos, etc) que define un módulo.

In [11]:
import fibo
dir(fibo)
Out[11]:
['__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__name__',
 '__package__',
 'fib',
 'fib2']
In [12]:
import sys
dir(sys)
Out[12]:
['__displayhook__',
 '__doc__',
 '__excepthook__',
 '__name__',
 '__package__',
 '__stderr__',
 '__stdin__',
 '__stdout__',
 '_clear_type_cache',
 '_current_frames',
 '_getframe',
 '_mercurial',
 '_xoptions',
 'abiflags',
 'api_version',
 'argv',
 'builtin_module_names',
 'byteorder',
 'call_tracing',
 'callstats',
 'copyright',
 'displayhook',
 'dont_write_bytecode',
 'exc_info',
 'excepthook',
 'exec_prefix',
 'executable',
 'exit',
 'flags',
 'float_info',
 'float_repr_style',
 'getcheckinterval',
 'getdefaultencoding',
 'getdlopenflags',
 'getfilesystemencoding',
 'getprofile',
 'getrecursionlimit',
 'getrefcount',
 'getsizeof',
 'getswitchinterval',
 'gettrace',
 'hash_info',
 'hexversion',
 'int_info',
 'intern',
 'maxsize',
 'maxunicode',
 'meta_path',
 'modules',
 'path',
 'path_hooks',
 'path_importer_cache',
 'platform',
 'prefix',
 'ps1',
 'ps2',
 'ps3',
 'setcheckinterval',
 'setdlopenflags',
 'setprofile',
 'setrecursionlimit',
 'setswitchinterval',
 'settrace',
 'stderr',
 'stdin',
 'stdout',
 'subversion',
 'version',
 'version_info',
 'warnoptions']

Paquetes

Los paquetes son una forma de organizar la nomeclatura de módulos (el espacio de nombres). Por ejemplo, el módulo de nombre A.B designa un sub-módulo llamado B dentro del paquete llamado A. Del mismo modo que el uso de módulos ayuda a que el programador no tenga que preocuparse por los nombres usados en otros módulos, el uso de módulos con punto permite que en paquetes de múltiples módulos como Matplotlib o NumPy no sea problemático el nombre de los módulos de otro paquete.

A modo de ejemplo consideremos la siguiente situación. Supóngase que se quiere diseñar una colección de módulos (un paquete) para el manejo y procesamiento de archivos de audio. En primer término, hay muchos tipos de archivo de audio (e.g. .wav, .aiff, .au, etc) por lo que se debería mantener una colección de módulos destinados a abrir, escribir y convertir entre diferentes formatos. En segundo lugar, hay muchos tipos de procesamiento que uno puede realizar sobre un archivo de audio, como ecualización, echo, filtrado, etc. En particular uno podría discriminar entre filtros y otro tipo de procesamiento más complejo (e.g. phase-vocoder, modelado sinusoidal, síntesis granular, etc.) y a su vez se podría separlos de efectos de audio clásicos (e.g. reverb, delay, phasor, flanger, etc.). A continuación hay una posible estructura de un paquete de este tipo.

In [13]:
%load sound.txt
In []:
sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              reverb.py	
              delay.py
              echo.py
              phasor.py
              flanger.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              lowpass.py
              highpass.py
              bandpass.py
              notch.py
              ...
       processing/              Subpackage for sound processing and synthesis techniques
              __init__.py
              phasevocoder.py
              sinusoidalmodeling.py
              granularsynthesis.py
              amplitudemodulation.py
              frequencymodultaion.py
              ...

Cuando se importa el paquete Python busca a través de los subdirectorios definidos en sys.path el directorio del paquete. Los archivos __init__.py son necesarios para hacer que Python trate a los directorios como conteniendo paquetes. En este ejemplo sencillo los archivos __init__.py están vacíos, pero también pueden ser usados para ejectuar código de inicialización para el paquete.

Los módulos del paquete se pueden importar de forma independiente como en el siguiente ejemplo. Al hacer este tipo de importación hay que usar el nombre completo para referirse una función del módulo.

In [14]:
import sound.effects.echo
# se debe usar el nombre completo de la función
sound.effects.echo.echofilter(delay=0.7, atten=4)
delay: 0.7, attenuation: 4
doing some echo effect with the sound ...

Una forma alternativa de importar el sub-módulo es la siguiente. En este caso no es necesario usar el prefijo del paquete en el nombre de función.

In [15]:
from sound.effects import echo

# en este caso no hay que usar el prefijo del paquete en el nombre de la función
echo.echofilter(delay=0.3, atten=10)
delay: 0.3, attenuation: 10
doing some echo effect with the sound ...

Una forma de importar solo la función deseada es la siguiente. Esto hace que, además de cargarse el módulo, el nombre de la función esté disponible directamente.

In [16]:
from sound.effects.echo import echofilter

# el nombre de la función está accesible directamente
echofilter(delay=0.9, atten=12)
delay: 0.9, attenuation: 12
doing some echo effect with the sound ...

Cabe señalar que cuando se usa la instrucción from package import item, el item puede ser un sub-módulo (o un sub-paquete) del paquete, o cualquier otro nombre definido en el paquete, como una función o variable. Contrariamente, cuando se usa la sintaxis import item.subitem.subsubitem, cada item excepto el último debe ser un paquete; y el último item puede ser un módulo o un paquete, pero no puede ser una función o una variable definida en el item previo.

Si bien es una práctica no recomendada es posible importar el paquete de la siguiente forma.

In [17]:
from sound.effects import *

Para que esto funcione correctamente el programador del paquete debe proporcionar un índice explícito. La convención es la siguiente: si el código __init__.py define una lista llamada __all__ se considera que es la lista de nombres de los módulos a importar. Si por el contrario la definición no existe, la instrucción anterior no importa los submódulos del paquete. Es una responsabilidad del programador del paquete mantener la lista actualizada.

A continuación el contenido del archivo __init__.py para el módulo sound.effects.

In [18]:
%load sound/effects/__init__.py
In []:
__all__ = ["reverb", "delay", "echo", "phasor", "flanger"]

Y con la variable __all__ definida de esa forma se puede ver a continuación los módulos que son importados.

In [19]:
from sound.effects import *

dir(sound.effects)
Out[19]:
['__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__name__',
 '__package__',
 '__path__',
 'delay',
 'echo',
 'flanger',
 'phasor',
 'reverb']

Ejercicio 10.1

Escriba un módulo cursopython.py con todas las funciones de los ejercicios anteriores. Vuelva a escribir los ejercicios importando el módulo. El nombre de archivo de cada ejercicio debe ser ejercicio_ seguido del número de ejercicio, por ejemplo ejercicio_7.1.py