HOME Python download 書き込む

5. 関数のクラスを使ってクールなコードを書こう


1. 初めに

Tkinter では、Button や Event と結びつけるのは関数そのものではなく、 関数へのポインターです。また、引数に厳しい制限があり、似たような動作を 関数へ渡す引数の違いで表すことができません。

しかし、関数のクラスを用いると、あたかも引数をとる関数と Button や Event が バインドしたように見えます。

ここでは、図1に示す画像の背景を変えるプログラムを例にとって 説明していきたいと思います。


[図1]

2. 何もテクニックを使わない書き方

[code 1] に何もテクニックを使わないプログラムを示します。 9 個のボタンと 9 個のメソッドを別々に定義しているのできわめて冗長です。

[code 1]

01:     #! /usr/bin/env python
02:     
03:     """
04:     bg1.py 
05:     
06:     Change Background of flower
07:     
08:     June 28, 2005
09:     """
10:     
11:     
12:     import Tkinter as Tk
13:     
14:     FLOWER = 'nanohana3.gif'
15:     
16:     class Frame(Tk.Frame):
17:         
18:         def __init__(self, master=None):
19:             Tk.Frame.__init__(self, master)
20:             self.master.title('select background')
21:             f_button = Tk.Frame(self)
22:             f_button.pack(side=Tk.LEFT, padx=5, pady=5)
23:             self.flower = Tk.PhotoImage(file=FLOWER)
24:             self.label = Tk.Label(self, image=self.flower, relief=Tk.RAISED, bd=3)
25:             self.label.pack(side=Tk.RIGHT, padx =5)
26:     
27:             b_aliceblue = Tk.Button(f_button, text='aliceblue',  bg='#F0F8FF', command=self.c_aliceblue)
28:             b_azure = Tk.Button(f_button, text='azure',  bg='#F0FFFF', command=self.c_azure)
29:             b_beige = Tk.Button(f_button, text='beige',  bg='#F5F5DC', command=self.c_beige)
30:             b_cornsilk = Tk.Button(f_button, text='cornsilk',  bg='#FFF8DC', command=self.c_cornsilk)
31:             b_khaki = Tk.Button(f_button, text='khaki',  bg='#F0E68C', command=self.c_khaki)
32:             b_lightgreen = Tk.Button(f_button, text='lightgreen',  bg='#90EE90', command=self.c_lightgreen)
33:             b_lightpink = Tk.Button(f_button, text='lightpink',  bg='#FFB6C1', command=self.c_lightpink)
34:             b_lightskyblue = Tk.Button(f_button, text='lightskyblue',  bg='#87CEFA', command=self.c_lightskyblue)
35:             b_palegreen = Tk.Button(f_button, text='palegreen',  bg='#98FB98', command=self.c_palegreen)
36:     
37:             b_aliceblue.pack(fill=Tk.X)
38:             b_azure.pack(fill=Tk.X)
39:             b_beige.pack(fill=Tk.X)
40:             b_cornsilk.pack(fill=Tk.X)
41:             b_khaki.pack(fill=Tk.X)
42:             b_lightgreen.pack(fill=Tk.X)
43:             b_lightpink.pack(fill=Tk.X)
44:             b_lightskyblue.pack(fill=Tk.X)
45:             b_palegreen.pack(fill=Tk.X)
46:     
47:         def c_cornsilk(self):
48:             self.label.configure(bg='#FFF8DC')
49:     
50:         def c_khaki(self):
51:             self.label.configure(bg='#F0E68C')
52:     
53:         def c_lightgreen(self):
54:             self.label.configure(bg='#90EE90')
55:     
56:         def c_lightpink(self):
57:             self.label.configure(bg='#FFB6C1')
58:     
59:         def c_lightskyblue(self):
60:             self.label.configure(bg='#87CEFA')
61:     
62:         def c_palegreen(self):
63:             self.label.configure(bg='#98FB98')
64:     
65:         def c_azure(self):
66:             self.label.configure(bg='#F0FFFF')
67:     
68:         def c_aliceblue(self):
69:             self.label.configure(bg='#F0F8FF')
70:     
71:         def c_beige(self):
72:             self.label.configure(bg='#F5F5DC')
73:     
74:     
75:     ##------------------------------------------------ 
76:     
77:     if __name__ == '__main__':
78:         f = Frame()
79:         f.pack()
80:         f.mainloop()
見てわかると思いますが、同じようなコードが9回現れるのできわめて冗長です。 特に目新しい部分はないので説明は省略したいと思います。

3. Tk.Button の導出クラスを用いる方法

こういう場合の OOP の定石は、動作をオブジェクトに含めてしまうことでしょう。 こうするとプログラムの冗長性はなくなります。

個の場合は Tk.Button の導出クラス Button を定義し、そのメソッドとして背景色の変更を定義します。 コードは下の [code 2] のようになります。

[code 2]

