Ej. 49 : Creando las frases…

Lo que debería obtener del escáner del léxico de mi pequeño juego es algo similar a lo siguiente:

(…)

Ahora voy a convertir ésto en algo con lo que el juego pueda trabajar, que sería un tipo de clase frase (Sentence). Una frase puede ser una estructura simple como: Sujeto – verbo – objeto.  Obviamente llega a ser más complejo que ésto. Lo que quiero es convertir las listas anteriores de tuplas en un objeto frase que tenga un sujeto, verbo y objeto.

BUSCANDO COINCIDENCIAS Y ECHANDO UN VISTAZO

Para hacer ésto, necesito cuatro herramientas:

  1. Una manera de recorrer la lista de tuplas. Eso es fácil.
  2. Una forma de encontrar coincidencias (match) en los diferentes tipo de tuplas que espero en mi sujeto-verbo-objeto.
  3. Una forma de echar un vistazo (peek) en la tupla potencial para que pueda tomar algunas decisiones.
  4. Una manera de saltarme (skip) las cosas que no importan, como las palabras vacías.

Voy a poner estas funciones en un fichero llamado ex48/parser.py con el fin de probarlo. Usaré la función peek para mirar el siguiente elemento de mi lista de tuplas y luego match para tomar una y trabajar con ella. Ésta es una primera función peek:

def peek(word_list):
    if word_list:
        word = word_list[0]
        return word[0]
    else:
        return None

Muy sencilla. Ahora la función match:

def skip(word_list, word_type):
    while peek(word_list) == word_type:
        match(word_list, word_type)

LA GRAMÁTICA DE LA FRASE

Con mis herramientas puedo empezar a construir objetos de frases a partir de la lista de tuplas. Utilizaré el siguiente proceso:

  1. Identificar la siguiente palabra con peek.
  2. Si ésta palabra se ajusta a mi gramática, llamo a una función para manejar la parte de la gramática, por ejemplo parse_subject.
  3. Si no, lanzo un error como aprenderé en breve.
  4. Cuando haya terminado, debo tener un objeto frase para trabajar con él en mi juego.

La mejor manera de demostrar ésto es darme el código para leerlo, aunque éste ejercicio es diferente del anterior: tendré que escribir la prueba para el código tendré.

Voy a hacer el código para escribir la prueba para analizar frases simples usando el módulo ex48.lexicon:

class ParseError(Exception):
    pass


class Sentence(object):
    
    def __init__(self, subject, verb, object):
        #Recordar que tomo tuplas ('nombre', 'princesa') y las convierto
        self.subject = subject[1]
        self.verb = verb[1]
        self.object = object[1]


def peek(word_list):
    if word_list:
        word = word_list[0]
        return word[0]
    else:
        return None


def match(word_list, expecting):
    if word_list:
        word = word_list

        if word[0] == expecting:
            return word
        else: 
            return None
    else:
        return None


def skip(word_list, word_type):
    while peek(word_list) == word_type:
        match(word_list, word_type)


def parse_verb(word_list):
    skip(word_list, 'stop')

    if peek(word_list) == 'verb':
        return match(word_list, 'verb')
    else:
        raise Parse Error("Expected a verb next.")


def parse_object(word_list):
    skip(word_list, 'stop')
    next = peek(word_list)

    if next == 'noun':
        return match(word_list, 'noun')
    if next == 'direction':
        return match(word_list, 'direction')
    else:
        raise ParseError("Expected a noun or direction next.")


def parse_subject(word_list, subj):
    verb = parse_verb(word_list)
    obj = parse_object(word_list)

    return Sentence(subj, verb, obj)


def parse_sentence(word_list):
    skip(word_list, 'stop')

    start = peek(word_list):
    
    if start == 'noun':
        subj = match(word_list, 'noun')
        return parse_subject(word_list, subj)
    elif start == 'verb':
        # Entonces asumimo que el sujeto es el jugador
        return parse_subject(word_list, ('noun', 'player'))
    else:
        raise ParseError("Must start with subject, object, or verb not: %s" % start)

UNAS PALABRAS SOBRE EXCEPCIONES

Ya he aprendido algo sobre las excepciones, pero no la forma de lanzarlas. En éste código, al comienzo, se muestra cómo hacerlo con ParseError. Observo que usa clases para darle el tipo de excepción, y el uso de la palabra clave raise para lanzar la excepción.

En mis pruebas, tendré que trabajar con éstas excepciones.

QUÉ DEBERÍA PROBAR

Escribiré una prueba completa que confirme que todo el código funciona. Voy a colocar el código de prueba en tests/parser_tests.py, de forma similar al fichero de prueba del último ejercicio. Eso incluye crear excepciones cuando se da una mala frase.

Comprobar si hay alguna excepción mediante el uso de la función assert_raises. Tengo que aprender cómo usarlas para poder escribir la prueba que espero que falle, lo que es muy importante en las pruebas.

Cuando lo haya hecho, debería saber cómo funciona ese código y cómo escribir una prueba para el código de otras personas, incluso si ellos no quieren que lo haga.

PRÁCTICAS

  1. Cambiar los métodos parse_ e intentar escribirlos dentro de una clase en lugar de ser sólo métodos.
  2. Hacer el parser_ (analizador) más resitente a errores para que evite molestar a otros usuarios cuando escriban palabras que mi léxico no entiende.
  3. Mejorar la gramática para que pueda tratar cosas con números.
  4. Pensar acerca de cómo puedo utilizar esta clase frase (Sentence) en mi juego para hacer más cosas divertidas a partir de lo que escriba el usuario.

PREGUNTAS

P: No consigo que assert_raises funcione bien.

R: Assegúrate de que estás escribiendo assert_raises(excepción, llamada, parámetros) y no escribe assert_raises(excepción, llamada (parámetros)). Observa como la segunda forma llama a la función después de pasar al resultado a assert_raises, lo cuál está mal. Tienes que llamar a la función (?) y a sus argumentos assert_raises.

Ej. 48 : Avanzando en la entrada de usuario…

Mi juego probablemente funcione bien, pero el sistema de entrada de usuario no es muy robusto. Cada habitación necesitaba su propio conjunto de frases exactas que sólo funcionaba si el jugador las había escrito perfectamente. En lugar de ésto, debería de tener un sistema que permita al usuario escribir la frases de varias maneras, como por ejemplo:

  • Abrir puerta
  • Abre la puerta
  • Pasa la puerta

Estas dos frases deberían de funcionar también:

  • Golpear al oso
  • Golpear al oso en la cara

Estaría bien que el usuario pudiera escribir algo muy parecido en su idioma y que el juego lo entendiese. Para ello, voy a escribir un módulo que hace precisamente eso. Dicho módulo tendrá algunas frases que trabajan juntas para gestionar la entrada del usuario y convertirlo en algo en lo que mi juego pueda trabajar con fiabilidad. Una versión simplificada de mi idioma puede usar estas reglas:

  • Las palabras están separadas por espacios.
  • Las frases están compuestas por palabras.
  • Gramaticales, que ructura de la estructura de las frases en significado.

Esto significa que tendremos que averiguar cómo obtener las palabras del usuario y qué tipo de palabras son.

MI LÉXICO DEL JUEGO

En mi juego, tendré que crear un léxico de palabras:

  • Palabras de dirección: Norte, sur, este, oeste, abajo, arriba, izquierda, derecha, atrás.
  • Verbos: Ir, parar, matar, comer.
  • Palabras vacías: El, en, de, desde, a, lo.
  • Nombres: Puertas, oso, princesa, armario.
  • Números: Cualquier cadena de caracteres del 0 al 9.

Cuando llegue a los sustantivos, tendré un pequeño problema porque cada habitación puede tener un conjunto diferente de nombres, pero voy a recoger éste pequeño conjunto para trabajar por ahora y más tarde haré las mejoras.

Dividiendo en partes una frase

Una vez que tengo mi léxico de palabras, necesitaré una forma de dividir en partes la frase para que pueda averiguar qué son. En mi caso he definido una frase como «palabras separadas por espacios», así que sólo tengo que hacer lo siguiente:

stuff = raw_input('> ')
words = stuff.split()

Eso es realmente todo lo que me preocuparé en hacer por el momento, aunque va a funcionar muy bien durante bastante tiempo.

Tuplas del léxico

Una vez que se dividir la frase en palabras, sólo tengo que recorrer la lista de palabras y comprobar de qué «tipo» son. Para ello, voy a usar una pequeña estructura de Python llamada «tupla«. Una tupla no es más que una lista que no se puede modificar. Se crean escribiendo datos dentro de los paréntesis y separados con comas, como una lista:

first_word = ('direction', 'north')
second_word = ('verb', 'go')
sentence = [first_word, second_word]

Esto crea un par (TIPO, PALABRA) que me permite consultar la palabra y hacer cosas con ella.

Lo anterior sólo es un ejemplo, pero es básicamente el resultado final. Así que lo que haré es tomar la entrada bruta del usuario, obtener cada palabra con split y, a continuación, analizar esas palabras para identificar su tipo y, por últomo, crear una nueva frase final.

Escanenando la entrada

