例題5-3:ダイアログ出したいんですけど(1)

B: あのー。Aさんりんごいかがですか?

A: ん? どうしたの、こんなたくさん。10個以上あるか?

B: いや、実家からすんごくたくさん送られてきて、食べきれないんであちこち配ってるんです。

A: あー。そういう事、あるよな。ありがたいんだけど絶っっ対に食えねえよこんなにって量を送ってきたりなんかして。 じゃあ、ありがたくいただいておくよ。

B: それでですね、りんごと引き換えにちょっと教えてほしいんですが。

A: って、オイ、引き換えかよ!

B: せっかくの実家からの支援なので有効活用しないと。

A: 親御さんもこんなふうに活用されるとは思ってなかっただろうがな。で、何が知りたいの?

B: いや、バイト先の先輩に手伝ってもらって実験をしようと思ってるんですけど、実験結果のファイル名を きちんと付け替えてもらえるかどうか不安なんですよね。普通のソフトみたいに、プログラムを実行したらダイアログが開いてファイル名を入力しないと プログラムが始まらないように出来ないんですかね?

A: バイト先の先輩? よく手伝ってもらえるな?

B: 先輩とはツーリング仲間なんですよね。オートバイの。で、先輩のバイクを修理するのと引き換えに。

A: へえ。B君がそんな趣味を持ってるとは知らなかったな。修理って、B君が修理すんの?

B: そうですよ。工具は一通り持ってますんで。部屋の一角はメンテナンスルームになってます。

A: 部屋ん中でそんなことしていいのか? 家主さんに見つからんようにな。まあそれはともかく、 ダイアログを出して保存ファイル名を指定するってのは教えてあげよう。ちょうどクラスの入門にいいだろうし。

B: じゃあ交渉成立と言う事で。

A: あ、りんごはそこに置いといて。私もそんなにもらっても一人じゃ食えんから、後でみんなで食べよう。 んじゃ、さっそく本題に入るか。まずはTkinterの説明からしないといけない。

B: …。

A: どうした? 怪訝な顔して。

B: "Tkinter"って、どう発音するんですか?

A: そりゃ"Tkinter"って発音するのさ…って同じようなネタを昔マンガで見たな。この業界は辞書に載っていない 造語が多くて、正しい発音がわかんないから人前で話す時に困る。TkinterはTk(ティーケー)というダイアログなどのユーザーインターフェースを作るためのツールキットを pythonで使うためのライブラリだ。ちなみにTkはtool kitの略だそうな。多分Tk + inter = Tkinterで「てぃーけーいんたー」と読むんだと思うんだけど、 正確なところは私も知らない。知らなくてもプログラムを書くには困んないし。

B: はあ。Aさんも知らないんですか。

A: ちょっと前に検索してみたんだけど出てこないんだよね。 まあ読み方の話はこれくらいにしておいて、話を進めよう。Tkinterはきちんと解説しようとするとそれだけで大変なことになる。だから今回はクラスの継承を解説する事を もう一つの目的として、使い方のごく一部を説明する事にしよう。 B: さっきからクラス、クラスって言ってますけど、クラスってよくわからないんですよ。おまけに、なんですって? 継承?

A: ふむ。ではダイアログを例として、改めてクラスの説明をするか。これは準備編1でちょっと名前が出たサクラエディタの置換ダイアログだ。 このダイアログの右側にボタンが7つあるのに注目してほしい。このダイアログを実際に自分でプログラムとして書かないといけないとしたら、どうだろう?

../_images/05-3-01.png

B: え、いきなりどうだろうって言われても。

A: ここで注目してほしいのは、これらのボタンの機能には共通しているものと、そうではないものがある点だ。ちょっと書き出してみようか。

共通しているもの

角が丸まった長方形を描く
長方形を着色する
ラベル文字列を描画する
ボタンが選択されたら強調色に変える
ボタンがクリックされたら対応する動作をする
ショートカットキーが押されたら対応する動作をする

異なっているもの

クリックされたりショートカットキーを押されたするときに呼び出す処理内容
ボタンの位置、大きさ
現在選択できる状態か否か

B: ボタンの位置とか大きさってのは機能なんですか? それに長方形を描くとか着色するとかいうのも、機能と言われると?な気が。

