Programar Juegos Arcade
con Python y Pygame

Chapter 14: Bibliotecas y Módulos

fig.example_sprite_game

Una biblioteca es un conjunto de definiciones de variables, funciones y clases. Habitualmente, estás librerías han sido desarrolladas por otra persona que las ha incluido en un proyecto, de forma que cualquier otro programador no tenga que “reinventar la rueda.” En Python usamos el término módulo para describir el código de una biblioteca determinada.

Al escribir import pygame e import random al principio de los programas que hemos usado hasta ahora, lo que hemos hecho ha sido llamar a esos módulos. Una biblioteca puede estar compuesta de múltiples módulos que pueden importarse. Frecuentemente, una biblioteca tiene un solo módulo, por lo que ambos términos se pueden usar indistintamente.

Generalmente, los módulos se organizan en grupos con funcionalidades similares. Los programas que hemos visto en clase han hecho uso de funciones que provenían de los módulos math y random, así como de la biblioteca pygame. Los módulos se pueden organizar de forma que módulos individuales contengan otros módulos. Por ejemplo, el módulo pygame contiene los submódulos pygame.draw, pygame.image, y pygame.mouse.

Los módulos no se cargan en el programa hasta que éste los necesita. Esto ahorra tiempo y memoria. En este capítulo veremos cómo crear un módulo, importarlo y usarlo.

14.1 ¿Por Qué Crearnos Una Biblioteca?

Existen tres razones principales para que un(a) programador(a) cree sus propias bibliotecas:

  1. Trocear el código en unidades más pequeñas y fáciles de usar.
  2. Permite que varias personas trabajen al mismo tiempo en un programa.
  3. El código escrito puede compartirse fácilmente con otros programadores.

Algunos de los programas que hemos creado en este libro han empezado a extenderse demasiado. Si troceáramos un programa grande, en programas más pequeños, sería más fácil gestionar ese código. Por ejemplo, en el capítulo anterior de los sprites, un programador podría mover la clase sprite a un archivo separado. En un programa más complejo, cada sprite debería estar contenido en su propio archivo individual.

Si varios programadores trabajaran en un mismo proyecto, sería prácticamente imposible hacerlo si todo el código estuviera contenido en un único archivo. Si embargo, todo sería más fácil si troceamos el programa en múltiples partes. Podríamos tener a un programador desarrollando una clase sprite “Orco” y a otro trabajando con el sprite “Goblin”. Como los sprites se encuentran en archivos separados, los programadores no entrarían nunca en conflicto.

En la actualidad, los programadores raramente construyen programas desde cero. Generalmente, los programas son construidos con partes de otros programas con los que comparten la misma funcionalidad. Si por ejemplo, un programador crea un programa para gestionar un préstamo hipotecario, ese código sería un candidato ideal para ir dentro de una biblioteca. Entonces, cualquier otro programa bancario que necesite usar esa aplicación de cálculo de hipotecas, lo único que tendría que hacer es llamar a la biblioteca correspondiente.

14.2 Creamos Nuestro Archivo Módulo/Biblioteca:

Vídeo: Bibliotecas

En este ejemplo trocearemos, en varios archivos, un pequeño programa. Tenemos una función y su correspondiente llamada, dentro de un archivo llamado test.py:

# Función Foo
def foo():
    print("foo!")

# Llamada a la función Foo
foo()

Lo sé, este programa no es tan largo como para tener que trocearlo. Pero si tanto la función, como el programa fueran extensos, la cuestión sería diferente. Si tuviéramos varias funciones, cada una con 100 líneas de código, nos llevaría mucho tiempo gestionar ese archivo. Pero, de momento trabajaremos con este pequeño archivo por cuestiones de claridad.

Podemos sacar del archivo a la función foo. De esta forma, en el archivo solo nos quedaríamos con el código principal del programa. (Repito, en este caso no sería necesario todo esto, solo lo hacemos por cuestiones didácticas.)

Creamos ahora un archivo nuevo, y copiamos la función foo en él. Guardamos este archivo con el nombre mis_funciones.py. Debemos guardarlo en la misma carpeta que test.py.

# Función Foo
def foo():
    print("foo!")
# Llamada a la función Foo que no se ejecuta
foo()

Lamentablemente no es tan sencillo como esto. El archivo test.py desconoce dónde tiene que buscar al archivo mis_funciones.py para importarlo. Tenemos que añadir el comando que lo importe:

# Importa el archivo mis_funciones.py
import mis_funciones

# Llamada a la función Foo que aún no se ejecuta
foo()