Ahora ya estoy lista para escribir mi escáner. El escáner tomará una cadena de la entrada bruta del usuario y devolverá una frase que se compone de una lista de tuplas con las parejas (TIPO, PALABRA). Si una palabra no está en el léxico, debería devolver la PALABRA, pero asignando un error a TIPO. Éstos errores indicarán al usuario qué los ha creado.

Aquí es donde se pone divertido. Tengo una prueba unitaria u debo esscribir el escáner para que la prueba unitaria funcione.

Excepciones y números

Hay una pequeña cosa con la que voy a empezar, la conversión de números. «Me voy a engañar» y usar excepciones. Una excepción es un error que sse obtiene de alguna función que debería haber funcionado. Lo que ocurre es que la función «lanza» una excepción cuando encuentra un erro, así que tengo que manejar esa excepción. Por ejemplo, supongo que escribo lo siguiente en Python:

(…)

ValueError es una excepción que la función int() lanzó porque lo que pasé a dicha función no era un número. La función int podría haber devuelto un valor para indicar que hubo un error, pero sólo devuelve números enteros. No puede devolver -1. En lugar de tratar de averiguar qué devolver cuando se produce un error, la función int() lanza una excepción ValueError, que debo tratar.

Para tratar la excepción utilizo las palabras clave try y except:

def convert_number(s):
    try:
        return int(s)
    except ValueError:
        return None

En el bloque de try escribo el código que quiero controlar si lanzo una excepción y en el bloque except escribo el código de lo que hay que hacer si se produce un error. En éste caso, quiero intentar llamar a int() con algo que podría ser un número. Si se produce un error, lo «atrapo», y devuelvo None.

En el escáner que escriba debo utilizar esta función para comprobar si algo es un número. También debo hacerlo como última cosa a comprobar antes de declarar que esa palabra es errónea.

QUÉ DEBERÍA PROBAR

A continuación, están los ficheros de tests/lexicon_tests.py que debería usar:

from nose.tools import *
from ex48 import lexicon

def test_directions():
    assert_equal(lexicon.scan("north"), [('direction', 'north')])
    result = lexicon.scan("north south east")
    assert_equal(result, [('direction', 'north'),
        ('direction', 'south'),
        ('direction', 'east')])

def test_verbs():
    assert_equal(lexicon.scan("go"), [('verb', 'go')])
    result = lexicon.scan("go kill eat")
    assert_equal(result, [('verb', 'go'),
        ('verb', 'kill'),
        ('verb', 'eat')])

def test_stops():
    asert_equal(lexicon.scan("the"), [('stop', 'the')])
    result = lexicon.scan("the in of")
    assert_equal(result, [('stop', 'the'),
        ('stop', 'in')
        ('stop', 'of')])

def test_nouns():
    assert_equal(lexicon.scan("bear"), [('noun', 'bear')])
    result = lexicon.scan("bear princess")
    assert_equal(result, [('noun', 'bear'),
        ('noun', 'princess')])

def test_numbers():
    assert_equal(lexicon.scan("1234"), [('number', 1234)])
    result = lexicon.sscan("3 91234")
    assert_equal(result, [('number', 3),
        ('number', 91234)])


def test_errors():
    assert_equal(lexicon.scan("ASDFADFASDF"), [('error', 'ASDFADFASDF')])
    result = lexicon.scan("bear IAS princess")
    assert_equal(result, [('noun', 'bear'),
        ('error', 'IAS'),
        ('noun', 'princess')])

Recordar que debo crear un nuevo proyecto partiendo del esqueleto, escribir una prueba para éste caso (no copiar y pegar) y escribir su escáner para que la prueba funcione. Hay que centrarse en los detalles y asegurarse de que todo funciona bien.

CONSEJOS DE DISEÑO

Centrarme en hacer una prueba cada vez. Debo mantenerlo simple y escribir todas las palabras de mi léxico en listas en el módulo lexicon.py. No debo de modificar la lista de palabras de entrada, en lugar de ésto, voy a crear mi propia lista nueva con sus tuplas de léxico en ella. Además, utilizaré la palabra clave en éstas listas de léxico para comprobar si una palabra está en el diccionario. Usaré el diccionario en mi solución.

+PRÁCTICAS

  1. Mejorar la prueba de unidad para asegurarme de que cubre más léxico.
  2. Añadir más palabras al léxico y luego actualizar la prueba unitaria.
  3. Asegurarme de que mi escáner es capaz de procesar lo que el usuario escribe independientemente de si lo hace en mayúsculas o minúsculas.
  4. Encontrar otra manera de convertir el número.
  5. ¿Cuántas líneas tiene mi solución?

PREGUNTAS

P: ¿Por que sigo teniendo ImportErrors?

R: Por lo general los errores de importación son causados por cuatro cosas:

  1. No creé un fichero __init__.py en un directorio que tiene módulos
  2. Me encuentro en el directorio incorrecto
  3. Estoy importando el módulo equivocado porque lo deletreé mal
  4. Mi PYTHONPATH no está configurado a . por lo que no puedo cargar módulos desde mi directorio actual

P: ¿Cuál es la diferencia entre try-except y if-else?

R: La construcción try-except sólo se usa para manejar excepciones que los módulos pueden lanzar. Nunca debe usarse como una sentencia condicional if-else.

P: ¿Hay alguna forma de mantener el juego en ejecución mientras el usuario está esperando escribir?

R: Asumo que quiero tener un mostruo que ataque a los usuarios si no reaccionan rápido.

Ej. 47 : Haciendo pruebas…

Tener que escribir comandos en el juego una y otra vez para asegurarme de que funciona es cansado. ¿No sería mejor escribir pequeños trozos de código que lo pongan a prueba? Así, cuando haga un cambio o añada algo nuevo al programa, sólo tendré que ejecutar y probar y los test asegurarán que todo funciona. Éstas pruebas automatizadas no encontrarán todos los errores, pero reducirán el tiempo dedicado repetidas veces a escribir y ejecutar el código.

A partir de éste ejercicio, los siguientes no tendrán la sección «Qué debería ver», sino «Qué debería probar». Escribiré purebas automatizadas para todo el código a partir de ahora, esperando que ésto me convierta en una programadora mejor: 😛

Probar fragmentos de código es algo aburrido y sin duda tedioso, por lo que podría escribir un poco de código para que lo haga el ordenador en mi lugar.

Voy a dar el siguiente salto y escribir el código que conozco de otro código que he escrito. Éste proceso de escribir una prueba o test que ejecuta un código me obliga a entender con claridad lo que acabo de escribir. Fortalece en mi cerebro lo que hago y por qué funciona consiguiendo un nuevo nivel de atención al detalle.

ESCRIBIENDO UNA PRUEBA

Voy a partir de un fragmento muy simple de código y a escribir una prueba sencilla. Voy a basar ésta pequeña prueba en un nuevo proyecto a partir del esqueleto que he hecho en el ejercicio 46.

Primero, creo un proyecto ex47 a partir del equeleto. A continuación, sigo los pasos. Daré las instrucciones en mi idioma en lugar de mostrar cómo escribirlas, así que tengo que averiguarlo.

  1. Copio el esqueleto a ex47.
  2. Renombro cada cosa con NAME con ex47.
  3. Cambio la palabra NAME de todos los archivos por ex47.
  4. Por último, borro los archivos con extensión .pyc para asegurarme de que queda limpio.

NOTA: Recordar que debo ejecutar el comando nosetests para ejecutar las pruebas. Puedo ejecutarlo con Python ex46_tests.py, pero no funcionará tan facilmente y tendré que hacerlo con cada fichero a probar…

A continuación, creo un simple fichero ex47.game.py en el que escribir el código a probar. Es una clase muy simple que quiero probar con éste código:

class Rom(object):

    def __init__(self, name, description):
        self.name = name
        self.description = description
        self.paths = []

    def go(self, direction):
        return self.paths.get(direction, None)
    
    def add_paths(self, paths):
        sself.paths.update(paths)

Una vez tenga éste archivo, cambio el esqueleto de prueba unitaria a éste:

from nose.tools import *
from ex47.game import Room

def test_room():
    gold = Room("GoldRoom",
        """This room has gold in it yoy can grab. There's a door to the north.""")
    assert_equal(gold.name, "GoldRoom")
    assert_equal(gold.paths, [])

def test_room_paths():
    center = Room("Center", "Test room in the center.")
    north = Room("North", "Test room in the north.")
    south = Room("South", "Test room in the south.")

center.add_paths(['noth', 'south': south])
assert_equal(center.go('north'), north)
assert_equal(center.go('south'), south)

def test_map():
    start = Room("Start", "You can go west and down a hole.")
    west = Room("Trees", "There are trees here, yoy can go east.")
    down = Room("Dungeon", "It's dark down here, you can go up.")

    start.add_paths(['west': west, 'down': down])
    west.add_paths(['east': start])
    down.add_paths(['up': start])

    assert_equal(start.go('west'), west)
    assert_equal(start.go('west').go('east'), start)
    assert_equal(start.go('down').go('up'), start)

Este fichero importa la clase Room del módulo ex4.game para que pueda probarlo. Hay un conjunto de pruebas que son funciones que empiezan con test_. Dentro de cada prueba hay un poco código que crea una room o varias, y se asegura de que cada una funciona como se espera. Pone a prueba las funciones básicas de room, luego de path, y finalmente de map.