A: ふむ。自力でこういうユーザーインターフェースを作ろうと思った事がなければ違和感を感じるかも知れないな。でも、コンピュータに「ボタンを描け」って言っても通じないから、 「座標(x1,y1)を左上、(x2,y2)を右下とする領域を色番号0x888888で塗りつぶす」とかいう指令を次々とコンピュータに出してボタンを描かないといけないんだ。大変そうだろう?

B: うーん。そう言われると大変なのかも知れないという気になってきました。

A: 一度VisionEggのVisionEgg.MoreStimuli.Target2D()とかだけを使ってこのボタン1個描くにはどうしたらいいだろうかって考えてみれば良いよ。 本当にうんざりするほど面倒くさいから。

B: はあ。

A: まぁ練習問題にするほどの事でもないけどチャレンジ精神旺盛ならやってみたらいい。とにかく、これを関数やリストを使って書こうとするならば、 例えば以下のように管理する事になる。一度本気でやってみればわかるが、ボタンの数が多くなってくると、どのボタンがどのリストや関数と対応しているのかを管理するのは本当に大変だ。

共通しているもの

drawButtonNormal()
(通常状態のボタンを描く関数)

drawButtonNormal()
(強調状態のボタンを描く関数)

drawButtonNormal()
(選択不可状態のボタンを描く関数)

異なっているもの(上検索用)

buttonUEKensaku
(ボタンの位置、大きさ、ラベル等を保持するリスト)

funcUEKensaku()
(上検索の処理を行う関数)

異なっているもの(下検索用)

buttonSHITAKensaku
(ボタンの位置、大きさ、ラベル等を保持するリスト)

funcSHITAKensaku()
(下検索の処理を行う関数)

B: …相変わらず実感がありませんが、大変になるんですね?

A: 少なくとも私ぁこんな面倒くさいプログラム書きたくないな。そんな面倒くさがりの強い味方がクラスだ。 非常に雑な言い方をすると、クラスはダイアログのボタンのようにデータや処理内容に共通部分がある機能的なまとまりをうまく管理するためのものなんだよ。

B:

A: まあ、百聞は一見にしかずということで、実際に見てもらおうか。(サンプル1)

サンプル1

1#!/usr/bin/env python
2# -*- coding: shift-jis -*-
3
4import Tkinter
5
6w = Tkinter.Frame()
7w.mainloop()

B: じゃあ実行、と。

../_images/05-3-02.png

B: なんか小さなウィンドウが出てきましたね。中身がからっぽだ。

A: そう。6行目のTkinter.Frame()はウィンドウを作って、ボタンなどを配置するためのフレームを作成する。 フレーム自身は文字通りただの枠なので、ボタンなどを配置しなければからっぽだ。あ、ちなみにウィンドウに配置するボタンなどの部品のことを ウィジェット(Widget) という。この用語を覚えておけば自分で調べものをしたい時に役に立つだろう。

B: ふむふむ。で、そのウィジェット?ってのはどうやって配置するんですか?

A: 例えばこんな具合かなぁ。

サンプル2

 1#!/usr/bin/env python
 2# -*- coding: shift-jis -*-
 3
 4import Tkinter
 5
 6class myFrame(Tkinter.Frame):
 7    def __init__(self,master=None):
 8        Tkinter.Frame.__init__(self,master)
 9        self.master.title(u'ダイアログのテスト')
10
11        Tkinter.Label(self,text=u'被験者名').grid(row=0,column=0)
12        Tkinter.Entry(self).grid(row=0,column=1)
13        Tkinter.Button(self,text=u'開始').grid(row=1,column=0)
14        Tkinter.Button(self,text=u'キャンセル').grid(row=1,column=1)
15        self.pack()
16
17w = myFrame()
18w.mainloop()

A: じゃあ、実行。

../_images/05-3-03.png

B: おおっ、それっぽいものが出てきましたね。…って、あれ、これ「開始」を押しても何も起こらないですよ。

A: ははは。そりゃこのプログラムでは押した時にどういう動作をするか定義してないからね。まあ出来あがったウィンドウと プログラムを見比べてもらえば、なんとなくTkinter.Label()でテキストを表示して、Tkinter.Button()でボタンを配置する事が出来るのはわかるかな? すると残ったTkinter.Entry()が文字を入力するボックスを配置する関数、ということになるね。ちなみに文字を入力するボックスはエディットボックスとか言われる事がある。

