[LinuxFocus-icon]
首页  |  站点地图  |  索引  |  搜索

新闻 | 过往期刊 | 链接 | 关于LF
This document is available in: English  Castellano  ChineseGB  Deutsch  Francais  Italiano  Portugues  Turkce  

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

关于作者:

Hilaire Fernandes is the Vice-President of OFSET, an organization to promote the development of 'Free' educational software for the Gnome desktop. He also wrote Dr. Geo, a primer program for dynamic geometry, and is currently working on Dr. Genius - another education program for Gnome.


目录:

 

Developing Applications for Gnome with Python (Part 3)

Gnome

摘要:

This series of articles is specially written for newbie programmers using Gnome and GNU/Linux. Python, the chosen language for development, avoids the usual overhead of compiled languages like C. The information in this article assumes a basic understanding of Python programming. More information on Python and Gnome are available at http://www.python.org and http://www.gnome.org.

Previous articles in the series :
- first article
- second article



 

所需工具:

所需的软件已经列在了这些系列文章的开始部分。

您还需要:

请参看第一部分以获知如何安装及使用 Python-Gnome 和 LibGlade。

 

开发模式

在上一篇文章(第二部分)中,我们为后续的练习创建了一个用户界面 -- Drill 。现在,让我们进 一步了解如何用 Python 进行面向对象开发,以增强 Drill 的功能。在本课,我们暂且放下在 Gnome 下的 Python 开发。

我们继续前次的练习,这次,我们将用在 Drill 中增加一个色彩游戏作为我们的本课的练习。 我们可以通过这个练习了解本课的主题同时为这个主题找到一个解决方案。

 

面向对象开发

简略的说,面向对象开发用对象之间的关系来定义或分类,而不管它们是否实际存在。 We can find comparisons in different domains like the categories of Aristotle, taxonomies, or ontologies. 在每一种情况下,它们中任一个都必须通过抽象表述以理解复杂的情形。我们把这种开发模式称为面向类的开发。

在这种开发模式下,对象由程序控制,并构成程序,我们称之为 类(classes), 并把抽象对象的代表称为 实例(instances). 类由 属性(attributes) 包括值(value) 及 方法(methods) (函数)定义。对于给定的一个类,我们所说的父子关系是指子类可以从基类继承属性。 类通过关系被组织在一起,子类依然与基类具有相同类型。 我们称没有被完整定义的类为抽象类。 当我们声明了一个方法而不去实际定义它(函数体是void),我们称之为虚函数。 一个抽象类含有一个或多个虚函数,因而无法实例化。抽象类允许子类通过重定义纯虚函数来重载基类中的方法。

不同的语言在定义对象时表述不尽相同,但通常如下:

  1. 子类从基类继承属性和方法。
  2. 子类覆盖从基类继承来的方法并重载。
  3. 多重继承,一个子类有多个基类。


 

Python 面向对象开发

在 Python 中,保留字很少,这使得我们在学习面向对象开发的过程中不会因过多的繁杂细节而分心。

在 Python 中,一个对象的方法总是虚函数。这意味着它总是能被子类重载 -- 这正是我们选择面向对象开发的原因, 这种特性也简化了语法。但同时也导致难以分辨一个方法是否被重载。 因而我们无法封装对象,否则其它对象及方法无法访问此对象的属性及方法。 由此得出的结论是,在 Python 中,一个对象的属性对于任何对象来说总是可读写的。

 

基类

在我们的例子中(参看 templateExercice.py), 我们将会定义许多 exercice 类型的对象。我们将用 exercice 类型为以后将创建的练习定义一个基类。 exemple 对象是所有其它将创建的练习的基类。这些练习的派生类将与exercice类具有相同的属性和方法。 这将允许我们控制所有练习的派生类而不去考虑它是哪个对象的实例。

例如,以下将创建一个 exercice 类的实例:

from templateExercice import exercice

monExercice = exercice ()
monExercice.activate (ceWidget)


实际上,我们没有必要去创建 exercice 类的实例,它仅仅是其他类得以继承的一个模板而已。

属性

我们可以根据我们的需要来添加属性,比如:运行的时间。

方法

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"

这段代码包含在文件 templateFichier.py 中,使我们能够明了每个对象的具体作用。方法定义在类 exercice 及实际的函数中。

我们可以看到,参数 area 指向由 LibGlade 创建的一个 GTK+ 部件 -- 一个带有滑块的窗体。



在这个对象中,方法 __init__reset 的内容均为空,如果有必要,将由子类来重载它们。

 

labelExercice,一个 继承 的例子