Las funciones importantes aquí son assert_equal, que se asegura de que las variables que he creado o los paths creados en una room están en realidad donde creo que están. Si obtengo algún resultado erróneo, entonces nosetests mostrará un mensaje de error para que pueda averiguar qué pasa.

DIRECTRICES PARA PRUEBAS

Estás son las normas generales que seguiré cuando haga pruebas:

  1. Probar los ficheros en test/ y llamarlos BLATH_test.py, de lo contrario nosetest no los ejecuta. Así también mantengo las pruebas con el resto del código.
  2. Escribir un fichero de prueba para cada módulo que realice.
  3. Intentar que mis casos de prueba(funciones) sean cortos. No pasa nada si están un poco desordenados…
  4. Tratar de mantener los casos de prueba limpios y eliminar cualquier código repetitivo. Crear funciones para ayudarme a desacerme del código duplicado porque dificulta cambiar las pruebas.
  5. No obsesionarse con las pruebas. A veces, la mejor manera de rediseñar algo es simplemente borrarlo y volver a empezar.

QUÉ DEBERÍA VER

(…)

PRÁCTICAS

  • Leer más sobre nosetests y sobre sus alternativas
  • Aprender más sobre «doc tests» de Python.
  • Crear un room más avanzado y, a continuación, utilizarlo en el juego y probarlo.

PREGUNTAS

P: Obtengo un error de sintaxis cuando ejecuto nosetests.

R: Lee el mensaje de error y corrige esa línea de código o una de las anteriores.

P: No puedo importar ex47.game.

R: Asegúrate de crear el fichero ex47/__init__.py. Repasa el ejercicio 46 otra vez para ver cómo sse hace. Si éste no ess problema, entonces haz ésto en Windows:

$env:PYTHONPATH = "$env:PYTHONPATH;."

Por último, asegúrate de que estás ejecutando otras pruebas con nosetests y no sólo con Python.

P: Obtengo UserWarning cuando ejecuto nosetests.

R: Probablemente tengas instaladas dos versiones de Python o no estés usando distribute. Vuelve para atrás e instala distribute o pip como describí en el ejercicio 46.

Ej. 46 : Diseñando el esqueleto de un proyecto…

En éste ejercicio voy  a aprender cómo crear un buen directorio «esqueleto» de un proyecto. Éste esqueleto, tendrá todos los elementos básicos que necesito para crear un proyecto y ejecutarlo. Tendré lo principal de un proyecto, pruebas automatizadas, módulos y el código de instalación. Cuando vaya a hacer un nuevo proyecto sólo tendré que copiar éste directorio con un nuevo nombre y editar los archivos y lo tendré todo listo para empezar.

INSTALANDO PAQUETES PYTHON

Antes de empezar éste ejercicio es necesaraio instalar algún software para Python usando una herramienta llamada «pip» para instalar nuevos módulos.

Instalaré los siguientes paquetes Python:

  1. pip desde http://pypi.python.org/pypi/pip
  2. distribute desde https://pypi.python.org/pypi/distribute
  3. nose desde https://pypi.python.org/pypi/nose
  4. virtualenv desde https://pypi.python.org/pypi/virtualenv

(…)

Nota: A veces, el instalador de Python no añade C:\Python27\Script al PATH del sistema. Si ocurre ésto, hay que añadir ésto al path como hice para C:\Python27 en el ejercicio 0 con [Environment]::SetEnvironmentVariable(«Path», «$env:Path;C:\Python27\Scripts», «User»)

CREANDO EL DIRECTORIO CON EL ESQUELETO DEL PROYECTO

Primeto, creo la estructura del directorio esqueleto con éstos comandos:

$ mkdir projects
$ cd projects/
$ mkdir skeleton
$ cd skeleton
$ mkdir bin
$ mkdir NAME
$ mkdir tests
$ mkdir docs

He usado un directorio llamado projects para guardar en él todas las cosas con las que trabajo. Dentro de éste directorio tengo mi directorio skeleton, donde tengo lo básico del proyecto. El directorio NAME será renombrado con el nombre del módulo principal de mi proyecto cuando use el esqueleto.

A continuación, tengo que crear algunos ficheros. Así lo hago en Windows con PowerShell:

$ new- item - type file NAME/__init__.py
$ new- item - type file tests/__init__.py

De ésta manera, creo un directorio de módulos Python a los que puedo añadir código. Luego, necesitaré crear un fichero setup.py que usaré para instalar mi proyecto más adelante.

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

config = [
    'description': 'My Proyect',
    'author': 'My Name',
    'url': 'url to get it at.',
    'download_url': 'Where to download it.',
    'author_email': 'My email.',
    'version': '0.1', 
    'install_requires': ['nose'],
    'packages': ['NAME'],
    'scripts': [],
    'name': 'projectname'
]

setup(**config)

Edito el archivo para que tenga mi información de contacto y esté listo para copiarlo.

Por último, creo un simple archivo esqueleto para hacer pruebas llamado test/NAME_test.py:

from nose.tools import *
import NAME

def setup():
   print "SETUP!"

def teardown():
    print "TEARDOWN!"

def test_basic():
    print "I RAN!"

ESTRUCTURA FINAL DEL DIRECTORIO

Cuando haya terminado de configurar todo ésto, el directorio debería ser algo así…

setup.py
NAME/
    __init__.py
bin/
docs/
tests/
    NAME_tests.py
    __init__.py

(…)

A partir de ahora debo ejecutar los comandos que trabajan con éste directorio desde éste punto. Si no puedo hacer ls – R y veo ésta misma estructura, entonces hay algún error en algún lugar. Por ejemplo, la gente suele estar en el directorio tests/  para intentar ejecutar el fichero, lo cual no va a funcionar. Para ejecutar las pruebas de mi aplicación, debo estar antes de tests/ .

Ej. 45 : Creando mi juego…

Tengo que empezar a aprender a caminar por mí misma…Siiii. Ya he aprendido en que toda la información está en Internet (curiosamente lo he aprendido en un libro…y porque no han parado de repetírmelo, ejem).

En éste post lucharé con un gran proyecto, e intentaré hacerlo funcionar…

Éstos son algunos requisitos:

  1. Crear un juego diferente al único que he hecho.
  2. Utilizar import para usar más de un fichero.
  3. Usar una clase por habitación y dar a las clases nombres adecuados a sus funciones.
  4. Mi programa necesita saber que existen esas habitaciones, así que debo crear una clase que las ejecute y sepa de su existencia. Hay muchas maneras de hacerlo, pero hay que tener en cuenta que cada habitación devuelve la habitación siguiente o asigna a una variable cuál es la habitación siguiente.

El objetivo de éste post es aprender cómo estucturar las clases que necesitan otras clases dentro de otros archivos.

(…)

EVALUANDO MI JUEGO

(…)

NORMAS DE ESTILO EN LAS FUNCIONES

Todas las reglas que he aplicado sobre cómo hacer una buena función, se siguen aplicando, pero añadiré alguna más.

  • Por diversas razones, lo programadores llama métodos a las funciones que forman parte de las clases. Cada vez que diga función, me corregirán diciendo «método»…
  • Cuando se trabaja con clases, se dedica mucho tiempo a hablar de crear la clase «hacer cosas» …
  • Simplificar las funciones y hacerlas pequeñas. Cuando se avanza con las clases, no hay que olvidar ésto.

NORMAS DE ESTILO EN LAS CLASES

  • Mi clase debería usar el estilo llamado «camel case», como en SuperGoldFactory, en lugar de super_gold_factory.
  • No escribir demasiado en las funciones __init__, ya que eso las hace más difíciles de usar.
  • Las otras funciones deben utilizar el «formato de subrayado». Así, escribo mi_pelo_rubio en lugar de mipelorubio o MiPeloRubio.
  • Ser constante en la forma de organizar los argumentos de las funciones. Si la clase tiene user, dog y cat, mantener el orden en todo, a menos que realmente no tenga sentido. Si tengo una función que toma los parámetros (dog, cat, user) y otra que los toma (user, cat, dog), será muy difícil usarlas.
  • Tratar de no usar las variables que vienen del módulo o globales. Éstas deben de ser bastante autónomas.
  • Siempre siempre siempre usar el formato class Nombre(object) o de lo contario surgirán problemas.

NORMAS DE ESTILO EN EL CÓDIGO

  • Añadir espacios entre algunas líneas de código para facilitar la lectura.
  • Si no se puede leer en voz alta, probablemente resulte difícil de leer. Intentar hacerlo lo más legible posible.
  • Copia un estilo, hasta que encuentres el tuyo.
  • Cuando crea que he encontrado mi propio estilo, todavía se podrá mejorar. XD Trabajar con el código de otras personas es parte de la tarea de un programador y otras personas tienen muy mal gusto…
  • Si encuentro un estilo que me guste, intentar imitarlo.

BUENOS COMENTARIOS

  • Hay quien dice que el código debe de ser lo suficientemente legible para no necesitar comentarios…ergo ¿no hay que escribir comentarios? Ni caso.
  • Lo mejor es describir por qué hago lo que hago. El código dice cómo.
  • Es ideal crear comentarios pensando en el lector que use mi código. Añadir una buena frase sobre qué puede hacer alguien con una función, puede ayudar mucho.
  • Comentarios breves y concisos.
  • Si se cambia código, revisar los cometarios. Obvio.

