2005年02月28日

本文是庄秀丽老师在2005年2月26日啄木鸟社区周例会上与大家交流的内容整理后的文章。非常值得学习。

planet是一种把blog信息聚合的一种手段,啄木鸟社区现在也已经提供这样的服务,不过没有正式公开,内容目前只是与 Python 相关的blog,至于今后会是什么样,还要等大家讨论来决定。当然,东西刚建立起来,由ZoomQuiet建议,还可能存在问题,可以与ZoomQuiet联系,如果你是一个python爱好者,希望把你的RSS放在planet中,那么请与ZoomQuiet联系。

啄木鸟社区的planet访问

2005年02月27日

说实在的,整个会议由于家里有人,再加上网络及转播效果,因此我本人并未听到多少。我的录音效果也并不是很好。好在ZoomQuiet录制,要仔细听一听才知道。

本次会议邀请的两位嘉宾应约前来,很守时,准备也很充分。庄老师的发言全文可以看这里。会议的录音及反馈可以去啄木鸟社区会议页面去下载或在线收听。

在会后已经有人进行了思考相关内容也同见上面的页面。

如果大家还有什么想法和好的建议都希望在wiki中有所体现。会议总的是成功的,希望以后的录单效果会更好。

这是天成写的心得:《对于知识管理的社会性软件项目实现的一些思考

2005年02月25日

Q:一个模块A调用一个模块B,那么B如何访问模块A中的数据

A:非常简单,在模块B中导入A模块即可

其实这根本可以不认为是一个技巧,很多人也许就是这样做的。之所以写出来,只是提醒大家,你想到的可能就是可行的办法。因为之前我遇到过这样的问题,也想到这种方法,但感觉似乎并不好。但看了 CherryPy 的源码之后,它就是这样做的。因此我想这其实就是一种可行的办法。

2005年02月24日

最近忙于作一个生成测试用的XML报文的工具。在这个工具中我有几个面板,它们是同时存在的。其中一个面板是用来管理数据字典的,它可以进行编辑,在其它的面板上有这些数据字典的一个显示,不能进行编辑,它们反映的是同一个数据源。同时,这个工具还可以根据交易报文导入交易表中,同时会把每个交易字段加入到数据字典中(如果这个字段在数据字典中不存在的话),这样,数据字典就还有可能发生变化。面板之间可以相互切换。那么我面临的一个问题就是如何处理当数据字典变化时,不同的窗口(或称为视图)进行更新。

下面给出两个可能的情景。面板A用于数据字典的维护。面板B一方面可以显示数据字典,同时还可以导入交易信息,从而也会修改数据字典。面板C也可以显示数据字典。

情景一是当面板A维护了数据字典后,它要通知面板B和面板C中的相应的UI组件执行刷新过程。

情景二是当面板B导入交易信息后,修改了数据字典,它一方面要通知自身的显示数据字典的组件,另外还要通知面板A和面板C的相应的UI组件来更新。

以前我可以想到的方法就是:

1. 直接调用法

当面板A的数据字典发生变化,我主动调用面板B和面板C的相应组件的刷新处理方法。当面板B改动了数据字典后,由面板B来调用面板A和面板C的相应组件的刷新方法。但这种方法需要知道有哪些组件要刷新,并且可以得到需要执行刷新动作的对象方法。当一个应用UI元素很多,得到目标对象并不容易。而且代码看上去也很难看。

2. 通过发送事件

可以考虑使用事件机制,利用GUI的事件循环,直接传递到指定窗口,这也已经与第一种差不多了。

正好我看到了在 wxPython 中谈到了 pubsub 功能,可以非常好的解决我的问题。

3. 发布/订阅机制(pubsub)

pubsub 就是发布和订阅机制,可能大家有所耳闻。首先我有一个公共的发布渠着,谁都可以通过它发布某个主题的信息。这样我可以制定一些主题,用来描述某些状态的变化。可以由产生变化的一方发起。然后有一些会对这些变化进行处理的订阅者,它们可以收到自已感兴趣的主题,当有信息出现时,它们就可以进行各自的处理。这里只是给大家讲一下大概的原理。不同的环境实现起来差别挺大。比如说可以用在一个网络环境,而这里我感兴趣的是一个应用程序之内如何做到。

wxPython 提供了这种机制,源码在 wx.lib.pubsub.py 中,有使用说明,有完整的测试用例和代码。那么它的思想其实说白了也还是直接的调用,但它封装得很好,让你感受不到。那么简单来讲,它实现了以下东西:

主题发布

