[LinuxFocus-icon]
<--  | Hogar  | Mapa  | Indice  | Busqueda

Noticias | Arca | Enlaces | Sobre LF
Este documento está disponible en los siguientes idiomas: English  Castellano  ChineseGB  Deutsch  Francais  Italiano  Portugues  

Hilaire Fernandes
por Hilaire Fernandes
<hilaire(at)ofset.org>

Sobre el autor:

Hilaire Fernandes es vicepresidente de OFSET, una organización que promueve el desarrollo de software educativo libre para Gnome. Ha escrito también Dr. Geo, un programa de geometría dinámica, y ahora trabaja en Dr. Genius -otro programa educativo para Gnome.



Traducido al español por:
Francisco Igual Peña <pacodani(at)inicia.es>

Contenidos:

 

Desarrollo de Aplicaciones para Gnome con Python (3ª parte)

Gnome

Resumen:

Esta serie de artículos está específicamente escrita para programadores noveles que usan Gnome y GNU/Linux. Python es el lenguaje escogido para el desarrollo; para entender este artículo son necesarios conocimientos básicos de programación en Python. Hay información disponible sobre Python y Gnome en http://www.python.org y http://www.gnome.org.

Artículos anteriores de esta misma serie:
- primer artículo
- segundo artículo


_________________ _________________ _________________

 

Herramientas necesarias

Las dependencias de software necesarias para ejecutar el programa descrito en este artículo pueden ser consultadas en la lista de la primera entrega de esta serie de artículos

También necesitarás:

Para la instalación y uso de Python-Gnome y LibGlade, consulta la primera parte.

 

Modelo de desarrollo para los ejercicios

En el artículo anterior (segunda parte), creamos la interfaz de usuario -- Drill --, que es el marco para la creación de los ejercicios descritos a continuación. Ahora, nos fijaremos más en profundidad en el desarrollo orientado a objetos usando Python, para añadir funcionalidades a Drill. En este estudio, dejaremos de lado los aspectos de desarrollo Python en Gnome.

Así que sigamos donde lo dejamos, en la inserción de un juego de color en Drill como ejercicio para el lector. Usaremos esto para ilustrar el tema que estamos tratando, y la la vez ofrecer una solución para ese ejercicio.

 

Desarrollo orientado a objetos

Brevemente, sin pretender hacer un análisis exhaustivo, el desarrollo orientado a objetos intenta definir y crear categorías sobre las cosas usando relaciones "es un", sin importar si existen en el mundo real o no. Podemos ver esto como una abstracción de los objetos relacionada con el problema en el que estamos interesados. Podemos encontrar comparaciones en distintos ámbitos, como las categorias de Aristóteles, taxonomías o ontologías. En cada caso, se debe entender una situación compleja a través de una abstracción. Este tipo de desarrollo se podía haber llamado perfectamente desarrolo orientado a categorías.

En este modelo de desarrollo, los objetos manipulados por el programa, o los que constituyen el programa, se llaman clases, y los representantes de estos objetos abstractos son instancias. Las clases se definen mediante atributos (que contienen valores) y métodos (funciones). Hablamos de una relación padre-hijo para una clase dada cuando la clase hijo hereda propiedades de un padre. Las clases se organizan mediante una relación és un', donde un hijo es un tipo del padre y a la vez un tipo del propio hijo. Las clases podrían no estar completamente definidas, en cuyo caso se llaman clases abstractas. Cuando un método es declarado pero no definido, (el cuerpo de la función es void), también se llama un método virtual. Una clase abstracta tiene uno o mas de estos métodos indefinidos y por tanto no puede ser instanciada. Las clases abstractas permiten la especificación de aquello que es tomado por las clases derivadas - las clases hijo en las cuales el método virtual será definido.

Los diferentes lenguajes de programación tienen más o menos elegancia a la hora de definir objetos, pero el denominador común es el siguiente:

  1. Herencia de atributos y métodos de la clase padre por parte del hijo
  2. Habilidad de la clase hijo para sobrecargar los métodos heredados del padre.
  3. Polimorfismo, donde una clase podría tener más de un padre.


 

