我的日志

就在这里写写日志吧. :) 在Yahoo开辟正式的天地了 http://360.yahoo.com/JerryMarx_Python

1. 2004年9月

1.1. 9月7日

指南针在这里星期算是启动了,当然目前还是做一些准备工作.翻译Twisted文档是第一件事情了.这几日忙着帮朋友写PHP,翻译的时间少了很多,不过怎么样也要在星期天之前找时间把Low-Level部分翻译完,拖的时间太久了.

写了几天的PHP觉得其实PHP也挺有意思的. :) 有一些地方和Python比较类似,可能脚本语言都是这样吧,不过我没有涉及过其它的脚本语言,也不好多说什么.教本语言一般都有自己的内存管理机制吧,让写惯C++的我感觉很是爽,少了很多的麻烦事.

昨天看到群里面有人问怎么安装STLPort和Boost.又想起我自己的计划,最近是没有时间弄这个事情了.再等等吧...

2. 2004年10月

下面是转载limoduo的一些文章.仔细学习一下

2.1. Mix-in技术介绍

mix-in技术,中文不知道应该如何称呼,但意思好象是混入。它的作用是,在运行期间 ,动态改变类的基类或类的方法,从而使得类的表现可以发生变化。可以用在一个通用类接口中,?br>莶煌难≡袷褂貌煌牡筒憷嗍迪郑卟憷嗖挥梅⑸浠6艺庖皇迪挚梢栽谠诵泄讨卸 懈谋洹S捎谖乙彩歉湛吹剑蠹矣形侍饪梢杂胛医薪涣鳌?a href="http://www2.linuxjournal.com/lj-issues/issue84/4540.html">这就是我看到的文章的链 接。前面的一个贴子,dont cry out loud褪怯胫泄?

下面再详细向大家介绍一下:

有一个类,

class foo:
    pass

我可以定义另外一个类,

class foobase:
    def hello(self):
        print "hello"

如果我直接调用:

>>> obj=foo()
>>> obj.hello()

这时你会看到出错。那么我可以这样:

>>> foo.__bases__ +=(foobase,)
>>> obj.hello()
hello

成功了。原理是,每个类都有一个bases属性,它是一个tuple,用来存放所有的基类。而且在 运行中,可以动态改变。所以当我们向其中增加新的基类时,再次调用原来不存在的函数,由于基 类中的函数已经存在了,所以这次成功了。

这是一个最简单的应用,可以看到我们可以动态改变类的基类。有几个注意事项要说一下:

  1. bases是一个tuple,所以增加一个值要使用tuple类型,而单个元素tuple的写法为(foobase,)

  2. 类必须先存在。所以,如果想使用这一技术,先要将相关的类的模块导入(import)。

由于mix-in是一种动态技术,以多继承,对象为基础,而python正好是这样的语言,使得在python 中实现这一技术非常容易。

2.2. Mix-in技术介绍(续一)

前一篇文章,简单地向大家介绍了一下mix-in技术,它实现了基类的动态 增加。这样 我们就可以在运行时,根据选择可以动态地增加基类,从而 实现不同的目的。现在 还有一个问题,就是,在基类与派生类中都有同 名的函数,要如何处理呢?

在Python中,如果派生类中有与基类同名的函数,那么调用函数时,会调 用派生类的函数,而不是基类的函数,可以测试一下:

>>> class foobase:
        def a(self):
                print "hello"

>>> class foo(foobase):
        def a(self):
                print "foo"

>>> c=foo()
>>> c.a()
foo

可以看出,执行的是foo类的函数。这样在使用mix-in技术时,如果原来 的类中存在与Mix类中同名的函数,那么Mix类中的函数不会运行,如果 想对其进行替换怎么办呢?方法就是使用getattr()和setattr()函数。当然 还是最简单的,更复杂更通用的以后再讨论。

定义两个类:

>>> class foobase:
        def a(self):
                print "hello"

>>> class foo:
        def a(self):
                print "foo"

>>> f=getattr(foobase, "a") 
>>> setattr(foo, "a", f.im_func) #f.im_func会得到真正的函数对象 
>>> c=foo() 
>>> c.a() 
hello

可以看到,函数被替换了。

注意,使用dir(f)还会看到其它的属性im_class,它表示这个函数属于哪个类, im_self表示属于哪个实例

setattr(object,name,func) 这个函数会给object的name函数引用重新定义为func。即原来name所指的内容发生了变化。 你可以自已定义一个类如

class foo:
    def a(self):pass

f=getattr(foo, "a") 
dir(f) 