EVALUANDO MI CÓDIGO

(…)

Ej. 44 : Comparando herencia vs composición…

¿QUÉ ES LA HERENCIA?

La herencia indica que una clase obtendrá la mayoría o la totalidad de sus carácterísticas a partir de una clase padre.

Ésto ocurre de forma implícita siempre que escribamos class Audi(Car), lo que viene a decir que «crea una clase llamada Audi que hereda de Car«. Cuando hago ésto, cualquier acción que se hizo en la instancia de Car, también funciona como si estuviese hecha en la instancia de Audi. Hacer ésto nos permite tener una funcionalidad común en la clase Car, de manera que posteriormente se especializa esa funcionalidad en la clase Audi.

En éste tipo de especialización hay tres maneras en las que clases padres e hijos pueden interactuar:

  1. Las acciones en el hijo implican una acción en el  padre.
  2. Las acciones en el hijo anulan la acción en el padre.
  3. Las acciones en el hijo alteran la acción en el padre.

A continuación voy a demostrar cada una de ellas con su correspondiente código.

1. HERENCIA IMPLÍCITA

Primero voy a mostrar las acciones implícitas que suceden cuando definimos una unción (?) en la clase padre, pero no en la hija.

class Parent(object):

    def implicit(self):
        print "PARENT implicit()"

# Creo a la clase Child, que hereda del padre, sin nada definido dentro de ella:
class Child(Parent):   
    pass    # Es la forma de decirle a Python que quiero un bloque vacio

dad = Parent()
son = Child()

dad.implicit()
son.implicit()

La clase Child, heredará todo su comportamiento de los padres.

Ejecuto en la línea de comandos y obtengo:

Ej 44a

Se observa que Child, no tiene definida una función implícita pero funciona y llama a la fución definida en Parent. Ésto demuestra que si ponemos una función en una clase base (ej. Parent), todas las subclases (ej.Child) automáticamente toman esas características. Muy práctico para el código repetitivo que necesitamos muchas veces.

2. ANULACIÓN EXPLÍCITA

El problema con la herencia implícita es que a veces se desea que el hijo se comporte de manera diferente. En éste caso, necesito que la función del hijo anule o reemplace la funcionalidad del padre. Para ello, basta con definir una función con el mismo nombre en Child de la siguiente manera:

class Parent(object):

    def override(self):
        print "PARENT override()"
        
class Child(Parent):

    def override(self):
        print "CHILD override()"

dad = Parent()  # La variable dad está en Parent
son = Child()   # La variable son está en Child
# son es una instancia de Child
dad.override()  # En realidad se va a ejecutar la función Parent.override
son.override()  # Al ejecutar, mostrará en pantalla Child.override...

En éste ejemplo, tengo una función llamada override en ambas clases. Ejecuto y…

Ej 44b

Se observa que cuando ejecuto dad.override(), se ejecuta Parent.override porque la variable dad está en Parent.

Y cuando se ejecuta son.override() se muestra en pantalla el mensaje de Child override() porque son es una instancia de Child que anula esa función definiendo su propia versión.

Por si cabía alguna duda, override significa anular.

3. ALTERAR ANTES O DESPUÉS

La tercera forma de utilizar la herencia es un caso especial de anulación en el que quiero modificar el comportamiento antes o después de la ejecución de la clase Parent. Primero anulo la función como hice en el último ejemplo, pero luego uso una función de Python llamada super para llamar a la versión Parent.

Se ve mejor en el ejemplo:

1    class Parent(object):
2
3        def altered(self):
4            print "PARENT altered()"
5
6    class Child(Parent):
7
8        def altered(self):
9            print "CHILD, BEFORE PARENT altered()"
10           super(Child, self).altered()# Uso super para llamar a la versión de Parent.altered = Llamo a super con los argumentos Child y self, luego llamo a la función altered y la devuelve
11           print "CHILD, AFTER PARENT altered()"
12
13   dad = Parent()
14   son = Child()
15
16   dad.altered()
17   son.altered()

Las líneas importantes son de la 9 a la 11, dentro de Child, donde ocurre lo siguiente al llamar a son.alter():

  1. Como he anulado Parent.altered, se ejecuta la versión de Child.altered() y la línea 9 se ejecuta.
  2. En éste caso, quiero un antes y un después, así que después de la línea 9 quiero usar super para llamar a la versión de Parent.altered.
  3. En la línea 10, llamo a super(Child, self).altered(), lo cual es muy parecido a la función getattr que ya he usado en alguna ocasión, aunque ésto es para herencias, y obtendré la clase Parent. Lo leo como «llamo a super con los argumentos Child y self, luego llamo a la función altered y la devuelve».
  4. En éste punto, se ejecuta la versión Parent.altered de la función y muestra en pantalla el mensaje de Parent.
  5. Por último, regresa de Parent.altered y la función Child.altered continúa mostrando en pantalla el mensaje de después.

Lo ejecuto…

Ej 44c

4. LAS TRES FORMAS COMBINADAS

Para demostrar todo ésto, haré una versión final que muestra cada tipo de interacción en un sólo fichero:

class Parent(object):

    def override(self):
        print "PARENT override()"

    def implicit(self):
        print "PARENT implicit()"

    def altered(self):
        print "PARENT altered()"

class Child(Parent):

    def override(self):
        print "CHILD overridde()"

    def altered(self):
        print "CHILD, BEFORE PARENT altered()"
        super (Child, self).altered()
        print "CHILD, AFTER PARENT altered"

dad = Parent()
son = Child()

dad.implicit()
son.implicit()

dad.override()
son.override()

dad.altered()
son.altered()


Ejecutando…

Ej 44d

LA RAZÓN DE SUPER()

Ésto debería de ser de sentido común, pero luego encontramos problemas con una cosa que se llama herencia múltiple.

La herencia múltiple es cuando se define una clase que hereda de una o más clases así:

class SuperFun(Child, BadStuff):
    pass

Ésto es como decir: «Haz una clase llamada SuperFun que hereda de la clase Child y Badstuff a la vez».

En éste caso, cada vez que tenga acciones implícitas en cualquier instancia de SuperFun, Python tiene que buscarlas en ambas clases de las que hereda, Child y BadStuff, por si también estuvieran allí definidas. Pero lo hace en un orden coherente. Para ello, Python utiliza algo llamado «método de resolución de orden» (MRO) y un algoritmo llamado C3 para obtenerlo directamente.

Debido a que MRO es un algoritmo complejo y bien definido, se usa, pero Python no nos permite hacerlo… En su lugar, Python utiliza la función super() que me permite manejar todo ésto cuando lo necesite para alterar el tipo de acciones demostradas en Child.altered más arriba. Con super() Python encuentra la función correcta.

Usando super() con __init__

El uso más común de super() es en realidad en la función __init__ en las clases base. Éste será el único sitio donde necesitaré hacer alguna cosa en un hijo después de completar la inicialización en el padre.

class Child(Parent):
    def __init__(self, stuff):
        self.stuff = stuff
        super(Child, self).__init__()

Ésto es más o menos lo mismo que en el ejemplo anterior de Child.altered, excepto que estoy definiendo alguna variable en el __init__ antes de inicializar Parent con su Parent.__init__.

COMPOSICIÓN

La herencia es útil, pero otra hacer exactamente lo mismo consiste en usar sólo otras clases y MÓDULOS, en lugar de depender de la herencia implícita. Si nos fijamos en las tres formas de explotar la herencia, dos de las tres implican escribir de nuevo el código para reemplazar o alterar la funcionalidad. Ésto puede ser fácil de replicar simplemente LLAMANDO A FUNCIONES EN OTRAS CLASES.

Lo veo en el ejemplo:

class Other(object):

    def override(self):
        print "OTHER override()"

    def implicit(self):
        print "OTHER implicit()"

    def altered(self):
        print "OTHER altered()"

class Child(object):

    def __init__(self):
        self.other = Other()

    def implicit(self):
        self.other.implicit()

    def override(self):
        print "CHILD override()"

    def altered(self):
        "CHILD, BEFORE OTHER altered()"
        self.other.altered()
        print "CHILD, AFTER OTHER altered()"

son = Child()

son.implicit()
son.override()
son.altered()

En éste código no estoy usando el nombre Parent, ya que no hay una relación Padre-Hijo. Estoy ante una relación «tiene-un«, donde Child tiene-un Other que lo usa para obtener algo… Mmmm.

Ejecutando obtengo lo siguiente:

Ej 44e

CUÁNDO USAR HERENCIA O COMPOSICIÓN

La cuestión de la herencia vs composición se reduce a un intento de resolver el problema de la reutilización de código. No quiero duplicar código en todo mi software, ya que no es limpio ni eficiente. La herencia resuelve éste problema mediante la creación de un mecanismo para que tengamos características implícitas en las clases base. La composición soluciona ésto dándole módulos y la posibilidad de simplemente llamar a funciones en otras clases.

