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.

Ej. 38 : Practicando con listas…

Cuando escribo el código Python mysuff.append(‘hello’), en  realidad se están desencadenando una serie de procesos dentro de Python para que suceda algo a la lista mystuff. Así funciona:

  1. Python ve que indiqué mystuff y revisa esa variable. Puede que revise si anteriormente se creó con un =, si se trata de un parámetro de una función o si es una variable global. En cualquier caso, tiene que encontrar donde aparece por primera vez mystuff.
  2. Una vez encuentra mystuff, llega al operador . (punto) y empieza a revisar las variables que son parte de mystuff. Desde el momento que sabe que mystuff es una lista, también sabe que tiene muchas funciones asociadas.
  3. Luego encuentra append y lo compara con todas las funciones y variables de mystuff. Si append está ahí, la usa.
  4. A continuación, Python ve los paréntesis y dice «¡Oh! Ésto debe de ser una función». En ese momento, llama o ejecuta la función con un parámetro extra.
  5. El parámetro extra es…¡¡mystuff!! Lo sé, es raro, ¿verdad? Pero así es como funciona Python, así que lo mejor es simplemente recordarlo y asumir que eso es lo correcto. Lo que ocurre es que la llamada a la función, en el fondo se procesa de una forma similar a append (mystuff, ‘hello’), en lugar de mystuff.append(‘hello’).

No es necesario saber que ésto está pasando pero me será de ayuda para cuando tenga mensaje de error como éste:

>>> class Thing(object) :

…  def test (hi) :

…  print «hi»

>>> a = Thing ()

>>> a.test («hello»)

Traceback (most recent call last): 

File «<stdin>», line 1, in <module>

TypeError: test () takes exactly 1 argument  (2 given)

>>>

Más adelante lo veré, pero el error significa que Python cambió a.test(«hello») por test(a,»hello») y que en algún lugar alguien se confundió y no añadió el argumento o parámentro para a.

Dedicaré algunos ejercicios para que éste concepto quede claro. Haré éste ejercicio que mezcla cadenas y listas para divertirme un poco.

Ej. 38 - Editor Ej. 38 - Terminal

PREGUNTAS

P: ¿No dijimos que no era recomendable usar bucles while?

R: Las reglas se pueden romper si tienes una buena razón. No hay que ser esclavos de las reglas.

P: ¿Qué hace stuff[3:5]?

R: Obtiene una parte de la lista, del elemento 3 al 4, lo que significa que no incluye el 5. Es similar a como funciona range (3,5).  Aunque no es lo mismo, ¿eh?. Con stuff[3:5] se obtiene una sublista de stuff, tenga lo que tenga, range sólo devuelve números.

P: ¿Por qué no funciona join(‘ ‘, stuff)?

R: Esa forma no tiene sentido. No funciona de esa manera, ya que join es un método asociado a una cadena. Reescríbelo así: ‘ ‘.join(stuff).

+ EJERCICIOS

  1. Toma cada llamada a una función y revísala siguiendo los pasos descritos anteriormente para traducirlo a lo que hace Python. Por ejemplo,   ‘ ‘.join(cosas) es join(‘ ‘ , cosas).
  2. Traduce estas dos formas de ver las llamadas a funciones. Por ejemplo, ‘ ‘.join(cosas) se puede leer como «join cosas aplicado a ‘ ‘ «. Otra forma es «Llamada a join con ‘ ‘ y cosas». En realidad es lo mismo.
  3. Busca en internet sobre PROGRAMACIÓN ORIENTADA A OBJETOS (OOP).
  4. Busca que es class en Python, no en otros lenguajes, que puede dar lugar a confusión.
  5. ¿Cuál es la relación entre dir(algo) y la class de algo?