你会看到有一些属性,其中有im_class,im_func,im_self属性。其中im_func是真正的函数引用(?br>部梢越泻韵螅蛭赑ython中都是对象) 使用setattr可以将类foo的函数a的实际函数对象替换成别的东西,象文章中所说的。 使用setattr(foo, "a", f.im_func)

如果foo不是一个类,而是一个模块,你使用getattr()后,再使用dir()不会看到im_func函数。

2.3. Mix-in技术介绍(续二)

前面讲了基本的实现技术,下面给大家介绍一个mix-in安装函数,这个函 数是从前面所说的文章copy下来的。

   1 import types
   2 
   3 def MixIn(pyClass, mixInClass, makeAncestor=0):
   4    if makeAncestor:
   5      if mixInClass not in pyClass.__bases__:
   6         pyClass.__bases__ = (mixInClass,) + pyClass.__bases__
   7    else:
   8      # Recursively traverse the mix-in ancestor
   9      # classes in order to support inheritance
  10      baseClasses = list(mixInClass.__bases__)
  11      baseClasses.reverse()
  12      for baseClass in baseClasses:
  13         MixIn(pyClass, baseClass)
  14 
  15      # Install the mix-in methods into the class
  16      for name in dir(mixInClass):
  17         if not name.startswith('__'):
  18         # skip private members
  19            member = getattr(mixInClass, name)
  20            if type(member) is types.MethodType:
  21                member = member.im_func
  22            setattr(pyClass, name, member)

这个函数可以将某个mix-in类安装为指定类的基类,同时可以通过关键 字参数指定在基类中的顺序,是最前还是最后。因为Python在处理基类 时,是安顺序进行的,所以安装在最前则优先级最高。同时对于指定类 的方法如果在mix-in类中存在,则将指定类中的方法替换成mix-in类中的方法。

   1    if makeAncestor:
   2      if mixInClass not in pyClass.__bases__:
   3         pyClass.__bases__ = (mixInClass,) + pyClass.__bases__

如果makeAncestor为1,表示是安装在最前,则首先判断在pyClass的基 类中是否存在mixInClass类,如果不存在,再进行安装。

   1    else:
   2      # Recursively traverse the mix-in ancestor
   3      # classes in order to support inheritance
   4      baseClasses = list(mixInClass.__bases__)
   5      baseClasses.reverse()
   6      for baseClass in baseClasses:
   7         MixIn(pyClass, baseClass)

如果makeAncestor为0,并不将mixInClass安装在最后,原作者说他在 实际中没有这样用的。那么它完成什么任务呢?它实际完成了一个递归, 即从mixInClass的最底层的基类开始(因为mixInClass也可能是多重继承 而来的),对pyClass中也存在的函数进行替换。这样执行完毕后, mixInClass类中,包含所有基类中的函数,如果有与pyClass类中的函 数重名的,都将pyClass中的函数替换成mixInClass相应的函数。(有 些复杂!)

   1      # Install the mix-in methods into the class
   2      for name in dir(mixInClass):
   3         if not name.startswith('__'):
   4         # skip private members
   5            member = getattr(mixInClass, name)
   6            if type(member) is types.MethodType:
   7                member = member.im_func
   8            setattr(pyClass, name, member)

这步完成重名函数的替换。首先去掉私有方法(私有方法名前有双下划线). 得到mixInClass类中的指定名字的方法对象,判断是否为方法类型。 因为还有可能取到属性。在types模块中包含了一些类型,可以用它 来判断是否为方法类型。对于方法对象,如果是类方法,实际的函数 应使用它的属性im_func。然后将pyClass相应的方法替换成 mixInClass中的方法。

这样就将mixInClass安装为pyClass的基类了。

使用例子如:

   1 from classa import classa
   2 from classb import classb
   3 MixIn(classa, classb) #将classb安装为classa的基类

2.4. Mix-in技术与分布类编程

mix-in技术与分布类编程 作者:limodou

大家一看到这个题目,看到“分布类编程”可能会认为是一种什么新技术,其实只不过是我个人所创,是指一个类的实现由多个文件(或模块)组成。至于它如何构成,有何作用,及相应的实例且听我慢慢道来。

mix-in技术简介 关于mix-in技术本人有专门的文章讲述,这里就不再赘述,而只进行简单地介绍。[1]

如果我们在运行时改变一个类的基类或类的方法、属性等就叫做mix-in。那么它与类派生和重载有什么区别呢?根本的区别就是它的动态性。派生和重载是在程序运行前就已经对类进行了修改,它的改变是确切存在于某个文件中的,这种改变在运行时是稳定的。而mix-in是在运行时才对相应的类发生作用,其运行时的表现与文件中所描述可能有所区别,而且随着运行环境的不同其表现也可以发生变化。另外,对于派生后的类,在运行时创建其一个实例就可以使用了。而使用mix-in技术,我们需要将新的基类或方法先加入到原来的类中,然后再创建实例进行应用。