Si ambas soluciones resuleven el problema de la reutilización, entonces, ¿cuál es el adecuado según qué situaciones? La respuesta es increiblemente subjetiva, pero he aquí tres consejos útiles:

  1. Evitar la herencia múltiple a toda costa, ya que es demasiado compleja para ser útil de una forma fiable. Entran en juego conceptos como la jeraquía de clases y se necesita tiempo para ver de dónde viene cada cosa…un lío, vamos.
  2. La composición es útil para empaquetar el código en módulos usados en situaciones y lugares no relacionados.
  3. La herencia es aconsejable usarla sólo cuando las piezas reutilizables de código estén claramente relacionadas entre sí, están bajo un mismo concepto común o si tengo algún motivo para usarla.

Sin embargo, no ser eclavo de las normas es la mejor norma. Sirva de meros consejos generales.

PREGUNTAS

P: ¿Cómo puedo mejorar resolviendo problemas que no había visto antes?

R: La única manera de mejorar resolviendo problemas es resolver por tí mismo tantos problemas como puedas. Por lo general, la gente encuentra un problema difícil y sale corriendo a encontrar la respuesta. Eso está bien cuando necesitas las cosas hechas para ya, pero si tienes tiempo para resolverlo por tí mismo, tómate el tiempo que necesites. Enfréntate al problema tanto como te sea posible hasta que lo resuelvas…o renuncies.

P: ¿Los objetos son copias de las clases?

R: En algunos lenguajes (como JavaScript), eso es cierto. Éstos son llamados lenguajes prototipos y no hay mucha diferencia entre objetos y clases a excepción de su uso. En Python, sin embargo, las clases actúan como plantillas que crean nuevos objetos, similar a cómo se crean monedas con moldes.

Ej. 43 : Analizando y diseñando juegos…

En éste ejercicio voy a proponer un proceso para construir algo con Python usando programación orientada a objetos (POO). Cuando digo proceso me refiero a un método de pasos a seguir, una hoja de ruta útil, que por supuesto se puede modificar y es susceptible de cualquier mejora. Como ya he dicho en alguna ocasión, no hay una única respuesta correcta, pero es interesante tener un método.

El proceso es el siguiente:

  1. Escribir o dibujar el problema
  2. Extraer y analizar los conceptos clave
  3. Crear un mapa de objetos y jerarquía de clases de los conceptos
  4. Escribir el código de las clases y hacer una prueba para ejecutarlas
  5. Repetir y refinar

Este proceso se realiza «de arriba a abajo», de lo abstracto a lo concreto, la implementación real.

ANÁLISIS DEL MOTOR DE UN JUEGO

El juego que haré se llamará «Gothons from Planet Percal #25» y será un pequeño juego de aventura espacial. Sin nada más que la idea en la mente, exploraré la idea y pensaré como hacer que el juego cobre vida.

1.- ESCRIBIR O DIBUJAR EL PROBLEMA

Voy a escribir un párrafo para el juego:

«Los aliens han invadido una nave espacial y nuestro héroe tiene que pasar por un laberinto de habitaciones derrotándolos, para que pueda escapar en una cápsula de escape hacia otro planeta. El juego será de tipo Zork o Adventure con salidas de texto y formas divertidas de morir. Incluirá un motor que ejecuta un completo mapa de habitaciones o escenas. Cada habitación mostrará su propia descripción cuando el jugador entre en ella e indicará al motor cuál es la próxima habitación del mapa a ejecutar.»

Describo cada escena:

  • Death: Cuando el jugador muere. Debería ser algo divertido.
  • Central corridor: Es el punto de partida y ya tiene un Gothon de pie, que el jugador tiene que derrotar con una broma antes de continuar.
  • Laser weapon armony: Aquí es donde el héroe consigue una bomba de neutrones para explotar la nave antes de llegar a la cápsula de escape. Tiene un teclado y debe averiguar su código para usarlo.
  • The bridge: Otra escena de batalla con un Gothon en la que el héroe coloca la bomba.
  • Escape pod: Donde el héroe escapa, pero sólo despué de adivinar cuál es la cápsula de escape correcta.

Ahora puedo dibujar el mapa, incluso más descripciones de cada habitación (cualquier cosa que se me venga a la mente).

2.- EXTRAYENDO Y ANALIZANDO CONCEPTOS CLAVE

Tengo suficiente información para extraer algunos de los nombres y analizar la jerarquía de clases. Primero, crearé la jerarquía de clases:

  • Alien
  • Player
  • Ship
  • Maze (laberinto)
  • Room
  • Scene
  • Gothon
  • Escape pod (cápsula de escape)
  • Planet
  • Map
  • Engine
  • Death
  • Central corridor
  • Laser weapon armory (armería de armas láser)
  • The bridge

También podría revisar los verbos y ver si podrían ser funciones de los nombres, pero de momento nos saltaremos esto.

3.- CREANDO UN MAPA DE OBJETOS Y UNA JERARQUÍA DE CLASES DE LOS CONCEPTOS

Mmmm. A ver. Tengo que construir una jerarquía de clases en base a similitudes y diferencias.

Room y Scene son básicamente lo mismo. Elijo Scene para éste juego porque Planet, Central corridor y Death son más escenas que habitaciones. Maze y Map son similares también, así que elijo Map ya que la usaré más. Alien y Player los guardaré para más adelante.

Entonces la jerarquía de clases es la siguiente:

  • Map
  • Engine
  • Scene
    • Death
    • Central corridor
    • Laser Weapon Armony
    • The bridge
    • Escape pod

Ahora voy a añadir verbos para las acciones que necesite…

  • Map
    • Next_scene
    • opening_scene
  • Engine
    • play
  • Scene
    • enter
    • Death
    • Central corridor
    • Laser Weapon Armony
    • The bridge
    • Escape pod

Añadimos enter debajo de Scene, ya que todas las ecenas hijas van a heredarlo y sobrescribirlo más tarde.

4.- ESCRIBIENDO EL CÓDIGO DE LAS CLASES Y UNA CLAVE PARA EJECUTARLAS

Una vez tengo el árbol de clases y algunas de las funciones, escribo el código con una pequeña prueba al final. Lo hago copiando el árbol anterior y convirtiéndolo en clases.

1     class Scene(object):
2     
3         def enter(self):
4             pass
5 
6
7     class Engine(object):
8    
9     def __init__(self, scene_map):
10        pass
11
12    def play(self):
13        pass
14 
15    class Death(Scene):
16
17    def enter(self):
18        pass
19 
20    class CentralCorridor(Scene):
21    
22        def enter(self):
23            pass 
24
25    class LaserWeaponArmony(Scene):
26
27        def enter(self):
28            pass
29
30    class TheBridge(Scene):
31
32        def enter(self):
33            pass
34
35    class EscapePod(Scene):
36
37        def enter(self):
38            pass
39 
40 
41    class Map(object):
42
43    def __init__(self, start_scene):
44        pass
45
46    def next_scene(self, scene_name):
47        pass
48 
49    def opening_scene(self):
50        pass
51 
52
53    a_map = Map('central_corridor')
54    a_game = Engine(a_map)
55    a_game.play()

En éste archivo se puede ver que sólo replico la jerarquía que quería y luego añado un poco de código al final para ejecutarlo y comprobar que todo funciona. Avanzo y lo completeré.

5.- REPITIENDO Y REFINANDO…1,2,3…

El último paso del proceso es hacer un bucle while. No siempre éste proceso se hace en una sola vez.

Por lo general, hay que repetir el proceso de nuevo y cada vez que se refina basándose en la información aprendida en los pasos anteriores.

A veces, llegaré al paso 3 y me daré cuenta de que tengo que trabajar más el paso 1 y 2, por lo que pararé y volveré a trabajar en ellos… ¡¡¡un símil de éste blog!!!  Y de la vida misma… XD

Otras veces, puedo tener un momento de inspiración y saltar al final para escribir el código que tengo en mente, pero después volveré atrás y seguiré los pasos para asegurarme de que se cubren todas las posibilidades que tengo.

Vamos, que el pensamiento lateral no es lineal precisamente. Y hay que saber organizarlo. Creo que ésto es importante.

La otra idea es que no es algo que se haga a un sólo nivel, sino que se puede hacer en todos los niveles cuando encuentro un problema en particular. Voy a suponer que todavía no se escribir el método Engine.play. Puedo parar y hacer todo éste proceso para averiguar cómo escribirlo.

DE ARRIBA A ABAJO VS DE ABAJO A ARRIBA

Lo que estaba haciendo hasta ahora es «de arriba a abajo», ya que parte de lo abstracto (parte superior) hasta llegar a la implementación real.

¡¡Pero hay otra forma muy interesante!!!

Éstos son los pasos:

  1. Tomar un fragmento pequeño del problema. Modificar algo del código…y conseguir que se ejecute.
  2. Refinar el código haciéndolo más formal, con clases y pruebas automatizadas.
  3. Extraer los conceptos clave utilizados, y tratar de saber más sobre ellos.
  4. Escribir lo que realmente está pasando.
  5. Volver atrás y refinar el código. Igual tengo que tirarlo a la basura y empezar de nuevo.. 😛
  6. Repetir el proceso pasando a algún otro fragmento del problema.