通过调用 Publisher()可以得到一个Singleton的对象(只有一个实例的对象)。然后调用这个对象的SendMessage(topic, message=None)即可发布主题。还可以顺便带一个消息。可以没有。

信息订阅

通过调用 Publisher() 得到发布者对象,然后调用这个对象的subscribe(callable, topic)方法来订阅某个主题。topic可以是分级结构,如果是这种情况,要使用tuple来存放主题,如(’subject1′, ’subject2′)。一个订阅者可以同时订阅多个主题。

这样,当某个事件发生了,由想要发布这种变化的处理首先调用SendMessage()发布相关的主题。然后订阅者会收到这样的事件被调用,从而进行相应的处理。

这样的好处是发布者不用关心哪些视图要进行更新,它只是进行通知而已。而订阅者也不用关心是谁发布的,只要有调用就进行处理。

仔细看pubsub的实现,其实就是通过 Python 的特性,把pubsub模块导入后,这个模块的信息就会成为全局信息。通过Publisher()可以得到对应的唯一全局对象。然后,每个订阅处理会把一个可供调用的函数,类方法或可以调用的实例放到这个全局对象中。订阅者将根据主题被组织成树状结构。(而且结点好象是使用的弱引用,从而节省一些资源,没太懂。)当执行SendMessage()时,会根据发布的主题,在这个对象的主题树中查找,找到匹配的主题,由取出它的订阅方法,然后一个个的执行。这么看来,这种pubsub并不是一种异步的调用,只是直接调用。只不过给你的感觉有些象。其实就是在SendMessage()中进行调用。因此,这种方法要求订阅时要提供一个可供执行的对象方法、函数或可执行的对象。并且至少要有一个参数用来接收消息。因此它对订阅者函数的参数个数是有要求的。

为什么上面说主题是树呢?因为主题是可以分层次的,如:(’subject’,) (’subject’, ’sport’) 这定义了两个主题。如果一个订阅者两个都订阅了,那么当发送主题为(’subject’, ’sport’)是,它会被调用两次。因为(’subject’, ’sport’)还匹配(’subject’,)。

下面给出一个最简单的代码片:

import wx.lib.pubsub as pubsub  #导入模块
… 略
class A:
    def __init__():
        publisher = pubsub.Publisher()
        publisher.subscribe(self.OnSubscribe, ‘metadata_update’)
    def OnSubscribe(self, message):
        print ‘A message’
        …

class B:
    def __init__():
        publisher = pubsub.Publisher()
        publisher.subscribe(self.OnSubscribe, ‘metadata_update’)
    def OnSubscribe(self, message):
        print ‘B Message’
        …

上面A和B分别定阅了同一个主题。

publisher = pubsub.Publisher()
publisher.sendMessage(‘metadata_update’)

这样就发布了一个主题。这样,A和B对象的方法都会被调用。如果有很多对象,会依次进行调用。但这些处理全是自动的,你感觉不到。

pubsub可以非常方便地简单化程序之间的关系。

更详细的内容建议还是看一看源码后的示例,有些比较复杂的调用。

如果你觉得不好,可以利用全局对象自已实现一个这样的pubsub功能。不过,最后提醒一点,还有别的pubsub处理方式,特别是在网络通讯中,完全可能是异步,而不象这个根本就是直接调用(因为是在一个程序内实现的)。其实利用事件循环机制也可以实现,如发布者维护订阅者的句柄。当发布主题时,找到所有的订阅者句柄,然后每一个发一个消息。只要想通了道理,实现方法其实是挺多的。只不过我介绍的这个相对简单,容易实现。不过它不是 Python 自带的模块,而是wxPython中提供的,要注意。有兴趣可以改造下或写个简单的用在其它的项目中。

2005年02月23日

具体有关的内容请去啄木鸟社区看吧,点击这里 WoodpeckerClass/2005-02-26

本次周例会基于上次讨论出的WeKnow项目引发而来,由ZoomQuiet联系和组织的,其主要目的让我们学习一些关于:知识、社会化、学习等方面的内容。到底讲些什么只有听了课才知道。方式仍将采用在Skype中讲课,在UC中听课的方式。同时有录音条件的都多多录音啊。这里有一个录音软件(CodeWave),大家可以试一下。如果有更好的一样可以采用。

这是一次难得的机会,我们不再是封闭的,而是采用开放的态度,兼收并蓄,这本身也符合 Python 自身的发展。同时更是与专家学习和交流的好机会,可以提高我们的理论知识水平,更好的武装自已。

讲课时间大概在7:00左右。请有志于参加课和交流的同学:)提前准备。主要是时间安排和相关的软件安装,用户注册。UC组为4070424。想发言的同学要准备好麦克风,如果家里怕吵请再准备耳机。