mix-in技术的主要实现方法有两种:加入基类和加入方法。加入基类则相当于从基类进行派生,从而使原来的类具有基类的方法和属性。加入方法使来的类具有新的方法,如果存在加入的方法与原类中的某个方法同名,则新的方法将替换原来的方法(这个规则不是必然的,因为mix-in的实现是你自已编写的,而不是固定不变的)。加入基类可以修改类的bases属性。加入方法可以使用内置函数getattr()和setattr()来实现。那么对于这种mix-in技术在实现时还要考虑当新的方法与原方法重名的处理。

之所以会有这种伟大的mix-in技术的存在,完全要得益于Python语言的动态性(当然可能不止一种语言能够实现这种技术),它允许你在运行时修改类所有的属性,也可以增加新的属性。通过setattr()我们就可以向一个类增加新的方法,并且使用它。

分布类的构成 类是对象的定义和描述,通过类我们就可以生成实例,即对象,用它进行处理。分布类,即表明类的定义不是一次性定义出来,而是分布在不同的文件或模块(以后我们就叫它们为分布类文件或分布类模块)中定义的。可能的一种类结构的分布如下:

+----原始类 
+----基类1 
+... 
+----基类n 
+----新功能1 
+... 
+----新功能2 
+----mix-in模块 

上面的意思就是在一个目录下,有一个原始类,可能有n个基类模块,有n个新功能模块。最后为了实现mix-in技术还需要一个mix-in模块,而这个模块可以是你自已编写的。更复杂的结构可能是原始类下还有子目录,但处理方法都是一样的。

在进行基类与新功能的编程时要考虑重名方法的情况。在新功能模块中,方法定义应按照类的方法进行定义,即第一个参数应为对象实例参数,如self。

在运行时,应先使用mix-in模块中的处理函数将基类与新功能加入到原始类中,然后再生成类的实例,进行使用。

大家可以看到,将类分成多个文件进行编程并不复杂,而mix-in的处理是比较复杂的,应根据实际情况进行处理。

分布类的作用 为什么要使用分布类呢?就我个人来说,主要有以下的好处:

清晰。把一个类的不同功能分组放在不同的模块中,可以清晰地看出类的构成,容易理解,维护起来相对容易。我们在写程序时,可以将最核心的功能写在一个文件中,在其中定义类的属性,类的初始化,及一些最基本的功能。然后对于类的扩展,我们可以根据功能分组放到不同的文件中。这样使得每个文件所描述的功能相对集中,而不再是拥肿的代码。 灵活。如果我们的类的功能耦合性非常小,那么增加、卸载功能都是相当的容易,只要把相应的文件加入或去掉就可以了。在调试程序和定位错误时可能会很方便。如果我们想使某些功能有效,就加入相应的文件,反之,去掉相应的文件。如果发现有错误,可以通过一个文件一个文件地减少,从而进行一个文件一个文件地进行排除。 那么可能带来的不便是:文件变多,没有一个完整的静态描述。那么如果喜欢静态类,我们完全可以在类的功能测试完全之后再合并成静态类。至于需不需要这样做完全看你自已了。

使用实例 下面举一个本人所写的FlyEdit程序中所用到的例子。FlyEdit是一个编辑器,它的基本结构就是建立在分布类的基础之上,其中还有一些特别的处理。FlyEdit的分布类模块放在两个目录下,一个为modules,另一个在plugin中。modules为相对核心的部分;plugin中为可由用户自行编写的插件功能模块。在FlyEdit中,最主要的一个类是编辑器类(Editor),它实现了编辑器的基本功能,同时规定了分布类模块某些功能的执行位置(如分布类模块的初始化就放在Editor类的初始化函数内,而且放在所有的可视控件初始化完成之后)。这个类放在主程序flyedit.pyw中。同时flyedit.pyw中在生成编辑器类时先进行mix-in的处理,然后再生成类的实例。

   1 import register
   2 register.registerpackage(Editor, 'modules')
   3 register.registerpackage(Editor, 'plugin')
   4 root=Tk()
   5 
   6 from windowlist import windowlist
   7 windowlist=windowlist(root)
   8 Editor(root, file, windowlist)
   9 root.mainloop()

第1行导入register模块。它是我写的实现mix-in功能的模块。