Éste método es muy bueno cuando conozco pequeñas piezas del rompecabezas en general, pero tal vez no tengo suficiente información sobre el concepto general. Modificando las pequeñas piezas y explorando el código, se consigue desgranar el problema, hasta conseguir resolverlo. Será más efectivo una vez voy cogiendo soltura en programación y piense con naturalidad el código del problema.

EL CÓDIGO: «GOTHONS FROM PLANET PERCAL #25»

Voy a partir el fichero ex43.py en partes para explicar cada una, en lugar de mostrar todo el código a la vez…

Éstos son los import para el juego:

from sys import exit
from random import randit

Como se ve en el esqueleto del código, tengo una clase base para Scene, que tendrá las cosas comunes para las escenas. En éste programa tan simple, no son muchas, así que es más bien para ver cómo funciona la clase principal.

class Scene(object):
     
    def enter(self):
        print "This scene is not configured. Subclass it and implement enter()."
            exit(1)

También tengo mi clase Engine y puedo ver como simplemente estoy usando los métodos Map.opening_scene y Map.next_scene. Como he hecho un poco de planificación se que tendré que escribirlos y luego usarlos antes de escribir la clase Map.

class Engine(object):

    def __init__(self, scene_map):
        self.scene_map = scene_map

    def play(self):
        current_scene = self.scene_map.opening_scene()

        while True:
        print "\n- - - - - - - - - "
        next_scene_name = current_scene.enter()
        current_scene = self.scene_map.next_scene(next_scene_name)

La primera escena es la llamada Death, que muestra el tipo de escena más sencilla de todas:

class Death(Scene):

    quips = [
        "You died. You kinda suck at this."
        "Your mom would be proud...if she were smarter.",
        "Such a luser.",
        "I have a small puppy that's better at this."
    ]

def enter(self):
        print Death.quips[randit(0, len(self.quips)-1)]
        exit(1)

Después de ésto crearé el CentralCorridor, que el inicio del juego. Estoy haciendo las escenas para el juego antes que el Map porque necesito referencias después.

class CentralCorridor(Scene):

    def enter(self):
        print "The Gothons of Planet Percal #25 have invaded your ship and destroyed"
        print "your entire crew. You are the last surviving member and your last"
        print "mission is to get the neutron destruct bomb for the Weapons when"
        print "a Gothon jumps out, red scaly skin, dark grimy teeth,and evil cloud costume"
        print "flowing around his hated filled body. He's blocking the door to the"
        print "Armory and about to pull a weapor to blast you."

        action = raw_input ("> ")

        if action == "shoot!!":
            print "Quick on the draw you yank out your blaster and fire it at the Gothon."
            print "His clown costume is flowing and moving around his body with throws"
            print "off your aim. Your laser hits his costume but misses him entirely. This"
            print "completely ruins his brand new costume his mother bought him, which"
            print "makes him fly into a range and blast you repeatedly in the face until"
            print "you are dead. Then he eats you."
            return 'death'


        elif action == "dodge!":
            print "Like a world class boxer you dodge, weave, slip and slide right"
            print "as the Gothon's blaster cranks a laser past your head."
            print "In the middle of your artful dodge your foot slips and you"
            print "bang your head on the metal wall and pass out."
            print "You wake up shortly after only to die as the Gothon stomps on"
            print "your head and eats you."
            return 'death'

        elif action == "tell a joke":
            print "Lucky for you they made you learn Gothon insults in the academmy."
            print "You telll the one Gothon joke you know:"
            print "Lbne zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr."
            print "The Gothon stops, tries not to laugh, then busts out lauhing and can´t move."
            print "While he's laughing you run up and shoot him square in the head"
            print "putting him down, then jump though the Weapon Armory door."
            return 'laser_weapon_armory'

        else:
            print "DOES NOR COMPUTE!"
            return 'central_corridor'

A continuación, hago el resto de escenas del juego…

class LaserWeaponArmory(Scene):

    def enter(self):
        print "You do a dive roll into the Weapon Armory, crouch and scan the room"
        print "for more Gothons that might be hiding. It's dead quiet. too quiet."
        print "you stand up and run to the far side of the room and find the"
        print "neutron bomb in its container. There's a keypad lock on the box"
        print "and you need the code to get the bomb out. If you get the code"
        print "wrong 10 times then the lock closes forever and you can't"
        print "get the bomb. The code is 3 digits."
        code = "%d%d%d" % (randint(1,9), randint(1,9), randint(1,9))
        guess = raw_input("[keypad]> ")
        guesses = 0

        while guess != code and guesses < 10:
            print "BZZZZED!"
            guesses += 1
            guess = raw_input("[keypad]> ")

        if guess == code:
            print "The container clicks open and the seal breaks, letting gas out."
            print "You grab the neutron bomb and run as fast as you can to the"
            print "bridge where you must place it in the right spot."
            return 'the_bridge'
        else:
            print "The lock buzzes one last time and then you hear a sickening"
            print "melting sound as the mecanism is fused together."
            print "You decide to sit there, and finally the Gothons blow up the"
            print "ship from their ship and you die."
            return 'death'




class TheBridge(Scene):
    
    def enter(self):
        print "You burst onto the Bridge with the neutron destruct bomb"
        print "under your arm and surprise 5 Gothons who are trying to"
        print "take control of the ship. Each of them has an even uglier"
        print "clown costume than the last. They haven't pulled their"
        print "weapons out yet, as they see the active bomb under your"
        print "arm and don't want to set it off."

        action = raw_input("> ")

        if action == "throw the bomb":
            print "In a panic you throw the bomb at the group of Gothons"
            print "and make a leap for the door. Right as ou drop it a"
            print "Gothon shoots you right in the back killing you."
            print "As you die you see another Gothon frantically try to disarm"
            print "the bomb. You die knowing they will probably blow up when"
            print "it goes off."
            return 'death'

        elif action == "slowly place the bomb":
            print "You point your blaster at the bomb under your arm"
            print "and the Gothons put their hands up and start to sweat."
            print "You inch backward to the door, open it, and then carefully"
            print "place the bomb on the floor, pointing your blaster at it."
            print "You then jump back through the door, punch the close button"
            print "and blast the lock so the Gothons can't get out."
            print "Now that the bomb is placed you run to the escape pod to"
            print "get off this tin can."
            return 'escape_pod'
        else:
            print "DOES NOT COMPUTE!"
            return "the_bridge"


class EscapePod(Scene):

    def enter(self):
        print "You rush through the ship desperately trying to make to"
        print "the sscape pod before the whole ship explodes. It seems like"
        print "hardly any Gothons are on the ship, so your run is clear of"
        print "interference. You get to the chamber with the escape pods, and"
        print "now need to pick one to take. Some of them could be damaged"
        print "but you don't have time to look. There's 5 pods, which one"
        print "do you take?"

        good_pod = randint(1,5)
        guess = raw_input("[pod #]> ")

        if int(guess) != good_pod:
            print "You jump into pod %s and hit the eject button." %guess
            print "The pod escapes out into the void of space, then"
            print "implodes as the hull ruptures, crushing your body"
            print "into jam jelly."
            return 'death'
        else:
            print "You jump into pod % and hit the eject button" %guess
            print "The pod easily slides out into space heading to"
            print "the planet below. As it flies to the planet, you look"
            print "back and see your ship implode then explode like a"
            print "bright star. taking out the Gothon ship at the same"
            print "time. You won!"

            return 'finished'

Después de ésto, tengo mi clase Map:

class Map(object):

    scenes = [
        'central_corridor': CentralCorridor(),
        'laser_weapon_armory': LaserWeaponArmory(),
        'the_bridge': TheBridge(),
        'escape_pod': EscapePod(),
        'death': Death()
    ]

    def __init__(self, start_scene):
        self.start_scene = start_scene

    def next_scene(self, scene_name)
        return Map.scenes.get(scene_name)

    def opening_scene(self):
        return self.next_scene(self.start_scene)

Se puede ver

Ej. 42 : Practicando con objetos y clases…

Un concepto importante que hay que entender es la diferencia entre una clase y un objeto. El problema es que no existe una diferencia real entre ambos. Pero en realidad son lo mismo en diferentes puntos del tiempo.

Por ejemplo, ¿cuál es la diferencia entre un pez y un salmón?

Un salmón es un tipo particular de pez.

Voy a llamar al salmón Agustín.

¿Cuál es la diferencia entre Agustín y un salmón?

Ninguna. Agustín es una INSTANCIA de un salmón. Agustín ha sido «creado» a partir de algún otro salmón, y ahora es una cosa real que tiene atributos como salmón.

Avanzo.

El pez es una clase, el salmón es una clase y Agustín es un objeto.

Mmmmm

Un pez es una clase, lo que significa que es no es algo real, sino más bien una palabra ABSTRACTA que ATRIBUIMOS a INSTANCIAS de cosas con atributos similares. ¿Tiene aletas? ¿Branquias? ¿Vive en el agua? Bien. Probablemente es un pez.

Entonces, una persona con un doctorado viene y nos dice: «No, mi joven amiga, éste pez es en realidad un Salmo salar, vulgarmente conocido como salmón», Éste profesor sólo ha hecho una aclaración más del pez y ha creado una nueva clase llamada «salmón» que tiene atributos específicos. ¿Tiene una nariz larga, carne rojiza, es grande, vive en el mar o en agua dulce y es sabroso? Bien. Probablemente sea un salmón.