Esto sigue sin funcionar. ¿Qué es lo que hemos olvidado? Pues tal como hicimos cuando importábamos pygame, tenemos que poner el nombre del paquete por delante de la función. Tal como esto:

# Importa el archivo mis_funciones.py
import mis_funciones

# Llamada a la función Foo que ya se ejecuta
mis_funciones.foo()

Esto funciona debido a que hemos antepuesto mis_funciones a la llamada de la función.

14.3 Namespace

Vídeo: Namespace

Es muy probable que un programa deba utilizar dos bibliotecas. ¿Pero qué sucede si ambas bibliotecas tienen funciones con nombres idénticos? Digamos que hay dos funciones llamadas imprimir_informe, una de ellas imprime notas y la otra imprime un estado de cuentas. Por ejemplo

def imprimir_informe():
    print ("Informe de Notas del Estudiante:" )
def imprimir_informe():
    print ("Informe Financiero: " )

¿Cómo especificamos a cuál de las funciones debe llamar el programa? Pues bien, es bastante fácil. Especificas el namespace. En el siguiente código, el namespace es el texto que antecede al nombre de la función:

import funciones_alumnos
import funciones_financieras

funciones_alumnos.imprimir_informe()
funciones_financieras.imprimir_informe()

Podemos ver ahora el por qué esto era necesario. ¿Pero qué pasa si no hay conflicto con los nombres? Escribir el namespace una y otra vez podría resultar molesto. Esto lo podemos evitar importando la biblioteca como un namespace local. El namespace local es una lista de funciones, variables y clases a las que no necesitas anteponerles un namespace. Volviendo al ejemplo anterior foo, vamos a eliminar el import original y sustituirlo con otro tipo de import:

# import foo
from mis_funciones import *

foo()

Esto funciona sin tener que anteponer mis_funciones. a la llamada de la función. El asterisco es un metacaracter que importará todas las funciones de mis_funciones. El programador podría importar individualmente unas determinadas funciones, especificando el nombre que le interesa.

14.4 Bibliotecas de Terceros

Cuando trabajamos en Python, tenemos muchas bibliotecas disponibles que pertenecen a la propia estructura de Python. Mira en el siguiente enlace todas las bibliotecas que están disponibles:
http://docs.python.org/3/py-modindex.html

También es posible descargar e instalar otras bibliotecas. Existen bibliotecas para trabajar con la web, números complejos, bases de datos, y muchas más.

Una lista de estupendas bibliotecas para Python, con sus enlaces correspondientes, se encuentra disponible aquí:
http://www.lfd.uci.edu/~gohlke/pythonlibs/

Hacer un recorrido por la lista de bibliotecas disponibles, te puede ayudar a expandir tus horizontes sobre qué tipo de programas puedes llegar a crear. La mayor parte de programas consisten en el ensamblado de distintas piezas ya construidas, en lugar de tener que empezar desde cero.

14.4.1 Ejemplos: Biblioteca OpenPyXL

En este ejemplo usaremos la biblioteca OpenPyXL para escribir en un archivo Excel. También es muy fácil leer un archivo Excel.

Desde un terminal Windows (símbolo del sistema) podemos instalar OpenPyXL escribiendo pip install openpyxl. Si lo haces desde una máquina Mac o Linux, escribirías desde un terminal pip3 install openpyxl.

"""
Uso de OpenPyXL para crear una hoja de cálculo Excel
"""

from openpyxl import Workbook
import random

# Creamos una hoja Excel
wb = Workbook()

# Tomamos la hoja activa
ws = wb.active

# Podemos introducir datos directamente en las celdas
ws['A1'] = "Esto es una prueba"

# Podemos añadir también filas
for i in range(200):
    ws.append([random.randrange(1000)])

# Guardamos el archivo
wb.save("ejemplo.xlsx")

14.4.2 Ejemplos: Biblioteca Beautiful Soup

En este ejemplo extraeremos información de una página web.

Desde un terminal Windows (símbolo del sistema) podemos instalar OpenPyXL escribiendo pip install bs4. Si lo haces desde una máquina Mac o Linux, escribirías desde un terminal pip3 install bs4.

"""
Mostramos cómo leer desde una página web
"""

from bs4 import BeautifulSoup
import urllib.request

# Leemos el archivo
url= "http://www.espnfc.com/spi/rankings/_/view/fifa/teamId/203/mexico?cc=5901"
pagina = urllib.request.urlopen(url)
sopa = BeautifulSoup(page.read(), "html.parser")

# Encontramos la tabla con los datos
rango = sopa.find("table").tbody

# Obtenemos una lista con las filas de la tabla
filas = rank.findAll("tr")