第2,3行,对modules子目录和plugin子目录下的文件进行mix-in处理,将新功能加到Editor类上。在分布类模块中不仅有新的方法,还有一些新增的数据,如菜单等。这样为了使分布类的加入顺序不变,将modules和plugin目录做成python包的形式进行处理(即加入init.py文件);同时在包的init.py文件中定义了all列表,用来说明哪些模块应该mix-in,并且加入的顺序如何。

第8行,使用新的类创建实例。

下面举一个这样的包结构。

+----modules 
+----__init__.py 
+----a.py 
+----b.py 

对于init.py文件,内容可能为:

all=['a', 'b'] 在modules目录下共有三个文件,其中init.py文件记录了哪些模块将mix-in到待处理的类中去。它通过定义一个全局变量all来实现。all为一个列表,每一项记录了要mix-in到类中去的模块文件名(没有.py的后缀);同时列表的顺序表明每个模块mix-in的顺序。a.py和b.py即为真正的分布类模块。从all的内容上看,先处理a.py再处理b.py。如果想改变处理顺序或去掉哪些模块,则只要修改all的列表值即可,而不用将相应的文件删除。

对于分布类模块,如a.py中应定义要mix-in到类中去的方法或属性。同时也应定义一个all列表,其中列出所有要放入到类中去的方法、属性的字符串名称。那么在进行mix-in处理时,只有在all中定义的方法或属性才会被处理。

那么mix-in过程是如何实现的呢?下面将FlyEdit中所用到的register模块简述一下。

   1 #register functions to a class 
   2 def registerpackage(class_, modulename):
   3     module=__import__(modulename)
   4     if hasattr(module, '__all__'):
   5         for i in module.__all__:
   6             register(class_, modulename+'.'+i)
   7 
   8 def register(class_, modulename):
   9     import types
  10     module=my_import(modulename)
  11     if hasattr(module, 'init'):
  12         value=getattr(module, 'init')
  13         class_.initlist.append(value)
  14     if not hasattr(module, '__all__'): return
  15     for name in module.__all__:
  16         property=getattr(module, name)
  17         t=type(property)
  18         if t in (types.DictType, types.TupleType, types.ListType):
  19             if hasattr(class_, name):
  20                 value=getattr(class_, name)
  21                 if t == types.DictType :
  22                     value.update(property)
  23                 else:
  24                     value=value+property
  25             else:
  26                 value=property
  27             setattr(class_, name, value)
  28         else:
  29             if hasattr(class_, name): #exist a func 
  30                 delattr(class_, name)
  31             setattr(class_, name, property)
  32 
  33     def my_import(name):
  34         mod = __import__(name)
  35         components = name.split('.')
  36         for comp in components[1:]:
  37             mod = getattr(mod, comp)
  38         return mod

02行,定义函数registerpackage,用于将一个包结构mix-in到类上。

03行,导入包。由于这里使用字符串名字作为包名,故应使用import内置函数。

4行到6行,判断如果包中含有all属性(变量),则依次取出all的值(每个值为一个模块名),然后把每个模块名与包名相接,生成“包名.模块名”的可调用的形式,再调用register函数mix-in到类中。

08行,定义将某个模块mix-in到指定类的函数。

10行,导入真正的模块。使用import函数时,对形如“包名.模块名”的模块,只会返回包对象,而不是最后的模块对象。故调用一个可以真正返回模块对象的函数my_import。

11行到13行,判断模块是否有init属性,如果有则取出放到类的初始化列表中(这个处理是针对FlyEdit所特有的,主要是想解决某些模块需初始的问题,在你的应用中可以不使用)。

14行,判断模块如果没有all属性,则不会进行mix-in处理。这样,只有定义在all列表中的内容才会被mix-in,而其它的内容会被看到“私有”内容对待而不会mix-in。

从15行到31行,是真正的mix-in的处理。处理的对象是all列表中所有的元素。

16行,根据属性的字符串名,取出分布类模块中相应的属性。

17行,得到属性相应的类型。

18行到27行,如果属性类型为字典,元组(tuple),列表时,当类已经存在同名的值时,将新值添加到原值中;当不存在同名的值时,将新值加入到类中即可。

28行到31行,如果属性为其它类型,则先删除原值,再增加新值。

33行到38行,实现将“包名.模块名”形式的模块导入,返回模块对象。此处不详说了。

结论 分布类编程在软件开发阶段可以作为一种调试与功能编写的有力方法。同时在软件应用阶段可以作为一种插件技术而使用。大家要注意的是,本文实例中所介绍的具体的分布类的构成与mix-in方法的实现都是基于FlyEdit的实现。大家在实现自已的项目时应使用自已的方式。