Por último, viene un cocinero y le dice al doctor: «Voy a llamar a éste salmón Agustín, y lo cocinaré a la papillote. Es una de mis especialidades. Quedará exquisito». Ahora tengo esa instancia de un salmón, que también es una instancia de un pez, llamado Agustín, que se convirtió en algo real y fué fileteado. XD Se ha convertido en un objeto.

Así que AGUSTÍN ES UN OBJETO, UN TIPO DE SALMÓN DE LA CLASE PEZ.

A ver. Entonces, ¿Un objeto es una clase pero no todas las clases son objetos? Mmmmm.

En éste caso:

  • Pez = clase
  • Salmón = clase
  • Agústín = objeto, instanciado(creado) por la clase salmón

Perfect!!

CÓMO ES ÉSTO EN CÓDIGO

¿Clase u objeto?

Un truqui:

ES-UN para objetos y/o clases que se relacionan entre si por una relación de clase (relación pez-salmón).

TIENE-UN para objetos y clases que sólo están relacionados porque hacen referencia entre si (relación salmón-branquia).

Vamos a ver si lo he entendido.

Ej. 42 - Editor 1Ej. 42 - Editor 2 Ej. 42 - Editor 3Todos mis comentarios pueden no estar bien. Son suceptibles de cambio.

ACERCA DE LAS CLASS NOMBRE (OBJECT)

Como ya he visto las diferencias entre clase y objeto, sólo añadir que Python siempre requiere (object) cuando crea una clase.

En realidad, una clase hereda de otra clase llamada object para crear su clase, pero no es objeto. Algo confuso.

Ésto viene de la interpretación original que Python le dió a la clase, y de la fusión entre las clases viejas y las nuevas.

No digo más que me lío sola. 😛

 PREGUNTAS

P: ¿Qué es el punto de self.pet = None?

R: Asegura que el atributo self.pet de esa clase se le asigna el valor None.

P: ¿Qué hace super(Employee, self).__init__(name)?

R: Así es como se ejecuta el método __init__de una clase padre con seguridad. Busca «python super» y los diversos consejos sobre su uso.

P: ¿Es posible usar una clase como un objeto?

R: Interesante pregunta. ¿Tú qué crees?

P: Mmmmm

Ej. 41 : Aprendiendo a hablar orientado a objetos…

En éste ejercicio voy a aprender cómo hablar «orientado a objetos». Voy a practicar con un pequeño conjunto de palabras que necesito conocer junto con sus definiciones. Luego practicaré con una serie de frases con huecos y tendré que entender su significado. Por último, escribiré un gran conjunto de ejercicios que tengo que completar para crear así frases sólidas en mi vocabulario.

PRACTICANDO CON LAS PALABRAS

  • class – Le dice a Python que cree un nuevo tipo de cosa.
  • objeto – Tiene dos significados: el tipo de cosa más simple y cualquier instancia de alguna cosa.
  • instancia – Lo que obtengo cuando le digo a Python que cree una clase.
  • def – Para definir una función dentro de una clase.
  • self – Dentro de las funciones de una clase, self es una variable de la instancia/objeto al que se acaba de acceder.
  • herencia – el concepto de que una clase puede heredar aspectos de otra clase, al igual que usted de sus padres.
  • composición – El concepto de que una clase puede estar compuesta de otras clases, al igual que un coche tiene ruedas.
  • atributo – Una propiedad que tienen las clases, y suelen ser variables.
  • es-un – Una frase empleada para decir que algo hereda de otra cosa, como un «salmón» es-un «pez».
  • tiene-un  – Una frase para decir que algo está compuesto de otras cosas o tiene un rasgo, como «un salmón tiene-una boca».

PRACTICANDO CON LAS FRASES

A continuación, tengo una lista de fragmentos de código Python, seguido de su significado.

  • class X(Y) «Crea una clase llamada X que tiene-un Y».
  • class X(object): def __init__(self, J) «La clase X tiene-un __init__ que recibe por parámetro self y J.»
  • class X(object): def M(self, J)  «La clase X tiene-una función llamada M que recibe por parámetro self y J».
  • foo = X()  «Asigna a foo una instancia de la clase X»
  • foo.M(J)  «Desde foo se obtiene la función M y la llama pasándole por parámetro self,J.»
  • foo.K = Q «Desde foo se obtiene el atributo K y le asigna Q.»

En cada una de esas frases, puedo tratar X, Y, M, J, K, Q y foo como espacios en blanco. Por ejemplo, podría escribir las mismas frases de la siguiente manera.

  • «Crea una clase llamada ??? que tiene-un Y.»
  • «La clase ??? tiene-un __init__ que recibe por parámetro self y ??? .»
  • «La clase ??? tiene-una función llamada ??? que recibe por parámetro self y ???.»
  • «Asigna a foo una instancia de la clase ???»
  • «Desde foo se obtiene la función ??? y la llama pasándole por parámetro self, ???.»
  • «Desde foo se obtiene el atributo ??? y le asigna ??? «

PRACTICANDO CON AMBAS

Ahora voy a combinar los ejercicios de palabras con los ejercicios de las frases. Tengo que hacer lo siguiente:

  1. Tomar una de las notas de las frases y practicar con ella.
  2. Darle la vuelta, leer la frase y cada una de las palabras de la frase que esté en los ejercicios de las palabras, recuperar las notas de las palabras.
  3. Practicar con esas palabras de esas frases.
  4. Seguir haciéndolo

UNA PRUEBA DE LECTURA

Voy a hacer un pequeño programa Python con el que practicar las palabras que ya conozco. Lo único que hace es usar una librería llamada urllibr para descargar una lista de palabras que tengo. Éste es el código. Debería entrar en opp_test.py para trabajar con él.

1    import random
2    from urllib import urlopen
3    import sys
4    
5    WORD_URL = "http://learncodethehardway.org/words.txt"
6    WORDS = []
7    PHRASES = {
8        "class %%%(%%%):":
9        "Make a class named %%% that is-a %%%.",
10       "class %%%(object):\n\tdef __init__(self, ***)" :
11       "class %%% has-a function named *** that takes self and *** parameters.",
12       "class *** (object):\n\tdef ***(self, @@@)":
13       "class %%% has-a fuction named *** that takes self and @@@ parameters",
14       "*** = %%%()":
15       "Set *** to an instance of class %%%.",
16       "***.***(@@@)":
17       "From *** get the *** function, and call it with parameters self, @@@.",
18       "***.*** = '***'":
19       "From *** get the *** attribute and set it to '***'."
21   }
22
23   # Si quiero practicar primero con frases
24   PHRASE_FIRST = False 
25   if len(sys.argv) == 2 and sys.argv[1] == "english":
26   PHRASE_FIRST = True
27   
28   # Carga las palabras de la pagina web
29   for word in urlopen(WORD_URL).readlines():
30   WORDS.append(word.strip())
31   
32 
33   def convert (snippet, phrase):
34       class_names = [w.capitalize() for w in 
35           random.sample(WORDS, snippet.count("***"))]
36       other_names = random.sample(WORDS, snippet.count("***"))
37       results = []
38       param_names = []
39   for i in range(0, snippet.count("@@@")):
40   param_count = random.randint(1,3)
41   param_names.append(', '.join(random.sample(WORDS, param_count)))
42
43
44   for sentence in snippet, phrase:
45       result = sentence[:]
46   # Nombres falsos de clase
47   for word in class_names:
48       result = result.replace("%%%", word, 1)
49   
50   # Otros nombres falsos
51   for word in other_names:
52       result = result.replace("***", word, 1)
53
54 
55   # Lista de parametros falsos
56   for word in param_names:
57       result = result.replace("@@@", word, 1)
58   
59       results.append(result)
60 
61   return results
62 
63
64   # Se ejecuta hasta pulsar CTRL- D
65   try:
66       while True:
67           snippets = PHRASES.keys()
68           random.shuffle(snippets)
69
70       for snippet in snippets:
71           phrase = PHRASES[snippet]
72           question, answer = convert (snippet, phrase)
73           if PHRASE_FIRST:
74                   question, answer = answer, question
75
76           print question
77
78           raw_input("> ")
79           print "ANSWER: %s\n\n" % answer
80   except EOFError:
81       print "\nBye"

Ejecuto el código e intento traducir las «frases orientadas a objetos» a nuestro idioma.

Y tras depurar varios errores me sale lo siguiente:Ej. 41 - Terminal 1

Nota: El diccionario PHRASES tiene ambas formas y hay que usar la correcta.

PRACTICANDO DE NUESTRO IDIOMA A CÓDIGO

A continuación, debo ejecutar el código con la «english» para practicar la opción inversa.

Éstas frases están utilizando palabras sin sentido. Parte del aprendizaje para leer código consiste en dejar de poner mucho significado en los nombres usados para variables y clases.

LEYENDO MÁS CÓDIGO

Voy a leer más código, y ésta vez, para leer las frases que acabo de aprender. Tengo que buscar todos los ficheros con clases y hacer lo siguiente:

  1. Para cada clase, anotar su nombre y escribir qué otras clases heredan de ella.
  2. Debajo de ésto, anotar un listado de todas las funciones que tiene y todos los parámetros que reciben.
  3. Hacer una lista de todos los atributos uados con self.
  4. Para cada atributo, escribir a qué clase pertenece.