# Iteramos a través de cada fila
for fila in filas:

    # Obtenmos una lista con las celdas de la fila
    celdas = fila.findAll("td")

    # Iteramos a través de cada celda
    for celda in celdas:
        print(celda.text, end=", ")

    # Listo, hemos terminado con esa fila. Imprimos una línea en blanco y vamos a la 
    # siguiente.
    print()

14.4.3 Ejemplo: Biblioteca Matplotlib

Aquí tienes un ejemplo de lo que puedes hacer con la biblioteca de terceros “Matplotlib.” Puedes saltarte esta sección si solo te interesan los juegos, pero mi esperanza es que viendo esto te puedas hacer una idea de lo que es posible hacer utilizando bibliotecas.

Desde un terminal Windows (símbolo del sistema) podemos instalar OpenPyXL escribiendo pip install matplotlib. Si lo haces desde una máquina Mac o Linux, escribirías desde un terminal pip3 install matplotlib.

Para empezar, aquí tienes el código para crear la gráfica de unas rectas para cuatro valores:

fig.matplotlib_01
Figure 14.1: Gráfica Simple De Líneas
"""
Recta con cuatro valores.
Por defecto, el eje x empieza en cero.
"""
import matplotlib.pyplot as plt

y = [1, 3, 8, 4]

plt.plot(y)
plt.ylabel('Valor del Elemento')
plt.xlabel('Número del Elemento')

plt.show()

Observa que puedes hacer zoom, desplazar y guardar el gráfico. Incluso, puedes guardar la gráfica en un formato vectorial como el ps y svg, los cuales se importan en los documentos sin pérdida de calidad, tal como le pasaría a un gráfico raster (mapa de bits).

El valor de x para el Ejemplo 1, Figura 14.1, empieza por defecto en cero. Esto lo puedes cambiar y especificar tus propios valores de x para que vayan con los valores para y. Mira debajo el Ejemplo 2.

fig.matplotlib_02
Figure 14.2: Especificar valores para x
"""
Recta con cuatro valores.
Ahora especificamos unos valores para el eje x también.
"""
import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [1, 3, 8, 4]

plt.plot(x, y)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Es bastante fácil añadir otra serie de datos a la gráfica.

fig.matplotlib_03
Figure 14.3: Representar dos series de datos
"""
En este ejemplo se muestra como dibujar dos series diferentes de datos
sobre la misma gráfica.
"""
import matplotlib.pyplot as plt

x =  [1, 2, 3, 4]
y1 = [1, 3, 8, 4]
y2 = [2, 2, 3, 3]

plt.plot(x, y1)
plt.plot(x, y2)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

También puedes añadirle una leyenda a la gráfica:

fig.matplotlib_07
Figure 14.4: Añadir leyenda

import matplotlib.pyplot as plt

x =  [1, 2, 3, 4]
y1 = [1, 3, 8, 4]
y2 = [2, 2, 3, 3]

plt.plot(x, y1, label = "Serie 1")
plt.plot(x, y2, label = "Serie 2")

leyenda = plt.legend(loc = 'upper center', shadow = True, fontsize = 'x-large')
leyenda.get_frame().set_facecolor('#00FFCC')

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Puedes añadir también anotaciones:

fig.matplotlib_10
Figure 14.5: Añadir anotaciones
"""
Anotando sobre la gráfica
"""
import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [1, 3, 8, 4]

plt.annotate('Aquí',
             xy = (2, 3),
             xycoords = 'data',
             xytext = (-40, 20),
             textcoords = 'offset points',
             arrowprops = dict(arrowstyle = "->",
                               connectionstyle = "arc, angleA = 0,armA = 30,rad = 10"),
             )

plt.plot(x, y)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

¿No te gusta el estilo de líneas por defecto de tu gráfica? Lo puedes cambiar añadiendo un tercer parámetro al comando plot.

fig.matplotlib_04
Figure 14.6: Especificar un estilo de línea
"""
Aquí mostramos cómo establecer el estilo de línea y los símbolos.
"""
import matplotlib.pyplot as plt

x =  [1, 2, 3, 4]
y1 = [1, 3, 8, 4]
y2 = [2, 2, 3, 3]

# Primer carácter: Estilo de línea
# Elegir entre '-', '--',  '-.', ':', 'None', ' ', ”

# Segundo carácter: color
# http://matplotlib.org/1.4.2/api/colors_api.html

# Tercer carácter: forma del símbolo
# http://matplotlib.org/1.4.2/api/markers_api.html

plt.plot(x, y1, '-ro')
plt.plot(x, y2, '--g^')

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Pasar a una gráfica de barras es tan fácil como cambiar plot por bar.