这几乎是一个空的练习,它只做一件事--将练习的名字显示在 Drill 中。它是我们整个练习的第一步。

就象对象 exercice 一样,对象 labelExercice 定义在文件 labelExercice.py 中。由于它是从对象 exercice 继承而来的, 我们需要告诉它它的父对象是如何定义的。这步仅仅通过一个引用导入(import)即可实现:

from templateExercice import exercice

上述代码的意思是说在文件 templateExercice.py 中定义的类 exercice 被引用至当前代码中。

我们现在面对一个重要的知识点:声明类 labelExercice 为类 exercice 的子类。
labelExercice 由下面的风格来声明:

class labelExercice(exercice):

现在,类 labelExercice 继承了类 exercice 所有的属性和方法。

当然,我们现在还有些工作要做。首先,我们要初始化部件,我们通过重载 __init__ 方法来实现。 (说明:在类 labelExercice 中重新定义它),它将在实例被创建时调用。当然,这个部件由属性exerciceWidget指向, 因此,我们没必要重载类 exercice 中的 activateunactivate 方法。

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

这是我们唯一重载了的方法。我们通过调用下述代码来创建一个 labelExercice 的实例:

monExercice = labelExercice ("Un exercice qui ne fait rien")
(译注:"Un exercice qui ne fait rien" 意思是 "一个什么也不做的练习")

用下面的代码访问它的属性和方法:

# Le nom de l'exercice (译注:exercise 的名字)
print monExercice.exerciceName

# Placer le widget de l'exercice dans le container "area"
# (译注:在容器 "area" 中放置部件)
monExerice.activate (area)
 

colorExercice, 继承 的第二个例子

我们对以前的 color game 做个变化,用类 colorExercice 来实现它。 我们把它的定义放在文件 colorExercice.py 中,在文章的末尾你能找到完整的代码。

这个变化需要我们重新定义和分布变量及函数,将它们合理安排进方法及类 colorExercice 中。

在类的一开始,全局变量被声明为类的属性:

class colorExercice(exercice):
    width, itemToSelect = 200, 8
    selectedItem = rootGroup = None
    # to keep trace of the canvas item
    colorShape = []

类似于类 labelExercice,方法 __init__ 被重载以构造部件:

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 ()

属性 exerciceWidget 指向的 GnomeCanvas 的初始化代码没有什么变化。

另一个需要重载的方法是 reset 。由于它是用来重置的,因此,我们必须定制它:

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

通过使用变量 self 我们可以访问这个实例的方法和属性。只有在方法 buildStarbuildShape 中例外。 参数 k 由整个数值替换。我注意到在 colorExercice.py 中有一个奇怪的现象: 代码所获取的小数数值丢失了。 这个问题看来是由模块 gnome.ui 及 French locale 引起的(小数点由逗号代替)。我将在下一篇文章中讨论这个问题。

 

最后的调整

现在我们有2种 exercise -- labelExercicecolorExercice。我们通过函数 addXXXXExercice 来创建它们的实例。 这些实例在字典 exerciceList 中指向。

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 ()

函数 addGameExercice 通过调用 addExercice 在树形结构中创建带有属性 id="Games/Color" 的叶结点。 这个属性在字典 exerciceList 充当关键字由命令 colorExercice() 创建的实例所使用。

由于面向对象的特性,我们可以在对程序内部毫不知情的情况下去运行这些练习。我们仅仅需要调用基类 exercice 中的抽象方法, 而它们在类 colorExercice 或者类 labelExercice 中有着不同的实现。程序员用相同的方式去看待不同的练习,而回应几乎相同。 我们通过将属性 id ,字典 exerciceList 或者变量 exoSelected 联结在一起来实现这种方式。

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 - Main window of Drill, with the color exercise

在这篇文章的结尾,我们能感受到用 Python 在 GUI 中做面向对象开发的强大吸引力。我们将在下一篇文章中继续我们的练习。

 

附录:完整的源代码

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 ('event', 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


 

对这篇文章发表评论

每篇文章都有各自的反馈页面。在这个页面里,您可以提交评论,也可以查看其他读者的评论:
 反馈页面 

主页由LinuxFocus编辑组维护
© Hilaire Fernandes, FDL
LinuxFocus.org

点击这里向LinuxFocus报告错误或提出意见
翻译信息:
fr --> -- : Hilaire Fernandes <hilaire(at)ofset.org>
fr --> en: Lorne Bailey <sherm_pbody(at)yahoo.com>
en --> zh: Neil <freeneil(at)sohu.com>

2003-04-06, generated by lfparser version 2.25