作者: Jim Wang 公众号: 巴博萨船长

在未来使用中,窗口的表现形式可能根据版本的不同而不同,但又需要同时要保证代码的向下兼容,也就说可以不断地添加了新的窗口,那老用户的老版本也应该能够继续使用。所以要根据版本信息进行代码(至少是窗体生成部分的代码)的调度。

  1. 调度方式1, 传统设计理念中,也就是预定义一部分类方法,在根据实际需求,用字典索引的方式查找类方法或者函数。如下,

    • 可以如下方式进行调度:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      def p1(args):
      whatever

      def p2(more args):
      whatever

      myDict = {
      "P1": p1,
      "P2": p2,
      ...
      "Pn": pn
      }

      def myMain(name):
      myDict[name]()
    • 或者以下列方式进行调度:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      def myMain(key):
      def ExecP1():
      pass
      def ExecP2():
      pass
      def ExecP3():
      pass
      def ExecPn():
      pass
      locals()['Exec' + key]()

      两种的调度思路是一致的。

  2. 调度方式2, Python标准库种有一个functools模块,该模块可以为可调用对象定义高阶函数和操作。即,依据现有函数定义新的函数。我们实现的方式运用了partial函数和singledispatch函数,parital由于定义偏函数,也就是为了固定函数或者方法的一个参数,在该实现方式种用于向Event的Handler传递第二个参数。singledispatch是用于函数调度,即将函数装饰为泛函数。其根据参数的数据类型实现函数的自主调度。因为我们要实现类方法根据不同的版本进行自主调度,就意味着需要将版本作为一种新的数据类型。

    • 基本代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import wx 
import wx.adv
from sys import modules
from functools import singledispatch, update_wrapper, partial

def methdispatch(func):
dispatcher = singledispatch(func)
def wrapper(_args, **kw):
return dispatcher.dispatch(args[1].**class**)(_args, **kw)
wrapper.register = dispatcher.register
update_wrapper(wrapper, func)
return wrapper

thisModule = modules[__name__]
# assign the new types inodule level m
setattr(thisModule, 'v1_20', type('v1_20', (object,), {}))
setattr(thisModule, 'v1_30', type('v1_30', (object,), {}))
setattr(thisModule, 'v1_40', type('v1_40', (object,), {}))
setattr(thisModule, 'v1_50', type('v1_50', (object,), {}))
setattr(thisModule, 'v1_60', type('v1_60', (object,), {}))


class ClassDialog(wx.Dialog):

def __init__(self, parent, title="Test Demo", size=wx.DefaultSize, pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE, strVersion="v1_20", name="TestDemo"):
"""
strVersion, format v1_20
"""
wx.Dialog.__init__(self, parent, wx.ID_ANY, title,
size=size,
pos=pos,
style=style,
name=name)
try:
self.objVersion = eval(strVersion)()
except:
self.objVersion = None
self.CreateWidgets(self.objVersion)

@methdispatch
def CreateWidgets(self, objVersion): # @UnusedVariable
self.panel = wx.Panel(self)
vBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.quote = wx.StaticText(self.panel, label="Your quote: Default ", pos=(20, 30))
vBoxSizer.Add(self.quote, 5)
self.btn = wx.Button(self.panel, wx.ID_ANY, size=wx.DefaultSize, label="Test")
vBoxSizer.Add(self.btn, 1)
self.panel.SetSizer(vBoxSizer)
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

@CreateWidgets.register(v1_20) # @UndefinedVariable
def _(self, objVersion): # @UnusedVariable
self.panel = wx.Panel(self)
vBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.quote = wx.StaticText(self.panel, label="Your quote: 1.20 ", pos=(20, 30))
vBoxSizer.Add(self.quote, 5)
self.btn = wx.Button(self.panel, wx.ID_ANY, size=wx.DefaultSize, label="Test")
vBoxSizer.Add(self.btn, 1)
self.panel.SetSizer(vBoxSizer)
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

@CreateWidgets.register(v1_30) # @UndefinedVariable
def _(self, objVersion): # @UnusedVariable @DuplicatedSignature
self.panel = wx.Panel(self)
vBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.quote = wx.StaticText(self.panel, label="Your quote: 1.30 ", pos=(20, 30))
vBoxSizer.Add(self.quote, 5)
self.btn = wx.Button(self.panel, wx.ID_ANY, size=wx.DefaultSize, label="Test")
vBoxSizer.Add(self.btn, 1)
self.panel.SetSizer(vBoxSizer)
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

@methdispatch
def OnButtonClicked(self, objVersion, event): # @UnusedVariable
print("aaaaa")

@OnButtonClicked.register(v1_20) # @UndefinedVariable
def _(self, objVersion, event): # @UndefinedVariable @DuplicatedSignature @UnusedVariable
print(event.EventObject.Name)
print("bbbbb")


class SubClassDialog(ClassDialog):
def __init__(self, parent, title="Test Demo", size=wx.DefaultSize, pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE, strVersion="v1_20", name="TestDemo"):
"""
strVersion, format v1_20
"""
ClassDialog.__init__(self, parent, title=title, size=size, pos=pos, style=style, strVersion=strVersion, name=name) # @UndefinedVariable

@ClassDialog.CreateWidgets.register(v1_40) # @UndefinedVariable
def _(self, objVersion): # @DuplicatedSignature @UnusedVariable
print("1.40")

@ClassDialog.CreateWidgets.register(v1_50) # @UndefinedVariable
def _(self, objVersion): # @DuplicatedSignature @UnusedVariable
print("1.50")

class MyFrame(wx.Frame):
""" We simply derive a new class of Frame. """
def __init__(self, parent, title): # @DuplicatedSignature
wx.Frame.__init__(self, parent, title=title, size=(200,100))
dlg=SubClassDialog(self, strVersion="v1_x0")
res = dlg.ShowModal()
#self.Show(True)