2005年02月22日

这是我在网上找到的 Robin Dunn (wxPython主要开发人) 写的几个操纵 ListCtrl 的函数,可能会有些用,贴在这里:

def Select(self, idx, on=1):
    ”’[de]select an item”’
    if on: state = wx.LIST_STATE_SELECTED
    else: state = 0
    self.SetItemState(idx, state, wx.LIST_STATE_SELECTED)

def Focus(self, idx):
    ”’Focus and show the given item”’
    self.SetItemState(idx, wx.LIST_STATE_FOCUSED, wx.LIST_STATE_FOCUSED)
    self.EnsureVisible(idx)

def GetFocusedItem(self):
    ”’get the currently focused item or -1 if none”’
    return self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_FOCUSED)

def GetFirstSelected(self, *args):
    ”’return first selected item, or -1 when none”’
    return self.GetNextSelected(-1)

def GetNextSelected(self, item):
    ”’return subsequent selected items, or -1 when no more”’
    return self.GetNextItem(item, wx.LIST_NEXT_ALL,
wx.LIST_STATE_SELECTED)

def IsSelected(self, idx):
    ”’return True if the item is selected”’
    return self.GetItemState(idx, wx.LIST_STATE_SELECTED) != 0

def SetColumnImage(self, col, image):
    item = self.GetColumn(col)
    # preserve all other attributes too
    item.SetMask( wx.LIST_MASK_STATE |
                  wx.LIST_MASK_TEXT  |
                  wx.LIST_MASK_IMAGE |
                  wx.LIST_MASK_DATA  |
                  wx.LIST_SET_ITEM   |
                  wx.LIST_MASK_WIDTH |
                  wx.LIST_MASK_FORMAT )
    item.SetImage(image)
    self.SetColumn(col, item)

def ClearColumnImage(self, col):
    self.SetColumnImage(col, -1)

def Append(self, entry):
    ”’Append an item to the list control.  The entry parameter
should be a sequence with an item for each column”’
    if len(entry):
        if wx.USE_UNICODE:
            cvtfunc = unicode
        else:
            cvtfunc = str
        pos = self.GetItemCount()
        self.InsertStringItem(pos, cvtfunc(entry[0]))
        for i in range(1, len(entry)):
            self.SetStringItem(pos, i, cvtfunc(entry[i]))
        return pos

不过要声明一点:我并没有都试过。这是我在找如何通过程序来设定 ListCtrl 的某一条为选中状态而发现的,因为 ListCtrl 中并没有直接的方法可以设定一条记录被选中。

不过在使用中我发现,随着我设定 ListCtrl 的选中状态,越来越多的记录都变成选中状态,而不是只有一条被选中,不知道是怎么回事。为什么在我设置某条被选中时,其它已经被选中的条目并不消失,因此我设置的 style 是单选也不行。因此我只有想出一个笨方法:先把所有选中状态的记录改成未选中,再设置新的选中记录就行了。代码为:

def ClearSelection(self):
    n = metalist.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
    while n > -1:
        self.SetItemState(n, 0, wx.LIST_STATE_SELECTED)
        n = self.GetNextItem(n, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)