01:     #! /usr/bin/env python
02:     
03:     """
04:     bg2.py 
05:     
06:     Change Background of flower
07:     
08:     June 28, 2005
09:     """
10:     
11:     
12:     import Tkinter as Tk
13:     
14:     FLOWER = 'nanohana3.gif'
15:     
16:     
17:     BGS = [('aliceblue', #F0F8FF'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'),              \
18:            ('cornsilk', '#FFF8DC'), ('khaki', '#F0E68C'), ('lightgreen', '#90EE90'),          \
19:            ('lightpink', '#FFB6C1'), ('lightskyblue', '#87CEFA'), ('palegreen', '#98FB98')]
20:     
21:     
22:     class Button(Tk.Button):
23:     
24:         def __init__(self, master, label, color_name, color_code):
25:             Tk.Button.__init__(self, master, text=color_name, bg=color_code, command=self.bg_change)
26:             self.label = label
27:             self.color_code = color_code
28:     
29:         def bg_change(self):
30:             self.label.configure(bg=self.color_code)
31:         
32:     
33:     
34:     class Frame(Tk.Frame):
35:         
36:         def __init__(self, master=None):
37:             Tk.Frame.__init__(self, master)
38:             self.master.title('select background')
39:             f_button = Tk.Frame(self)
40:             f_button.pack(side=Tk.LEFT, padx=5, pady=5)
41:             self.flower = Tk.PhotoImage(file=FLOWER)
42:             label = Tk.Label(self, image=self.flower, relief=Tk.RAISED, bd=3)
43:             label.pack(side=Tk.RIGHT, padx =5)
44:     
45:             for name, code in BGS:
46:                 b = Button(f_button, label, name, code)
47:                 b.pack(fill=Tk.X)
48:     
49:     
50:     
51:     ##------------------------------------------------ 
52:     
53:     if __name__ == '__main__':
54:         f = Frame()
55:         f.pack()
56:         f.mainloop()
ずいぶんすっきりと書けました。下に簡単な解説を示します。括弧の数字はソースコードの行番号です。

4. 関数のクラスを用いる方法

上に述べた方法は、プログラムは短くなるのですが、ラベルの色を変えるという動作とボタンが強く結びついてしまう という欠点があります。例えば、キータッチやメニューから同じ動作をさせたいとき、似たような動作をするメソッドを また別に書かなければいけないという問題が生じます。

そもそも、グループ化したかったのは関数のほうであり、ボタンではありませんでした。 従って、上の方法は OOP の一般的な解決法ですが、問題が残ります。

そこで、お勧めなのが関数のクラスを用いる方法です。Python では関数のクラスをサポートしていて、 似たような関数をクラス定義から作り出すことができます。

早速コードを [code 3] に示します。

[code 3]

01:     #! /usr/bin/env python
02:     
03:     """
04:     bg3.py 
05:     
06:     Change Background of flower
07:     
08:     June 28, 2005
09:     """
10:     
11:     
12:     import Tkinter as Tk
13:     
14:     FLOWER = 'nanohana3.gif'
15:     
16:     
17:     BGS = [('aliceblue', '#F0F8FF'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'),              \
18:            ('cornsilk', '#FFF8DC'), ('khaki', '#F0E68C'), ('lightgreen', '#90EE90'),          \
19:            ('lightpink', '#FFB6C1'), ('lightskyblue', '#87CEFA'), ('palegreen', '#98FB98')]
20:     
21:     
22:     class BgChange:
23:     
24:         def __init__(self, label, color):
25:             self.label = label
26:             self.color = color
27:     
28:         def __call__(self, event=None):
29:             self.label.configure(bg=self.color)
30:     
31:     
32:     class Frame(Tk.Frame):
33:         
34:         def __init__(self, master=None):
35:             Tk.Frame.__init__(self, master)
36:             self.master.title('select background')
37:             f_button = Tk.Frame(self)
38:             f_button.pack(side=Tk.LEFT, padx=5, pady=5)
39:             self.flower = Tk.PhotoImage(file=FLOWER)
40:             label = Tk.Label(self, image=self.flower, relief=Tk.RAISED, bd=3)
41:             label.pack(side=Tk.RIGHT, padx =5)
42:     
43:             for name, code in BGS:
44:                 b = Tk.Button(f_button, text=name,  bg=code, command=BgChange(label, code))
45:                 b.pack(fill=Tk.X)
46:     
47:     
48:     ##------------------------------------------------ 
49:     
50:     if __name__ == '__main__':
51:         f = Frame()
52:         f.pack()
53:         f.mainloop()
[code 3] で注目して欲しいのは、22--29 行目の class BgChange の 定義の部分です。 これは、ラベルの背景色を変える関数のクラスの定義です。

まず、初期化するとき (__init__) は self, label, color の3つの変数を取ります。 label, color を内部変数に保持して、呼び出されるときに使います。

呼び出されるとき (__call__) は self, event の2つの変数を取ります。event は optional 変数なのでなくても差し支えありません。こうすることによって、ボタンを押されたときも、マウスやキーボード操作 などのイベントで呼び出されたときにも対処することができます。呼び出されると、 初期化したときに保持しておいた self.labelself.color を使って、背景の色を変えます。

このようにすると、45 行目のように Tk.Buttoncommand オプションで、あたかも引数をとる関数を指定できるように なります。もう少し、詳しく説明すると、45 行目では、labelcode から関数のクラス BgChange のインスタンスを作り、そのポインターを command にバインドしています。そして、これが呼び出されるときは 1 ないし 2 引数の関数として呼び出され、ラベルの背景色を変えるという動作をします。

この方法は Tk.Button のほかに Tk.Menu などのそのほかの widget や event にそのまま使えるので、 お勧めです。コードも Tk.Widget の導出クラスを作るよりもさらに短くなります。

また、Tcl/Tk の command は引数をとる関数を指定するので、Tcl/Tk のコードを翻訳するときにも 関数のクラスは有効です。つまり、Tcl/Tk の関数を Python の関数クラスとして定義しなおすことで、 他の部分を変更しないで、翻訳することができます。

5. 関数の部分適用を使う方法

Python 2.5 からは、関数の部分適用が使えるようになったので、ここで示したような簡単な例は わざわざ関数のクラスを作らなくても書くことができます。 詳しくは
Python 2.5 の新機能 をみてください。

6. 終わりに

この章では、関数のクラスを使うことで、command を簡潔に記述する方法について述べました。 次回は Listbox について述べます。ここで紹介したスクリプトは菜の花だけしか表示できなかったので、 Listbox からいろいろな花を選んで表示できるようにします。