B: ふうむ。じっくり見ていると、6行目の class ってキーワードは今回初めて出てくる奴なんだと思いますが、他は大体今までに見たものですねえ。 なのに意味がわかるような、全然分からないような…。

A: classの他にもselfとか__init__()という名前は特別な意味があるんだ。その辺の説明を兼ねてぼちぼちクラスの説明に踏み込んでいくかな。 以前にインスタンスという言葉を教えたのを覚えているかい?(注:例題1-3)

B: はあ、たい焼きはインスタンスだとかいう話ですよね。

A: たい焼きよりも覚えておいてほしい事をたくさん言ったはずだが。まあ大事なことなんで繰り返し言っておくと、 クラスというのはある時はたい焼きのように、またある時はボタンなどのウィジェットのように、機能的なまとまりを扱うための仕組みだ。

B: …たい焼きは機能的なまとまりなんですか?

A: うぐ。つまんない茶々を入れない! 今のサンプルプログラムだと例えばTkinter.Buttonがクラス なんだが、Tkinter.Buttonは一般的なボタンを作り出すための設計図みたいなものだ。そして、実際にTkinter.Button()関数を使って配置されたボタン は、その設計図に従って作られたインスタンスだ。ひとつのウィンドウにボタンをいくつも作る事が出来るように、ひとつの設計図でいくつもインスタンスを作る事が出来る。

B: ふむ。ちょっとわかるような、でも全然わからないような。

A: で、pythonの場合、クラスのインスタンスを新しく生成する時は、そのクラス名の関数を呼ぶ。 つまり、Tkinter.Buttonクラスのインスタンスを作るには、Tkinter.Button()という関数を呼ぶ。サンプル2では13行目と14行目だな。 このようにクラスのインスタンスを生成する関数をクラスの コンストラクタ という。

B: うわ、また新しいカタカナ語が出てきた。

A: クラスの設計図にはオーダーメイド出来る部分があって、コンストラクタへの引数でその辺のオーダーが出来る。 13行目では「開始」と書いたボタンを注文しているし、14行目では「キャンセル」と書いたボタンを注文している。

B: むむう。なんとなくわかったんですが、13~14行目のTkinter.Button()の引数に出てくるselfってなんですか?

A: それはインスタンス自身だ、って言っても全然わからんよな。

B: はい、全然わかりません。(きっぱり)

A: どう説明したらいいかなぁ…。先に__init__()の話をしたほうがわかりやすいかな。ちょっとselfは保留しておいてくれ。 クラスを作る時にはクラス名と同名のコンストラクタを呼び出すと言ったが、コンストラクタが標準で提供しているオーダー項目ではお望みの 機能を持つ物が作れない場合を考えよう。

B: はあ。

A: ちょっとサンプル1に戻ると、サンプル1でTkinter.Frame()のコンストラクタを呼び出したら空っぽのウィンドウが出てきたんだった。 空っぽのウィンドウはB君の目的には何の役にも立たないよな?

B: そりゃー、役に立たないでしょ。

A: そう、Tkinter.Frame()はウィジェットを配置するための枠を作ってくれるが、これだけでは 本当に役に立たない。これにボタンやら何やらを追加したい。

B: そうですね。

A: こういう時、ウィジェットを配置する枠を作るという機能をTkinter.Frameから受け継いだ、 自分だけのクラスを作る事が出来るんだ。これをTkinter.Frameを 基底クラス して 派生クラス を作ると言う。 基底クラスは 親クラス とか言われることもある。また、基底クラスの機能を引き継いだクラスを作ることを 継承 という。

B: ちょ、待って下さい。派生クラス? 継承?

A: 次々と専門用語が出てくるから難しいかな。まあゆっくり説明するから頑張ってついてきてくれ。 実は、サンプル2はTkinter.Frameを継承したmyFrameというクラスを定義する例なんだ。6行目のclassというキーワードから始まる行がmyFrameの定義だ。 最初のclassはこれからクラスの定義が始まるよという宣言。続いてクラス名。この辺りは関数を定義するdefと同じような感じだね。 問題は()の中で、ここには機能を引き継ぎたい基底クラスを書く。今はTkinter.Frameから引き継ぎたいんだから、()の中にTkinter.Frameと書く。