Python y el desarrollo orientado a objetos

En el caso de Python, este es el mínimo común denominador escogido. Esto permite aprender a desarrollar de forma orientada a objetos sin perderse en los detalles de esta metodología.

En Python, los métodos de un objeto son siempre métodos virtuales. Esto significa que siempre pueden ser sobrecargados por una clase hijo -- que es lo que habitualmente queremos al usar orientación a objetos -- y que simplifica sensiblemente la sintaxis. Pero no es fácil distinguir entre métodos que han sido sobrecargados y los que no. Lo que es más, es imposible crear un objeto opaco y por tanto denegar el acceso a los atributos y métodos desde fuera del objeto. En conclusión, los atributos de un objeto Python pueden ser escritos y leídos desde fuera del objeto.

 

Ejercicio con una clase padre

En nuestro ejemplo (ver el archivo templateExercice.py, queremos definir varios objetos de tipo exercice. Definimos un objeto de tipo exercice para que actúe como clase base abstracta para derivar otros ejercicios que crearemos más tarde. El objeto exemple es la clase padre de todos los otros tipos de ejercicios creados. Estos tipos derivados de ejercicios tendrán al menos los mismos atributos y métodos que la clase exercice porque los heredarán. Esto nos permitirá manipular los diversos tipos de objetos exercice de forma idéntica, sin tener en cuenta el objeto desde el que son instanciados.

Por ejemplo, para crear una instancia de la clase exercice podemos escribir:

from templateExercice import exercice

monExercice = exercice ()
monExercice.activate (ceWidget)


De hecho, no es necesario crear una instancia de la clase exercice porque sólo es una plantilla desde la cual se derivan otras clases.

Atributos

Si estamos interesados en otros aspectos de un ejercicio, podemos añadir atributos, por ejemplo el resultado obtenido o el número de veces que se ha usado.

Métodos

En código Python :

class exercice:
    "A template exercice"
    exerciceWidget = None
    exerciceName = "No Name"
    def __init__ (self):
        "Create the exericice widget"
    def activate (self, area):
        "Set the exercice on the area container"
        area.add (self.exerciceWidget)
    def unactivate (self, area):
        "Remove the exercice fromt the container"
        area.remove (self.exerciceWidget)
    def reset (self):
        "Reset the exercice"

Este código está incluído en su propio archivo templateFichier.py, el cual nos permite clarificar los roles específicos de cada objeto. Los métodos están declarados dentro de la clase exercice, y son de hecho funciones.

Veremos que el argumento area is una referencia a un widget GTK+ construido por LibGlade, que es una ventana deslizante.


En este objeto, los métodos __init__ y reset están vacíos y serán sobrecargados por las clases hijo si es necesario.

 

labelExercice, Primer Ejemplo de Herencia

Este es casi un ejercicio vacío. Sólo hace una cosa: pone el nombre del ejercicio en la zona del ejercicio en Drill. Sirve como un iniciador para los ejercicios que poblarán el árbol de la parte izquierda de Drill, pero que no han sido todavía creados.

De la misma forma que el objeto exercice, el objeto labelExercice se halla en su propio archivo, labelExercice.py. Además, como este objeto es hijo del objeto exercice, necesitamos decirle cómo está definido el padre. Esto se hace simplemente con un import:

from templateExercice import exercice

Esto significa literalmente que la definición de la clase exercice in el archivo templateExercice.py se importa al código actual.

Vamos ahora con el aspecto más importante, la declaraciónd de la clase labelExercice como una clase hija de exercice.
labelExercice se declara de la siguiente manera:

class labelExercice(exercice):

Voilà, con esto es suficiente para que labelExercice herede todos los atributos y métodos de exercice.

Por supuesto, todavía tenemos trabajo que hacer, en particular necesitamos inicializar el widget del ejercicio. Hacemos esto sobrecargando el método __init__ (por ejemplo, redefiniéndolo en la clase labelExercice), que es llamado cuando se crea una instancia. Además, este widget debe ser referenciado en el atributo exerciceWidget para que no tengamos que sobrecargar los métodos activate y unactivate de la clase padre exercice.

  def __init__ (self, name):
      self.exerciceName = "Un exercice vide" (Trans. note: an empty exercise)
      self.exerciceWidget = GtkLabel (name)
      self.exerciceWidget.show ()

Este es el único método que sobrecargamos. Para crear una instancia de labelExercice, haremos:

monExercice = labelExercice ("Un exercice qui ne fait rien")
(Translator Note: "Un exercice qui ne fait rien" means "an exercise doing nothing")

Para acceder a sus atributos o métodos:

# Le nom de léxercice (Nota del traductor: nombre del ejercicio.)
print monExercice.exerciceName

# Placer le widget de léxercice dans le container "area"
# (Nota del traductor: situar el widget del ejercicio en el contenedor "area".)
monExerice.activate (area)
 

colorExercice, Segundo Ejemplo de Herencia

Aquí empezamos la transformación del juego de color visto en el primer artículo de la serie en una clase del tipo exercice a la que llamaremos colorExercice. La colocamos en su propio fichero, colorExercice.py, que acompaña a este artículo con el código fuente completo.

Los cambios requeridos sobre el código inicial consisten básicamente en una redistribución de funciones y variables en métodos y atributos en la clase colorExercice.

Las variables globales se transforman en atributos declarados al inicio de la clase:

class colorExercice(exercice):
    width, itemToSelect = 200, 8
    selectedItem = rootGroup = None

    colorShape = []

Igual que para la clase labelExercice, el método __init__ se sobrecarga para hacer más cómoda la construcción de los widgets de ejercicio :

def __init__ (self):
	 self.exerciceName = "Le jeu de couleur" # Nota del tr.: el juego de color
    self.exerciceWidget = GnomeCanvas ()
    self.rootGroup = self.exerciceWidget.root ()
    self.buildGameArea ()
    self.exerciceWidget.set_usize (self.width,self.width)
    self.exerciceWidget.set_scroll_region (0, 0, self.width, self.width)
    self.exerciceWidget.show ()

Nada nuevo comparado con el código inicial, sólo se referencia el GnomeCanvas en el atributo exerciceWidget.

El otro método sobrecargado es reset. Como inicializa el juego a cero, debe ser modificado para adaptarlo al juego de color:

    def reset (self):
        for item in self.colorShape:
            item.destroy ()
        del self.colorShape[0:]
        self.buildGameArea ()

Los otros métodos son copias directas de las funciones originales, con el único añadido del uso de la variable self para permitir el acceso a los atributos y métodos de la instancia. Hay una excepción en los métodos buildStar y buildShape, donde el parámetro decimal k se sustituye por un número entero. Noté un comportamiento extraño en el documento colorExercice.py, donde los números decimales tomados por el código fuente eran cortados. El problema parece ser del módulo gnome.ui, y para las locales Francesas (en las que los números decimales usan oma para separar en vez de punto). Estoy trabajando para encontrar la fuente del problema antes del próximo artículo.

 

Ajustes finales en Drill

We now have two types of exercise -- labelExercice and colorExercice. We create instances of them with the functions addXXXXExercice in the code drill1.py. The instances are referenced in a dictionary exerciceList in which the keys are also arguments to the pages of each exercise in the tree at left:

def addExercice (category, title, id):
    item = GtkTreeItem (title)
    item.set_data ("id", id)
    category.append (item)
    item.show ()
    item.connect ("select", selectTreeItem)
    item.connect ("deselect", deselectTreeItem)
[...]
def addGameExercice ():
    global exerciceList
    subtree = addSubtree ("Jeux")
    addExercice (subtree, "Couleur", "Games/Color")
    exerciceList ["Games/Color"] = colorExercice ()

La función addGameExercice crea una hoja en el árbol con el atributo id = "Games/Color" llamando a la función addExercice. Este atributo se usa como una llave para la instancia del juego de color creada por la órden colorExercice() en el diccionario exerciceList.

A continuación, gracias a la elegancia del polimorfismo en el desarrollo orientado a objetos, podemos ejecutar los ejercicios usando las mismas funciones que actúan de forma diferente para cada objeto sin necesidad de preocuparse de su implementación interna. Sólo llamamos a métodos definidos en la clase base abstracta exercice, y ellos hacen diferentes cosas en las clases colorExercice o labelExercice. El programador "habla" a todos los ejercicios de la misma forma, aún si la respuesta de cada ejercicio es diferente. Para hacer esto, combinamos el uso del atributo id de las páginas del árbol y del diccionario exerciceList o de la variable exoSelected que referencia al ejercicio en uso. Dado que todos los ejercicios son hijos de la clase exercice, usamos sus métodos de la misma forma para controlar los ejercicios en toda su variedad.

def on_new_activate (obj):
    global exoSelected
    if exoSelected != None:
        exoSelected.reset ()

def selectTreeItem (item):
    global exoArea, exoSelected, exerciceList
    exoSelected = exerciceList [item.get_data ("id")]
    exoSelected.activate (exoArea)

def deselectTreeItem (item):
    global exoArea, exerciceList
    exerciceList [item.get_data ("id")].unactivate (exoArea)


[Main window of Drill]
Fig. 1 - Ventana principal de Drill, con el ejercicio del color.

Así termina nuestro artículo. Hemos descubierto los atractivos del desarrollo orientado a objetos en Python con el añadido de una interfaz gráfica de usuario. En los próximos artículos, continuaremos descubriendo los widgets de Gnome escribiendo nuevos ejercicios que insertaremos en Drill.

 

Apéndice: Código Fuente Completo.

drill1.py

#!/usr/bin/python
# Drill - Teo Serie
# Copyright Hilaire Fernandes 2002
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org


from gnome.ui import *
from libglade import *

# Import the exercice class
from colorExercice import *
from labelExercice import *

exerciceTree = currentExercice = None
# The exercice holder
exoArea = None
exoSelected = None
exerciceList = {}

def on_about_activate(obj):
    "display the about dialog"
    about = GladeXML ("drill.glade", "about").get_widget ("about")
    about.show ()

def on_new_activate (obj):
    global exoSelected
    if exoSelected != None:
        exoSelected.reset ()

def selectTreeItem (item):
    global exoArea, exoSelected, exerciceList
    exoSelected = exerciceList [item.get_data ("id")]
    exoSelected.activate (exoArea)

def deselectTreeItem (item):
    global exoArea, exerciceList
    exerciceList [item.get_data ("id")].unactivate (exoArea)

def addSubtree (name):
    global exerciceTree
    subTree = GtkTree ()
    item = GtkTreeItem (name)
    exerciceTree.append (item)
    item.set_subtree (subTree)
    item.show ()
    return subTree

def addExercice (category, title, id):
    item = GtkTreeItem (title)
    item.set_data ("id", id)
    category.append (item)
    item.show ()
    item.connect ("select", selectTreeItem)
    item.connect ("deselect", deselectTreeItem)


def addMathExercice ():
    global exerciceList
    subtree = addSubtree ("Mathématiques")
    addExercice (subtree, "Exercice 1", "Math/Ex1")
    exerciceList ["Math/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Math. Ex2")
    exerciceList ["Math/Ex2"] = labelExercice ("Exercice 2")

def addFrenchExercice ():
    global exerciceList
    subtree = addSubtree ("Français")
    addExercice (subtree, "Exercice 1", "French/Ex1")
    exerciceList ["French/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "French/Ex2")
    exerciceList ["French/Ex2"] = labelExercice ("Exercice 2")

def addHistoryExercice ():
    global exerciceList
    subtree = addSubtree ("Histoire")
    addExercice (subtree, "Exercice 1", "Histoiry/Ex1")
    exerciceList ["History/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Histoiry/Ex2")
    exerciceList ["History/Ex2"] = labelExercice ("Exercice 2")

def addGeographyExercice ():
    global exerciceList
    subtree = addSubtree ("Géographie")
    addExercice (subtree, "Exercice 1", "Geography/Ex1")
    exerciceList ["Geography/Ex1"] = labelExercice ("Exercice 1")
    addExercice (subtree, "Exercice 2", "Geography/Ex2")
    exerciceList ["Geography/Ex2"] = labelExercice ("Exercice 2")

def addGameExercice ():
    global exerciceList
    subtree = addSubtree ("Jeux")
    addExercice (subtree, "Couleur", "Games/Color")
    exerciceList ["Games/Color"] = colorExercice ()


def initDrill ():
    global exerciceTree, label, exoArea
    wTree = GladeXML ("drill.glade", "drillApp")
    dic = {"on_about_activate": on_about_activate,
           "on_exit_activate": mainquit,
           "on_new_activate": on_new_activate}
    wTree.signal_autoconnect (dic)
    exerciceTree = wTree.get_widget ("exerciceTree")
    # Temporary until we implement real exercice
    exoArea = wTree.get_widget ("exoArea")
    # Free the GladeXML tree
    wTree.destroy ()
    # Add the exercice
    addMathExercice ()
    addFrenchExercice ()
    addHistoryExercice ()
    addGeographyExercice ()
    addGameExercice ()

initDrill ()
mainloop ()


templateExercice.py

# Exercice pure virtual class
# exercice class methods should be override
# when exercice class is derived
class exercice:
    "A template exercice"
    exerciceWidget = None
    exerciceName = "No Name"
    def __init__ (self):
        "Create the exericice widget"
    def activate (self, area):
        "Set the exercice on the area container"
        area.add (self.exerciceWidget)
    def unactivate (self, area):
        "Remove the exercice fromt the container"
        area.remove (self.exerciceWidget)
    def reset (self):
        "Reset the exercice"


labelExercice.py

# Dummy Exercice - Teo Serie
# Copyright Hilaire Fernandes 2001
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org

from gtk import *
from templateExercice import exercice

class labelExercice(exercice):
    "A dummy exercie, it just prints a label in the exercice area"
    def __init__ (self, name):
        self.exerciceName = "Un exercice vide"
        self.exerciceWidget = GtkLabel (name)
        self.exerciceWidget.show ()


colorExercice.py

# Color Exercice - Teo Serie
# Copyright Hilaire Fernandes 2001
# Release under the terms of the GPL licence
# You can get a copy of the license at http://www.gnu.org

from math import cos, sin, pi
from whrandom import randint
from GDK import *
from gnome.ui import *

from templateExercice import exercice


# Exercice 1 : color game

class colorExercice(exercice):
    width, itemToSelect = 200, 8
    selectedItem = rootGroup = None
    # to keep trace of the canvas item
    colorShape = []
    def __init__ (self):
        self.exerciceName = "Le jeu de couleur"
        self.exerciceWidget = GnomeCanvas ()
        self.rootGroup = self.exerciceWidget.root ()
        self.buildGameArea ()
        self.exerciceWidget.set_usize (self.width,self.width)
        self.exerciceWidget.set_scroll_region (0, 0, self.width, self.width)
        self.exerciceWidget.show ()
    def reset (self):
        for item in self.colorShape:
            item.destroy ()
        del self.colorShape[0:]
        self.buildGameArea ()
    def shapeEvent (self, item, event):
        if event.type == ENTER_NOTIFY and self.selectedItem != item:
            item.set(outline_color = 'white') #highligh outline
        elif event.type == LEAVE_NOTIFY and self.selectedItem != item:
            item.set(outline_color = 'black') #unlight outline
        elif event.type == BUTTON_PRESS:
            if not self.selectedItem:
                item.set (outline_color = 'white')
                self.selectedItem = item
            elif item['fill_color_gdk'] == self.selectedItem['fill_color_gdk'] \
                 and item != self.selectedItem:
                item.destroy ()
                self.selectedItem.destroy ()
                self.colorShape.remove (item)
                self.colorShape.remove (self.selectedItem)
                self.selectedItem, self.itemToSelect = None, \
                 self.itemToSelect - 1
                if self.itemToSelect == 0:
                    self.buildGameArea ()
        return 1

    def buildShape (self,group, number, type, color):
        "build a shape of 'type' and 'color'"
        w = self.width / 4
        x, y, r = (number % 4) * w + w / 2, (number / 4) * w + w / 2, w / 2 - 2
        if type == 'circle':
            item = self.buildCircle (group, x, y, r, color)
        elif type == 'squarre':
            item = self.buildSquare (group, x, y, r, color)
        elif type == 'star':
            item = self.buildStar (group, x, y, r, 2, randint (3, 15), color)
        elif type == 'star2':
            item = self.buildStar (group, x, y, r, 3, randint (3, 15), color)
        item.connect (évent', self.shapeEvent)
        self.colorShape.append (item)

    def buildCircle (self,group, x, y, r, color):
        item = group.add ("ellipse", x1 = x - r, y1 = y - r,
                          x2 = x + r, y2 = y + r, fill_color = color,
                          outline_color = "black", width_units = 2.5)
        return item

    def buildSquare (self,group, x, y, a, color):
        item = group.add ("rect", x1 = x - a, y1 = y - a,
                          x2 = x + a, y2 = y + a, fill_color = color,
                          outline_color = "black", width_units = 2.5)
        return item

    def buildStar (self,group, x, y, r, k, n, color):
        "k: factor to get the internal radius"
        "n: number of branch"
        angleCenter = 2 * pi / n
        pts = []
        for i in range (n):
            pts.append (x + r * cos (i * angleCenter))
            pts.append (y + r * sin (i * angleCenter))
            pts.append (x + r / k * cos (i * angleCenter + angleCenter / 2))
            pts.append (y + r / k * sin (i * angleCenter + angleCenter / 2))
        pts.append (pts[0])
        pts.append (pts[1])
        item = group.add ("polygon", points = pts, fill_color = color,
                          outline_color = "black", width_units = 2.5)
        return item

    def getEmptyCell (self,l, n):
        "get the n-th non null element of l"
        length, i = len (l), 0
        while i < length:
            if l[i] == 0:
                n = n - 1
            if n < 0:
                return i
            i = i + 1
        return i

    def buildGameArea (self):
        itemColor = ['red', 'yellow', 'green', 'brown', 'blue', 'magenta',
                     'darkgreen', 'bisque1']
        itemShape = ['circle', 'squarre', 'star', 'star2']
        emptyCell = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
        self.itemToSelect, i, self.selectedItem = 8, 15, None
        for color in itemColor:
            # two items of same color
            n = 2
            while n > 0:
                cellRandom = randint (0, i)
                cellNumber = self.getEmptyCell (emptyCell, cellRandom)
                emptyCell[cellNumber] = 1
                self.buildShape (self.rootGroup, cellNumber, \
                 itemShape[randint (0, 3)], color)
                i, n = i - 1, n - 1


 

Formulario de "talkback" para este artículo

Cada artículo tiene su propia página de "talkback". A través de esa página puedes enviar un comentario o consultar los comentarios de otros lectores
 Ir a la página de "talkback" 

<--, regresar al índice de este número

Contactar con el equipo de LinuFocus
© Hilaire Fernandes, FDL
LinuxFocus.org
Información sobre la traducción:
fr --> -- : Hilaire Fernandes <hilaire(at)ofset.org>
fr --> en: Lorne Bailey <sherm_pbody(at)yahoo.com>
en --> es: Francisco Igual Peña <pacodani(at)inicia.es>

2004-06-14, generated by lfparser version 2.43