El objetivo es revisar código real y empezar a aprender a «reconocer los patrones» de las frases que he aprendido y cómo se usan. Si practico lo suficiente, se supone que debería de empezar a ver esos patrones en el código, entre tanto, parecerán esos espacios en blanco que desconozco.

PREGUNTAS

P: ¿Qué hace result = sentence [:]?

R: Es la manera de copiar una lista en Python. Se usa la sintaxis [:] para obtener desde el primer elemento hasta el último.

Ej. 40 : Practicando con módulos, clases y objetos…

Y ahora si.

Python es un lenguaje de programación orientado a objetos. Lo que significa que hay una construcción en Python llamada clase que nos permite estructurar el código de una manera en particular. Usando clases podemos dotar de consistencia a nuestros programas para que puedan ser utilizados de una manera más limpia. O al ménos, esa es la teoría.

A continuación, voy a tratar de definir los principios de la programación orientada a objetos. Utilizaré lo que ya conozco como diccionarios y módulos con clases y objetos.

Allá vamos.

LOS MÓDULOS SON COMO LOS DICCIONARIOS

Ya sé como crear y usar un diccionario (bueno, estoy en ello) y que éstos me permiten hacer referencia a un elemento a partir de un nombre, lo que significa que si tengo un diccionario con la clave ‘apple’ y quiero obtenerla, hago ésto:

mystuff = {'apple': "I AM APPLES!"   (NO ME DEJA ESCRIBIR LA LLAVE DE CIERRE NI CON EL TECLADO EN PANTALLA...WHAT A...????)
 print mystuff['apple']

Mantengo en mi cabeza la idea de «obtener X a partir de Y» y ahora pienso en los módulos. Hasta el momento he hecho algunos y los he utilizado de acuerdo al siguiente proceso:

  1. Se que un módulo es un fichero Python con funciones y variables.
  2. Entonces, importo ese fichero.
  3. Puedo acceder a las funciones o variables que hay en el módulo escribiendo el operador ‘.‘ (punto).

Imagina que tienes un módulo llamado mystuff.py y contiene la siguiente función llamada apple:

# Esto está dentro de mystuff.py
 def apple():
     print "I AM APPLES!"

Después, puedo usar ese módulo con import y acceder a la función apple:

import mystuff
mystuff.apple()

También podría incluir una variable llamada tangerine, así:

def apple():
   print "I AM APPLES!"
# Esto sólo es una variable
tangerine = "Living reflection of a dream"

A continuación, puedo acceder a ésta de la forma:

import mystuff
 mystuff.apple()
 print mystuff.tangerine

Volviendo a los diccionarios, debería empezar a darme cuenta de que ésto es similar a usar un diccionario, aunque la síntaxis es diferente. Compara:

mystuff ['apple']       # Obtiene apple del diccionario
mystuff.apple()         # Obtiene apple del módulo
mystuff.tangerine       # Lo mismo, pero con una variable

Observo entonces que hay un patrón común en Python:

  1. Hay relación entre una clave y un valor (clave=valor).
  2. Obtengo un dato a partir del nombre de la clave.

En el caso del diccionario, la clave es una cadena y la síntaxis es [clave].

En el caso del módulo, la clave es un identificador y la síntaxis es .key. A parte de ésto, son casi iguales.

LAS CLASES SON COMO LOS MÓDULOS

Una forma de ver un módulo es como un diccionario especializado que puede almacenar código Python que puede usar con el operador ‘.’. Python también tiene otra construcción empleada con un propósito similar y que se llama clase. Una clase es una forma de agrupar funciones y datos y guardarlos en un contenedor de manera que se accede a ellos con el operador ‘.’.

Si tuviera que crear una clase con el módulo mystuff, haría algo así:

class MyStuff(object) :
     def _init_(self) :
     self.tangerine = "And now a thousand years between"
      def apple(self) :
     print "I AM CLASSY APPLES!"

Puede parecer complicado comparado con los módulos y aunque queda pendiente ver algunas diferencias, deberías de ser capaz de apreciar que es como un «mini-módulo» MyStuff que tiene una función apple().

Pueden resultar algo confusos también la función _init_() y el uso de self.tangerine para dar un valor a la variable tangerine.

Esta es la razón por qué las clases se utilizan en lugar de los módulos: se puede tomar la clase anterior y utilizarla para crear muchas de ellas, muchísimas, y que no interfieran entre sí. Con los módulos, al importarlo, sólo hay uno para todo el programa, a no ser que se hagan malabarismos.

Antes de entenderlo, necesito saber qué es un objeto y cómo trabajar con MyStuff tal y como lo hago con el módulo mystuff.py.

LOS OBJETOS SON COMO MINI-IMPORTS

Si una clase es como un mini-módulo, entonces tiene que haber un concepto similar para import, pero aplicado a clases. Este concepto es llamado «instanciar», que es de una forma elegante como decir «crear». Cuando se instancia una clase se obtiene lo que conocemos como un objeto.

La manera de hacerlo es llamar a la clase igual que a una función:

thing = MyStuff ()
thing.apple ()
print thing.tangerine

La primera línea es la operación «instancia» y es muy parecido a llamar a una función. Sin embargo, cuando llamamos a ésta, hay una secuencia de eventos que Python coordina. Lo veo usando el código anterior:

  1. Python busca MyStuff() y ve que es una clase que he definido.
  2. Python crea un objeto vacío, con todas las funciones que he especificado en la clase usando def.
  3. Python comprueba si he creado una función mágica _init_  y si está, llama a esa función para inicializar el objeto vacío recién creado.
  4. En la función _init_ de MyStuff tomo la variable extra self, que es el objeto vacío que Python creó, y asigno valores a las variables utilizándolo igual que hacía con los módulos y otros objetos.
  5. En éste caso, asigno como valor a self.tangerine la letra de una canción y luego inicializo ese objeto.
  6. Ahora, Python puede tomar ese objeto y asignarlo a la variable thing para que trabaje con él.

Esto es lo básico de como Python crea los «mini-imports» cuando se llama a una clase como una función. Hay que recordar que ésto no crea una clase, sino que está utilizando una clase como un molde para crear copias de cosas de ese tipo.

En realidad ésto es una idea un tanto inexacta de cómo trabajan las clases y objetos para que se pueda empezar a comprender basándome en lo que ya conozco sobre módulos. La verdad es que las clases y lo objetos divergen de los módulos a partir de éste punto. Es 100% cierto lo siguiente:

  • Las CLASES son como moldes o definiciones que permiten crear nuevos mini-módulos.
  • Las INSTANCIAS son la forma de crear uno de éstos mini-módulos e importarlos en el mismo momento.
  • El mini-módulo creado se llama OBJETO y lo asigno a una variable para trabajar con él.

Las clases y los objetos son muy diferentes de los módulos y lo anterior sólo deve servirme para entender el concepto de clase.

OBTENIENDO COSAS DE THINGS

Hay 3 formas de obtener cosas de things:

# Estilo diccionario
mystuff['apples']   (En relidad serían llaves!!!)
# Estilo módulo
mystuff.apples()
print mystuff.tangerine
# Estilo clase
thing = MyStuff ()
thing.apples ()
print thing.tangerine

PRIMER EJEMPLO DE CLASES

Debería comenzar a ver las similitudes entre estos tres tipos de contenedores clave = valor y, además, tengo bastantes preguntas que iré resolviendo a partir del próximo ejercicio. Voy a terminar escribiendo algo de código, para ir practicando antes de continuar.

Ej. 40 - Programa con Errores

Ej. 40 - Terminal con Errores

Me da un error en la línea 12 que mareo y cambio cosas (pongo corchetes, espacios, borro, vuelvo a escribir…) y sigue dando error.

……

Al final descubro que aunque dice que el error está en la línea 12, es un problema de definición de los parámetros, el error parece estar al principio…

Necesitaba poner doble guión bajo en la línea 3, de éste modo:   def __ init__(self, lyrics):

Ej. 40 - Programa con Errores 2Ej. 40 - Terminal con Errores 2 peq

Ejecutando me da error, ésta vez en la línea 15, pero de nuevo me fijo en las líneas del principio…

En fin. Por hoy termino. Mañana retomo.

Buenas noches!!

Ej. 40 - Programa corregidoEj. 40 - Terminal final

Por fiiin. Que no lo veía. Eran los ([]) y no [()]. Hay veces que por una tontería el atasco es absurdo…bueno. Así aprendo para la próxima vez. La verdad es que estaba fijándome en lo que decía el terminal:

TypeError: 'type' object has no attribute ' __getitem__'

¿No es confuso? En fin. A veces sólo hay que fijarse mejor en los detalles del programa. La respuesta estaba ahí.

🙂

PREGUNTAS

P: ¿Por qué necesito self cuando creo _init_ u otra función de la clase?

R: Si no usas self, el código cheese = ‘Frank’ es ambiguo. En ese código no está claro si nos referimos al atributo cheese de la instancia o si es una variable local (?) llamada cheese. Con self.cheese = ‘Frank’ queda muy claro que nos estamos refiriendo al atributo de la instancia self.cheese.