6class myFrame(Tkinter.Frame):

B: ふむふむ。

A: で、6行目以後の字下げされている部分で具体的にmyFrameクラスの定義をしていくわけだが、 続く7行目に__init()__という関数が定義されている。これは特別な意味を持つ関数で、コンストラクタが呼び出されてクラスが作られた直後に 自動的に呼び出される。だから、myFrameクラスに新しく付け加えたい機能をこの関数に書けばいいんだ。

B: 前後の__は必要なんですか?

A: もちろん。ここでは紹介しないけど、他にも重要な関数で__が前後についているのがある。 重要な関数なんでユーザーに上書きされてしまわないようにわざわざこんな名前を付けたんだろうね。

B: 上書き?

A: オーバーライドっていう機能の話なんだが、それはまた後で説明するかな。とにかく__init__()の中身を見ていこう。まず8行目。 ここで基底クラスであるTkiner.Frameの__init__()を呼び出している。これによって、myFrameクラスはTkinter.Frameが持つさまざまな機能が 使える状態になる。続いて9行目だが、ここでいよいよずっと先送りにしてきたselfを説明しないといけない。単刀直入に言うと、 selfってのは インスタンス自身を指している んだ。

B: へ? インスタンス自身? を、指す?

A: そう。例えばmyFrameクラスのウィンドウを画面に3個開いたとしよう。言いかえると、myFrameのインスタンスを3個作ったとする。 そして、myFrameのウィンドウに何かの操作をしたら、そのウィンドウの色が青色にあるようにプログラムしたいとする。こういう場合、myFrameの定義には何と書いておけばいいだろう? ただ単に「ウィンドウの色を青色に変更する」と書いても、3個のウィンドウのどれを変更したらいいのかさっぱりわからない。

../_images/05-3-04.png

B: え、そんなの「操作されたウィンドウの色を青色に変更する」って書いたらいいだけじゃないんですか?

A: そう。それを実現するのがselfなんだよ。selfはインスタンス1を処理している時にはインスタンス1、インスタンス2を処理している時にはインスタンス2と いう具合に自分自身を表す。だから、selfを使うと現在処理中のインスタンスを簡単に操作する事が出来るんだ。

../_images/05-3-05.png

B: なるほど、ちょっとわかってきたような。ところでインスタンス1を操作した時にはインスタンス2の色が変わるように したりすることは出来るんですか?

A: もちろん出来るし、練習問題としてはちょっと面白いが…。ま、クラスを使ったプログラミングに慣れてからの練習問題だな。 今回はパス。なんなら自分で考えてみなさい。

B: んー。遠慮しときます。

A: selfの意味はわかったかな? 実は、pythonの文法上はクラスの内部で定義される関数の第1引数がインスタンスを指す変数であり、 必ずselfという名前にしないといけないわけではない。けれどもselfという名前にするのが一般的だし、この企画でもselfという名前で統一する事にする。いいかい?

B: ややこしくないのは大歓迎です。

A: よし。それともうひとつ。「クラスの内部で定義される関数」といちいち言うのは面倒くさいので、こういう関数の事をクラスの メソッド という。クラスの メンバ(member)関数 という事もある。実は先ほど「関数」と呼んだTkinter.Button()やTkinter.Label()もTkinterクラスのメソッドなので、 関数ではなくメソッドと呼ぶのが正しい。まあ、いいかげんがモットーの企画なんで、出来るだけ正確な用語を使おうとは思うがあっさり関数って言っちゃうかも知れん。 その時は勘弁してくれ。

B: うーん、それにしても今回、いくつ新しい用語出てきました? 覚えられる気がしません。

