この文書は、Linux magazine 2005年1月号〜3月号に掲載された連載の草稿を、(株)アスキーLinux magazine編集部の許可を得て公開するものです。校正前の原稿なので読みづらいところもあるかと思います。不明な点などありましたらコメントをお送りください。
今回の目次
簡単に前回のおさらいをしておきましょう。
特に、どのような関数があってどう呼び出すのか、どのようなクラスがあり、そのクラスでどのようなメソッドが定義されているのか、を覚えていくことが重要です。今回は、まずは、いろいろなオブジェクトの使い方を見ていきましょう。
最も基本的で、多用するオブジェクトは、数値と文字列です。また、データを格納するためのオブジェクトでよく使うものに、タプル、リストがあります。
Pythonでは、数値は(短い)整数、長整数、実数、複素数に分けられます。それぞれクラス名は次の表のとおりです。
| 区分 | クラス名 |
|---|---|
| (短い)整数 | int |
| 長整数 | long |
| 実数 | float |
| 複素数 | complect |
-(231-1)〜(231-1) の整数はint、それより大きい整数はlong、小数点以下のある数値はfloatとなります。intとlongはコンピュータ内部での都合で分けられていて、通常は使い分けを意識する必要はありません。floatは(これもコンピュータ内部の都合ですが)計算誤差が発生する場合があるので、小数点以下が不要なときは整数を使います。
次のスクリプトは、数値の四則演算を行います。
print 2 + 3 * 4 #=> 14 print (2 + 3) * 4 #=> 20 print 7 / 3 #=> 2 print 7.0 / 3 #=> 2.33333333333 print 2 ** 3 #=> 8
普通の計算順序どおりに、掛け算・割り算は足し算・引き算より先に行われます。括弧で囲めば括弧の内側のほうが先に計算されます。「x ** y」は、xのy乗を表します。
割り算は、実数の場合は小数点以下まで計算されますが、整数同士だと切り捨てられます。(正確には答がマイナス無限大の方向へ丸められます。)
文字の連なりを文字列といいます。Pythonでは、文字列は「"」(ダブルクォーテーション)または「'」(シングルクォーテーション)で囲んで表します。文字列のクラスはstrです。
文字列同士は「+」で連結することができます(書き方は x + y)。数値であってもstr() 関数で文字列に変換すれば連結できます。
例えば、リスト1のようにして文字列を操作できます。
![]() | Note.
メソッドはオブジェクトのクラスごとに同じ名前を付けられる(たとえ違う内容であっても)ので、以下では必要に応じて、 クラス名#メソッド名と書いて、どのクラスのメソッドなのかを明確にします。スクリプト上では、 対象オブジェクト.メソッド名(引数, ...)と書くことに注意してください。 |
リスト1のそれぞれのメソッド(いずれもstrクラスのメソッド)の働きは次のとおりです。
文字列オブジェクト[s:t]と書くと、先頭から数えて位置sから位置 (t - 1) までの文字で新しい文字列オブジェクトを作ります。
文字列には、通常モード(ASCIIモード)とUnicodeモードがあり、文字単位で扱いたいときは、文字列をUnicodeモードにします。
![]() | ASCIIモードでの文字位置と文字コード
Pythonでは、文字列操作での文字位置は0から始まる数値で、コンピュータ内部での文字の位置です。これはASCIIモードのときは文字単位ではないことに注意してください。 コンピュータ内部では、文字列はバラバラにされて数値の連なりとして格納されます。ある文字がどのような数値にするかは、そのときの文字コードによります。別の言い方をすると、文字コードは、ある文字をどのような数値列と対応づけるかの規則です。 文字コードは、歴史的な事情により、シフトJIS、EUC-JP、UTF-8などいくつもあります。第1回で、プログラムの文字コードはUTF-8にするように書きました。UTF-8では、半角の英数記号文字は1文字1バイトですが、全角ひらがな・カタカナ・記号・漢字は、1文字3バイト(一部2バイト)になります。
同じ文字列をEUC-JPで保存すると、次のようになります。EUC-JPでは、全角文字は1文字2バイトになります。
|
文字列をUnicodeモードにするには、スクリプト中で文字列を書くときに「"」あるいは「'」の前に「u」を付けます。Unicode文字列のクラスは、unicodeです。メソッドは、strクラスの文字列オブジェクトと同じものが使えます。
リスト2で、通常モード(ASCIIモード)とUnicodeモードで文字列の扱いがどう変わるか見てみます。
Unicodeモードでは、find() の結果(戻り値)が文字単位になっていることが分かります。lower()で全角文字も小文字になるようになります。
数値や文字列だけでは複雑なデータを表すことはできません。そこで、複数のデータを束ねるための、データを格納できるオブジェクトを導入しましょう。
Pythonでは、タプル (tuple)、リスト (list)などを用います。
タプルとリストはいずれも要素を一列にして格納します。タプルはいったん生成すると、要素の追加、削除はできません。リストは後から追加も削除もできます。この違い以外は、タプルとリストは同じように使えます。
リスト3は、タプルとリストを生成し、list#append() メソッドでリストに後ろ(右)から要素を追加します。文字列と同様に、[n]あるいは[lower:upper]で、要素を参照したり、一部分からなる新しいタプル、リストを作ることができます。
格納すると言っても、オブジェクトの内部に要素が埋め込まれる訳ではありません。外から見ると格納されているように見えますが、内部ではオブジェクトへのリンクが張られています。リスト3で、変数sが指すリストに要素を追加すると、変数listのリストが変更されたように見えます。
オブジェクトは、図2のようにオブジェクト空間に浮かんでいます。listの2番目の要素と変数sは同じオブジェクトを指しています。
前回掲載したサンプルプログラムでは、ウィンドウとボタンを表示しただけで、押しても何も起こりませんでした。今回は、ボタンを押すと文字列を出力するようにしてみます。自分で関数を定義します。
自分で関数を定義するには、次のようにします。
def 定義する関数名 ( 引数名, ... ) :
文
文
...
defの行の最後に「:」(コロン)を書き、次の行から行を字下げ(左側に空白を入れる)して、実行する文を書いていきます。字下げするのを止めたところがその関数の終わりになります。
例で見たほうが感じがつかめます。リスト4は、on_clicked()関数を定義して、ボタンが押されたときにターミナル画面に「ボタンが押された!」と表示します。
Tkinter.Button()のキーワード引数commandで指定し、on_clicked()を登録します。ボタンが押された時には、Tk#mainloop()の中から登録された関数が呼び出されます。
変数がオブジェクトを指すラベル(付箋)だということは、前回も解説しました。変数は、文脈によっては、同じ名前であっても別のオブジェクトを指すことがあります。ある変数が有効な範囲をスコープ(scope)といいます。
変数は名前空間の中にありますが、ある関数が呼び出されてその中の文が実行される時、新しい名前空間が導入されます。関数の引数、それから関数の中で代入した変数は、関数の外側の変数とは別の名前空間に属します。関数の中の文の実行が終わるときに、その関数の名前空間は消滅します。
次のプログラムは、関数fooの外側と内側で同じ名前の変数yを使っています。
y = 100
def foo(x):
y = x * 2
print "in foo(): y = ", y
foo(1)
foo(2)
print y
foo() の中で変数yに代入していますが、外側の変数yには影響しません(外側のyが指すオブジェクトは変わりません。)(図3)。このプログラムの実行結果は、次のようになります。
in foo(): y = 2 in foo(): y = 4 100
関数内でのみ有効な変数をローカル変数といいます。言い換えると、ローカル変数のスコープは関数です。なお、すべての関数のなかから参照できる変数をグローバル変数といいます。
ただし、関数呼び出しでは、名前(変数)が新しく取られるだけで、オブジェクトがコピーされるわけではありません。引数として与えられた変数を介してオブジェクトを操作すると、その影響は呼び出した側にまで及びます。
次のプログラムは、リストを関数の中で変更します。メソッドの外側で生成したオブジェクトが変更されます。
def hoge(ary):
ary.append(10)
ary = [1, 2, 3]
hoge(ary)
print ary #=> [1, 2, 3, 10]
これまでのサンプルでは、ラベル、ボタンをウィンドウに貼り付けてきました。Tkinterには、それ以外にも、いろいろなものがあります。
ウィンドウに貼れるものをウィジェット (widget) といいます。
![]() | Note.
ウィジェットは、「コントロール」あるいは「コンポーネント」と呼ぶこともあります。 |
いくつかのウィジェットをウィンドウに並べてみます(リスト5)。実行すると、画面1のように表示されます。
ウィジェットのクラスとその内容は表1のとおりです。それぞれのクラスの使い方は、紙面もないので、An Introduction to Tkinter などを参照してください。
| Tkinterクラス名 | 内容 | ||
|---|---|---|---|
| Widget | |||
| Button | 押しボタン | ||
| Canvas | キャンバス | ||
| Checkbutton | チェックボタン | ||
| Entry | エントリ(1行テキスト入力) | ||
| Frame | フレーム | ||
| Label | ラベル | ||
| LabelFrame | ラベル付きフレーム | ||
| Listbox | リストボックス | ||
| Menu | メニューバー | ||
| Menubutton | (推奨されない。MenuかOptionMenuを使う。) | ||
| OptionMenu | オプションメニュー | ||
| Message | (推奨されない。Labelを使う。) | ||
| PanedWindow | ペイン区切り | ||
| Radiobutton | ラジオボタン | ||
| Scale | スケール(スライダ) | ||
| Scrollbar | スクロールバー | ||
| Spinbox | スピンボックス | ||
| Text | 複数行テキスト入力 | ||
ウィジェットの機能のうち、共通の部分はWidgetクラスで定義されています。リファレンスマニュアルなどでは、調べたいウィジェットのクラスだけではなく、Widgetクラスも調べる必要があります。オプションメニューについても、OptionMenuクラス、Menubuttonクラス、Widgetクラスを遡って調べる必要があります。
Tkinterでは、マウスのボタンが押された、ドラッグされた、キーボードのキーが打たれた、など、ユーザーがウィンドウ上で何らかのことをしたりすると、イベントとしてプログラムに伝えられます。
イベントは、マウスのボタンを押したときの座標、どのボタンを押したのか、どのキーを打ったのか、などの情報をまとめたものです。イベントを受け取るためには、あらかじめ関数を登録しておく必要があります。関数を登録しておくと、これらユーザーの操作などがあったときに、Tk#mainloop()の中からその関数が呼び出されます。
すでに見たように、押しボタン(Buttonクラス)では、commandキーワード引数で関数を登録しておけば、クリックしたときにその関数が呼び出されました。ウィジェット特有のイベントはこのように特別な指定方法がありますが、一般的なイベントは、bindメソッドでイベントと関数を結び付け(束縛)ます。
bindメソッドは次のように書きます。
widgetオブジェクト . bind ( イベントを表す文字列, 関数・メソッド名 )
例えば、リスト7のようにします。「<Button-1>」は、マウスの左ボタンを押す、というイベントです。実際にイベントが発生したときには、イベントの情報を格納したオブジェクトを引数として、登録しておいた関数が呼び出されます。
表2に、イベントの一部を掲げます。
| イベント | 内容 |
|---|---|
| <Button-x> | マウスのボタンが押された。xはボタン番号で、1=左ボタン、2=中ボタン、3=右ボタン |
| <Bx-Motion> | マウスがドラッグされた。xはボタン番号。 |
| <ButtonRelease-x> | マウスのボタンが離された。xはボタン番号 |
| <Double-Button-x> | マウスがダブルクリックされた。xはボタン番号 |
| <Enter> | マウスポインタがウィジェットに重なった(入ってきた)。キーボードのEnterキーが押されたにあらず。 |
| <Leave> | マウスポインタがウィジェットから離れた |
関数は、引数として与えられたオブジェクトと、関数内で生成したオブジェクト、グローバル変数しか参照できません。大きな仕事をするには、多くの関数が協力・分担して、いろいろなことができなければなりません。
すでに見てきたように、それぞれのオブジェクトは多くのメソッドを持ち、これらがオブジェクトの内部情報を共有しています。外側から見ると、メソッドに対して指図することで(メソッド呼び出し)、オブジェクトを操作します(図4)。
我々もこのようなオブジェクトを作っていい頃合いです。オブジェクトの振る舞い(メソッド)は、クラスで定義します。
クラスを定義する文法は、次のようになります。
class 定義するクラス名 :
メソッド定義など...
メソッドを定義する文法は、関数とほとんど同じで、次のようになります。
def 定義するメソッド名 ( 自オブジェクト変数名, 引数, ... ) :
文...
関数定義と違うのは、最初の引数として操作対象となるオブジェクトを指す変数を用意することだけです。この変数の名前は何でもいいのですが、慣例的にselfとします。
字下げして文を書いたり、呼び出されるたびに新しい名前空間が導入されるのも同じです(ローカル変数のスコープも同じ)。
実際の書き方をリスト8に示します。__init__メソッドは、特別なメソッドで、オブジェクトが生成されるときに呼び出されます。
オブジェクトの内部状態を保持する変数をインスタンス変数といいます。インスタンス変数は、次のように書きます。
オブジェクト . 変数名 # 文法
上記のリスト8では、self.value がインスタンス変数です。インスタンス変数は、オブジェクトが生成されるたびに作られ、オブジェクトのメソッドで共有できます。オブジェクトが消滅するまで生き続けます。
それでは、これまでに見てきたことを踏まえて、ごく短いお絵かきプログラムを書いてみます(リスト9)。実行結果は、画面2のようになります。
どのようなことをしているか、順に見ていきましょう。
お絵かきのためのキャンバスオブジェクトと、プログラムを終了するためのボタンオブジェクトを作ります。キャンバスオブジェクトは、bg引数で背景を白に、widthとheightで大きさを指定します。
ボタンオブジェクトには、Tkinter.Tk#quitメソッドを登録しておきます。クリックされたら、このメソッドが呼び出され、プログラムが終了します。
キャンバスにはbind()でイベントを結び付けます。ユーザーがマウスの左ボタンを押したとき、マウスをドラッグしたときに、それぞれon_pressedメソッド、on_draggedメソッドが呼び出されるようにします。
ボタンが押されたら点を描きます。Tkinter.Canvas#create_oval()は楕円を描くメソッドです。この上下左右の座標を1点にすることで、点を描きます。
インスタンス変数sx、syに現在位置を入れておきます。on_dragged()でこれを使います。
マウスがドラッグされたときは、直前の座標との間に線を描けばうまくいきます。on_pressed()で保存した座標との間に、Tkinter.Canvas#create_line()で線を描きます。
その後、最後の座標(sx, sy)を更新しておきます。こうすることで、さらにマウスを動かしたときに、今度はここから次の座標まで線を引けます。
前節リスト9では、線の色と太さが一定でした。オプションメニューとスケールを使って、これらを変更できるようにしてみましょう。
Scribbleクラスに新しいメソッドを追加します(リスト10)。
このメソッドは、オプションメニュー(OptionMenuクラス)オブジェクトを生成し、それをウィンドウに配置します。
このメソッドの動作を順に説明すると、
Tkinter.OptionMenu(window, self.color, "red", "green", "blue", "#FF00FF", "black")
メソッドを作ったら、Scribble#__init__メソッドに、このメソッドを呼び出す文を追加します(リスト11)。
さらに、点を打つところ、線を引くところを修正しておきます(リスト12)。
これで色を選べるようになりました(画面3)。
今度は線の太さをスケールを使って変えられるようにしてみましょう。また新しいメソッドを作ります(リスト13)。
このメソッドは、スケールオブジェクトを生成し、ウィンドウに貼り付けます。
新しいメソッドを作成したら、Scribble#__init__()で、width_chooser()を呼び出す文を追加します。color_chooser()を呼び出す行の次がいいでしょう(リスト14)。
加えて、on_pressed(), on_dragged()も修正します。self.width.get()で現在のスケールの値を得ます。これでペンの太さと色を変えられるようになりました(画面4)。
Netsphere Laboratories http://www.nslabs.jp/
[PR]