def main():
app = wx.App(False)
frame = MyFrame(None, 'Small editor') # @UnusedVariable
app.MainLoop()

if __name__ == '__main__':
main()

在日常实践种singledispatch都是用来调度函数的,如果直接用在类方法上,会出现输入参数异常的问题。在这里我们就需要重新定义singledispatch进而得到一个methdispatch的函数。因为类方法的第一个参数为self即实例,所以我们要在函数种dispatch()第二个个参数。检查其数据类型然后调度注册的函数。

1
2
3
4
5
6
7
def methdispatch(func):
dispatcher = singledispatch(func)
def wrapper(*args, **kw):
return dispatcher.dispatch(args[1].__class__)(*args, **kw)
wrapper.register = dispatcher.register
update_wrapper(wrapper, func)
return wrapper

为了实现类方法依照版本类型自主调度,上文中说到,我们要将版本注册成新的数据类型,或者类。在未来任何需要新的版本,都要将该版本好吧注册成新的类,代码如下:

1
2
3
4
5
6
7
thisModule = modules[__name__]
# assign the new types inodule level m
setattr(thisModule, 'v1_20', type('v1_20', (object,), {}))
setattr(thisModule, 'v1_30', type('v1_30', (object,), {}))
setattr(thisModule, 'v1_40', type('v1_40', (object,), {}))
setattr(thisModule, 'v1_50', type('v1_50', (object,), {}))
setattr(thisModule, 'v1_60', type('v1_60', (object,), {}))

普通的类方法可以用如下的代码实现调度,如果采用上述的方式进行版本数据类型的声明,一些IDE并不能够自动找到新注册的数据类型,就会出现该类型未找到的错,如果使用Eclipse就需要添加特别注释@UndefinedVariable:

1
2
3
4
5
6
7
8
9
10
@CreateWidgets.register(v1_30)  # @UndefinedVariable
def _(self, objVersion): # @UnusedVariable @DuplicatedSignature
self.panel = wx.Panel(self)
vBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.quote = wx.StaticText(self.panel, label="Your quote: 1.30 ", pos=(20, 30))
vBoxSizer.Add(self.quote, 5)
self.btn = wx.Button(self.panel, wx.ID_ANY, size=wx.DefaultSize, label="Test")
vBoxSizer.Add(self.btn, 1)
self.panel.SetSizer(vBoxSizer)
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

针对一些特别的类方法如,事件处理函数,就要用到partial函数,代码如下。需要注意的是在这里partial固定的是类方法OnButtonClicked的第一个参数,第二个参数是Event。 理解了这些,我们就可以根据版本的不同调度同一事件的不同时间的处理方法了。

1
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

注册同一事件处理函数的泛函数方法如下,泛函数的名称可以自由定义,但是_在这里确实是一个很好的选择,如果某一版本的泛函数没被注册,就会使用函数的本体:

1
2
3
4
5
6
7
8
@methdispatch 
def OnButtonClicked(self, objVersion, event): # @UnusedVariable
print("aaaaa")

@OnButtonClicked.register(v1_20) # @UndefinedVariable
def _(self, objVersion, event): # @DuplicatedSignature @UnusedVariable
print(event.EventObject.Name)
print("bbbbb")

上述的内容都是在一个Python种的使用情况,下面我们介绍一下如何跨模块实现上述内容,在跨模块使用该调度方式的时候,需要注意要从基本模块加载版本的数据类型,在注册泛函数的时候写法也于在同一模块种的不同,本例种:@ClassDialog.CreateWidgets.register(v1_60),从类名,类方法名再进而注册版本类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import wx
from functools import partial
from test import ClassDialog, v1_60 # @UnusedImport @UnresolvedImport


class SubClassDialogInExtraModule(ClassDialog):
def __init__(self, parent, title="Test Demo", size=wx.DefaultSize, pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE, strVersion="v1_20", name="TestDemo"):
"""
strVersion, format v1_20
"""
ClassDialog.__init__(self, parent, title=title, size=size, pos=pos, style=style, strVersion=strVersion, name=name) # @UndefinedVariable


@ClassDialog.CreateWidgets.register(v1_60) # @UndefinedVariable
def _(self, objVersion): # @UnusedVariable @DuplicatedSignature
self.panel = wx.Panel(self)
vBoxSizer = wx.BoxSizer(wx.VERTICAL)
self.quote = wx.StaticText(self.panel, label="Your quote: 1.60 ", pos=(20, 30))
vBoxSizer.Add(self.quote, 5)
self.btn = wx.Button(self.panel, wx.ID_ANY, size=wx.DefaultSize, label="Test")
vBoxSizer.Add(self.btn, 1)
self.panel.SetSizer(vBoxSizer)
self.btn.Bind(wx.EVT_BUTTON, partial(self.OnButtonClicked, self.objVersion))

class MyFrame(wx.Frame):
""" We simply derive a new class of Frame. """
def __init__(self, parent, title): # @DuplicatedSignature
wx.Frame.__init__(self, parent, title=title, size=(200,100))
dlg=SubClassDialogInExtraModule(self, strVersion="v1_60")
res = dlg.ShowModal() # @UnusedVariable
#self.Show(True)

def main():
app = wx.App(False)
frame = MyFrame(None, 'Small editor') # @UnusedVariable
app.MainLoop()

if __name__ == '__main__':
main()

上述就是使用functools种的两个函数实现依据不同版本调度不同的泛函数的全部内容


版权声明:
文章首发于 Jim Wang's blog , 转载文章请务必以超链接形式标明文章出处,作者信息及本版权声明。