def SetSelection(self, idx):
    self.ClearSelection()
    self.SetItemState(idx, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
    self.EnsureVisible(idx)

此函数中用来设置选中状态,同时在设置完状态后保存选中的记录是可见的。

Python 的动态特性很奇妙和有趣,我在ElementTree的处理中看到差不多这样的一段代码:

def write(filename, domtree):
    if not hasattr(filename, ‘write’):
        f = file(filename, ‘wb’)
    else:
        f = filename
    ……

上面是一个代码片段,用来将dom对象树写成XML文件。在函数开始要对filename进行检查,如果没有write方法则认为filename是字符串,然后打开指定的文件。然后在后面使用文件对象的write方法。

那么为了测试,可以:

s = []
class dummy: pass
f = dummy()
f.write = s.append
write(f, domtree)

本来我们测试这个函数可以真正传一个文件名或文件对象,然后看生成的文件。但这里并没有那样做。而是通过空对象模拟了文件对象,但实际上内容却是放到了s数组中。很有趣,我们可以发现几点有意思的东西:

  1. Python 是动态的,对于要调用的对象是个什么东西根本不关心,只要有能使的方法就行
  2. 空对象在有时是很有用的

当然,你还可以想出其它的模拟文件对象的方法,其中可以使用StringIO或cStringIO模块来模拟,因为它本来就是做这个的。

写这篇文章的目的就是想让大家开拓一下思维,不要被习惯所左右,你也会写出非常有趣的代码。

这是一篇我发表在 Python.cn 邮件列表上,现在放在Blog作一个记录,略作整理。

如何处理unicode及其它的编码,其实这个问题想明白了很简单:

  1. 字符串有很多的编码
  2. 不同的系统和平台有各自的编码
  3. 为了实现系统或平台之间的信息交互可能需要编码转换
  4. 基本上在两个处理环节上进行转换:你的系统从其它系统读 你的系统从其它系统写
  5. 根据需要进行从源头到目标系统的编码转换

那么常见的环节都有哪些呢,我只能从经验上来回答了?如 NewEdit 使用 wxPython 的unicode版本,那么首先要明白,所有wxPython自带的函数和类都是使用unicode的,哪怕是打开文件对话框这样的从外部输入的类也会自动将文件名转为unicode。然后可能的环节有

  • 使用python的自带模块操作文件系统,如:file()和操作目录等
  • 使用socket发送和接收数据

总之是调用非wxPython的东西都可能需要编码转换

再如XML的处理也是经常有人问的。你应该要记住,当一个XML报文从文件被解析成DOM树时,所有的文本信息都被处理为unicode。然后再考虑与外部环境的交互,看一看是否需要进行编码转换。

在明白有可有需要进行编码转换的地方后,再考查到底需不需要进行编码转换。当调用的内容与你的系统编码一致时,并不需要转换。那么对方系统到底要什么样的编码呢,那只能仔细看文档,靠你的经验,靠你多做测试。而且根据python所报出来的错误一般也可以判断出来。那么当发现需要编码转换时,剩下的就是如何正确进行码制转换。

再举例说明,如你的系统为unicode编码。那么数据有两个流向,一种是调用各种内部或外部的方法来得到数据,这就是读操作。如果是调用系统内的函数,如调用wxPython自已的函数,你可以认为它返回的都是unicode,这样你的系统也为unicode,因此不需要转换。但如果调用的函数返回非unicode,一般需要转换。为什么说一般呢?因为如果返回的是英文串的话,不转换也不会出来,但对于包含中文的情况是会出错的。另一种流向是把系统中的数据传入一个方法中进行处理,这就是写的操作。如果调用的是系统内部的方法,如调用wxPython的方法,你可以认为这个方法需要unicode编码,而数据又是系统中的,因此编码都是unicode,不需要转换。但如果你调用的方法要求其它的编码时,一般需要进行编码转换。对于亚洲语言是特别要考虑这个问题。

常见的编码转换分为以下几种情况:

  • unicode->其它编码

    例如:a为unicode编码 要转为gb2312。a.encode(‘gb2312′)
  • 其它编码->unicode

    例如:a为gb2312编码,要转为unicode. unicode(a, ‘gb2312′)或a.decode(‘gb2312′)
  • 编码1 -> 编码2

    可以先转为unicode再转为编码2

    如gb2312转big5
    unicode(a, ‘gb2312′).encode(‘big5′)
  • 判断字符串的编码

    isinstance(s, str) 用来判断是否为一般字符串
    isinstance(s, unicode) 用来判断是否为unicode
  • 如果一个字符串已经是unicode了,再执行unicode转换有时会出错(并不都出错)

    可以写一个通用的转成unicode函数:
    def u(s, encoding):
        if isinstance(s, unicode):
            return s
        else:
            return unicode(s, encoding)

那么unicode与utf-8,utf-16有什么区别呢。我认为unicode可以称为抽象编码,也就是它只是一种内部表示,一般不能直接保存。保存到磁盘上时,需要把它转换为对应的编码,如utf-8和utf-16

除上以上的编码方法,在读写文件时还可以使用codecs的open方法在读写时进行转换。

另外我以前写过几篇blog有这方面的介绍:

同时在linuxforum.net上的python版有关于unicode的讨论,可以找一下。 网上也有不少的资料可以查阅。

希望本文对你有帮助。

2005年02月19日

其实没什么复杂的,只不过专门去找可能还真不方便,记下来。

对于对话框缺省按键的支持,象回车自动响应缺省按键,可以调用Button类的SetDefault()方法来做。如果使用xrced生成界面,可以在加入按钮后进行设置。因此对于回车键的响应是不存在问题的。

但没有对ESC的直接的方法调用或设置。其实很简单,就是把按钮的ID设为wx.ID_CANCEL即可。如果原来的按钮ID不是它,可以使用:

b.SetId(wx.ID_CANCEL)

进行设置。这样一切就OK了。

很简单,但 NewEdit 中的查找对话框一直都没实现,现在终于可以了。