A: すぐに覚えるのは難しいだろうけど、少しずつ慣れてほしいとしか言えないかな。こういう用語を理解できるようになれば わからないことがあった時に自分で調べることもできるし、将来仕事でpython以外のプログラミング言語を使わないといけなくなった時にきっと役に立つから。 じゃあ気を取り直して、サンプル2を詳しく見ていこうか。まず、7行目の__init__()の定義。 さっきは説明しなかったんだけど、第1引数にselfが指定されている。これは今言ったとおり、pythonのクラスメソッドの第1引数はselfにするのが定番。 第2引数は基底クラスのTkinter.Frameの__init()__に渡すためのもので、例えばExcelみたいにウィンドウの中にウィンドウが入ってるものを作りたい時に使う。 そうでない場合はNoneでいい。まあ、心理実験のために簡単なダイアログを作るという目的ではNone以外の値を使う必要はないと思う。 要するに、__init__()の引数はself, master=Noneとしておけばまず問題ない。

B: コピペでOKですね。

A: 続いて9行目。self.master.title()はTkinter.Frameから継承してきたメソッドで、ウィンドウのタイトルを指定する機能がある。 さっきの実行例の図ではウィンドウが小さくてわかりにくかったかも知れないが、ちゃんと「ダイアログのテスト」というタイトルのウィンドウになっている。ここの文字列をいろいろ変えて試してみたらい。 ちなみに何も指定しなければ「tk」というタイトルになる。気にならない人は「tk」のままで全然問題ない。

B: ぼくは全然気にしません。

A: そうか。なら9行目は省いてもいいぞ。続いて11行目から14行目でラベルやボタンといったウィジェットを作成して配置している。 これらのコンストラクタの第1引数は「ウィジェットを配置する場所」を代入することになっている。もちろん今自分で作成したウィンドウの中に配置したいんだから、 ここはselfを指定すればいい。ウィンドウの中に小さなウィンドウがあって…といった複雑なウィンドウを作っているのでもない限り、selfにしておけばいい。

B: ふむふむ。案外楽ですね。

A: あとはgrid()とpack()。grid()はTkinter.Frameで作った枠の中を表のように縦横に区切って、何行何列目のマスにウィジェットを配置するかを指定する。 pythonは0から数え始めるから、row=0,column=0で上から1行目、左から1列目。row=1,column=3なら上から2行目、左から4列目だね。必要なウィジェットを配置して、最後に self.pack()を呼べば、あとはTkinterがひとつひとつのウィジェットの大きさや行数、列数を考慮して自動的にボタンの大きさやら位置やらウィンドウの大きさやらを全部自動的に計算してくれる。

B: おおー。pack()、頼もしい!

A: さて、これでウィジェットの配置は終了。サンプル2ではウィジェットを配置するところまでを 説明するつもりだったので、ここで__init__()関数はひとまず完成。 ずいぶん長くなってしまったからここでいったん区切りにして、ボタンを押したらウィンドウを閉じたり、エディットボックスの文字列をデータファイル名に反映させたりする部分は 例題5-4としようか。

B: あのー。18行目のw.mainloop()ってのが何なのかわからないんですが。

A: おっと、忘れてた。本来ならサンプル1で説明しないといけないところだったな。 ついでに解説しておくと、17行目がクラス名と同じ名前の関数、つまりmyFrameのインスタンスを生成するコンストラクタの呼び出しだね。 生成されたインスタンスはwという変数に格納されている。myFrameのインスタンスを作っただけでは画面には表示されなくて、クラスメソッドのmainloop()を 呼び出した時に初めて表示される。mainloop()を呼び出したら、ウィンドウが閉じられるまでプログラムの処理は停止する。

B: mainloop()なんて関数、どこから出てきたんですか?

A: Tkinter.Frameクラスから継承したのさ。

B: じゃあ、myFrameクラスを使う時には、Tkinter.Frameの…なんでしたっけ、メソッド?も知ってなきゃ いけないっていうことですか?

A: 全てのメソッドを知っている必要はないけどね。どうせ心理実験で使う分にはTkinterのほんのわずかな機能しか必要ないから、 一回適当なひな形を作ってそれを使いまわせばいい。自分が知っている機能だけではどうも難しそうだな?と思ったらその時にTkinterのドキュメントを調べれば良いよ。

B: むむー。

A: 正直なところ、私もTkinterを使い始めて日が浅いんでわからない事の方が多いけど、実験に使う程度なら全然困ってないよ。 とにかく、ボタンを押した時の動作などを定義する方法を説明しよう。というわけで例題5-4に続く。

------------------------------ つづく ------------------------------