|
|
This document is available in: English Castellano ChineseGB Deutsch Francais Italiano Portugues |
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. 目录: |
摘要:
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),我们称之为虚函数。 一个抽象类含有一个或多个虚函数,因而无法实例化。抽象类允许子类通过重定义纯虚函数来重载基类中的方法。
不同的语言在定义对象时表述不尽相同,但通常如下:
在 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 的内容均为空,如果有必要,将由子类来重载它们。
这几乎是一个空的练习,它只做一件事--将练习的名字显示在 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 中的 activate 及 unactivate 方法。
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) |
我们对以前的 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 我们可以访问这个实例的方法和属性。只有在方法 buildStar 及 buildShape 中例外。 参数 k 由整个数值替换。我注意到在 colorExercice.py 中有一个奇怪的现象: 代码所获取的小数数值丢失了。 这个问题看来是由模块 gnome.ui 及 French locale 引起的(小数点由逗号代替)。我将在下一篇文章中讨论这个问题。
现在我们有2种 exercise -- labelExercice 及 colorExercice。我们通过函数 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) |
在这篇文章的结尾,我们能感受到用 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报告错误或提出意见 |
翻译信息:
|
2003-04-06, generated by lfparser version 2.25