fig.matplotlib_05
Figure 14.7: Gráfica de Barras
"""
Cómo construir una gráfica de barras.
"""
import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [1, 3, 8, 4]

plt.bar(x, y)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Puedes añadirle etiquetas a los valores de los ejes.

fig.matplotlib_06
Figure 14.8: X-axis labels
"""
Cómo añadir etiquetas a los valores del eje x.
"""
import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [1, 3, 8, 4]

plt.plot(x, y)

labels = ['Frogs', 'Hogs', 'Bogs', 'Slogs']
plt.xticks(x, labels)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

También puedes dibujar funciones. Para dibujar la función seno, necesitamos una biblioteca distinta llamada numpy.

fig.matplotlib_08
Figure 14.9: Dibujar la función seno
"""
Uso de la biblioteca numpy para dibujar una función para un determinado
rango de valores.
"""
import numpy
import matplotlib.pyplot as plt

x = numpy.arange(0.0, 2.0, 0.001)
y = numpy.sin(2 * numpy.pi * x)

plt.plot(x, y)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Si quieres, puedes también rellenar la gráfica.

fig.matplotlib_09
Figure 14.10: Rellenar una gráfica
"""
Uso de 'fill' para rellenar una gráfica
"""
import numpy
import matplotlib.pyplot as plt

x = numpy.arange(0.0, 2.0, 0.001)
y = numpy.sin(2 * numpy.pi * x)

plt.plot(x, y)

# 'b' significa azul. 'alpha' es la transparencia.
plt.fill(x, y, 'b', alpha = 0.3)

plt.ylabel('Valor del Elemento')
plt.xlabel('Elemento')

plt.show()

Crear una gráfica de tarta.

fig.matplotlib_11
Figure 14.11: Gráfica de tarta
"""
Crear una gráfica de tarta
"""
import matplotlib.pyplot as plt

# Etiquetas para la gráfica de tarta
etiquetas = ['C', 'Java', 'Objective-C', 'C++', 'C#', 'PHP', 'Python']

# Tamaños para cada etiqueta. Lo usamos para crear un porcentaje
dimensiones = [17, 14, 9, 6, 5, 3, 2.5]

# Para una lista de colores, ver:
# https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/colors.py
colores = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral', 'darkcyan', 'darksage', 'rosybrown']

# ¿Cuán separada ha de estar una porción? Normalmente cero.
separar = (0, 0.0, 0, 0, 0, 0, 0.2)

# Definimos la ratio de aspecto al valor 'equal' para que la tarta dibujada sea circular.
plt.axis('equal')

# Finalmente, dibujamos la tarta
plt.pie(dimensiones, explode = separar, labels = etiquetas, colors = colores,
        autopct = '%1.1f%%', shadow = True, startangle = 90)

plt.show()

Podemos hacer cosas realmente interesantes, como por ejemplo traer datos de cotizaciones desde la web y crear una gráfica de caja y bigotes para las acciones de Apple Computer:

fig.matplotlib_12
Figure 14.12: Gráfica de Caja y Bigotes
"""
Crear una gráfica de caja y bigotes para la cotización de un valor en bolsa
"""
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, WeekdayLocator,\
     DayLocator, MONDAY
from matplotlib.finance import quotes_historical_yahoo_ohlc, candlestick_ohlc

# Extraemos las cotizaciones para las fechas siguientes
fecha1 = (2014, 10, 13)
fecha2 = (2014, 11, 13)

# Vamos a la web y nos traemos la información de la cotización
valores = quotes_historical_yahoo_ohlc('AAPL', fecha1, fecha2)
if len(valores) == 0:
    raise SystemExit

# Definimos el aspecto de la gráfica
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)

# Ponemos marcas mayores para los lunes
lunes = WeekdayLocator(MONDAY)
ax.xaxis.set_major_locator(lunes)

# Ponemos marcas menores para el resto de días
resto_dias = DayLocator()
ax.xaxis.set_minor_locator(resto_dias)

# Damos formato a los días
formato_semana = DateFormatter('%b %d')  # e.g., Ene 12
ax.xaxis.set_major_formatter(formato_semana)
ax.xaxis_date()

candlestick_ohlc(ax, valores, width=0.6)

ax.autoscale_view()
plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')

plt.show()

Hay muchas más cosas que podemos hacer con matplotlib. Mira esta galería de imágenes:
http://matplotlib.org/gallery.html

14.5 Repaso

14.5.1 Test

Haz click para ir al Test.

14.5.2 Ejercicios

Haz click para ir a los Ejercicios.

14.5.3 Taller

Haz click para ir al Taller.


You are not logged in. Log in here and track your progress.