この文書はDr. Corran WebsterさんによるW Widgetsの"Calculator"を訳したものです。
訳の公開に快く承諾してくださったDr. Corran Websterさんに感謝いたします。
内容の理解が伴っていないので、おかしいところがあるかもしれません。お気づきの点がございましたら、わたしまでお願いいたします。

[ Calculator Image ]

計算機


IDEの中でWウィジェットを使う
Numberダイアログ / 計算機
練習

このセクションでは、IDE上のウィンドウとして現れる計算機のプログラムを作ります。 このなかで、イベントのバインド、自動的にウィジェットを生成するといったことに関連する 便利なトリックをいくつか見ていきます。

IDEで新しいウィンドウを開くことから始めます。そこにプログラムを入力し、IDEの"Run all"コマンドを使って実行します。

はじめに、Wと、他にいくつか便利なモジュールをインポートします。 キー入力を横取りするので、便利な定数が定義されているWkeys。 ちょっとしたキー入力を省くためにstring。そしてもちろん、mathモジュールの関数の全てが必要です。なんたって、計算機を作るのですから。

import W
import Wkeys

import string
from math import *

つぎに、いくつかウィジェットを作ります:メインのウィンドウと、入力された数式と結果を表示するテキストフィールドです。

w = W.Window((148,246), "Calculator")

w.display = W.EditText((4,4,-4,22), "0", fontsettings=("geneva",0,12,(0,0,0)))

次に、入力可能なキーを制限するのに有効なルーチンを作ります。なんでも入力できるとしたら そういう計算機はとても強力ですが、入力を制限すると計算機を安全にユーザーフレンドリーにします。もっと複雑にすることは可能ですが、ずっと難しくなります。また、ここの例のようにテキストフィールドへの入力を制限する方法を知っておくととても便利です。

ここから始まるのは、例えば'<key>'が何かを返すというオプションを持つように、カギカッコの中にイベントをバインディングすることです。その結果、返されたものがtrueならWはイベントの実行停止します。これを使って、キー入力がテキストウィジェットへそれ以上届かないようにすることができます。(XXX ウィンドウのdo_charハンドラで、もしそれがウィンドウにバインドされていたら、キーイベントをブロックします。しかし、もしそれが選択されたウィジェットにバインドされていたら、個々のキーイベントをブロックしません。key()だけはブロックしますが....なぜって?メニューやメニューアイテムなどに伝えるためです、たぶん?)

clearkey = '\033'
okkeys = ("0123456789c=+-*/() " + Wkeys.backspacekey + Wkeys.deletekey 
        + Wkeys.returnkey + Wkeys.enterkey
        + string.join(Wkeys.arrowkeys) + clearkey)

def blockkey(char, event):
    if char not in okkeys:
        return 1
    else:
        return 0

w.bind('<key>', blockkey)

残りのウィジェットを作る前に、いくつかイベントハンドラを作る必要があります。 do_clearは、表示をクリアします。do_evalは計算機の心臓部です - 表示された内容を計算し、いくつか単純なエラーを処理します(なにか大きなエラーはIDEへ送られます)。

def do_clear():
    w.display.set("0")
    w.display.selectall()

def do_eval():
    expr = w.display.get()
    try:
        out = str(eval(expr))
        w.display.set(out)
        w.display.setselection(len(out),len(out))
    except ZeroDivisionError, what:
        w.display.set("Division by zero")
        w.display.selectall()
    except SyntaxError, what:
        w.display.set("Invalid expression")
        w.display.selectall()
    except OverflowError, what:
        w.display.set("Number too large")
        w.display.selectall()

次に、ちょっとしたトリックを使います。一つひとつのボタンを作ってそれぞれにコールバックを 割り当てることはできますが、これではすぐめんどうになってしまうでしょう。それに、計算機のレイアウトを変えようと思ったら(例えばもうひとつボタンを加えようとしたら)、これはまたひと騒動です。なぜなら、一つひとつのボタンの位置を手書きで変えるというところまで戻らなければならないのですから。その代わりに賢くいきましょう。ほとんど全てのボタンは同じことをしています: ボタンはクリックされると表示ウィジェットにテキストを挿入します。これらのステップを一つひとつについて関数に記述してするのは可能ですが、もっと巧妙な手があります。任意のコールバックを作るのにlambdaを使うんです。こんな感じです:

lambda x=char: w.display.insert(x)
これはまさにPythonの標準的な慣用句です。こんなふうにlambdaを使ってコールバックを作るのは、 PythonのGUIプログラミングではふつうのテクニックです(技術的なことを言うとclosureの形です[訳注:意味知りません])。他の方法を選ぶとすれば、__call__メソッドを持ったクラスを書くことでしょう。こんなふうに:
class MyCallback:
    def __init__(self, char):
        self.char = char
    def __call__(self):
        w.display.insert(char)
そして、こうすればたちまちコールバックを作ることができます:
MyCallback(char)
これはもうひとつのPythonの標準的な慣用句です。はじめのよりさらに汎用性が高く、 "Pythonの流儀"にちかいものですが、面倒でもあります - わたしたちが望んでいるのはほんとに簡単な機能ですが、呼び出し可能なクラスインスタンスを作る方法をいろいろ見てきました。 この場合は、最初のテクニックを使うのがいいでしょう。

3番目の方法があって、それについては 新しいウィジェットクラスを作るあるいはW.Buttonをご覧下さい。

さあ、これでわたしたちが知りたかった、自動的にコールバックを作る方法がわかりました。 自動的にボタンを作るのは難しくありません。適切な変数とともにW.Buttonを 呼び出すだけです。表示したい文字列と、作ろうとするウィジェットの名前のリストがあれば、 このようになります(実際それは並びを示すリストで、きれいな四角形の配列で示されます):

buttons = [[("(", "leftpar"), (")", "rightpar"), ("pi", "pi"), ("**", "pow")],
        [("sin", "sin"), ("cos", "cos"), ("tan", "tan"), ("/", "divide")],
        [("7", "seven"), ("8", "eight"), ("9", "nine"), ("*", "times")],
        [("4", "four"), ("5", "five"), ("6", "six"), ("-", "minus")],
        [("1", "one"), ("2", "two"), ("3", "three"), ("+", "plus")],
        [("0", "zero"), (".", "point"), ("C", "clear"), ("=", "eval")]]

次にしなければならないのは、配列の内容をくり返してウィジェットを作ることです。 そして、できたボタンのpush()メソッドに適切なキー入力をバインドすることです。

for i in range(len(buttons)):
    for j in range(len(buttons[i])):
        char, symbol = buttons[i][j]
        if char != "C" and char != "=":
            w[symbol] = W.Button((4+36*j,30+36*i,32,32), char,
                    lambda x=char: w.display.insert(x))
            w.bind(char, w[symbol].push)

このようになります。自動的にボタンの位置と、タイトル、コールバックを作る方法がわかりましたね。残ったのは、文字を表示しないclearevalの2つのボタンです。コールバックは作りましたので(訳注:do_clear、do_evalとして先ほど作っています)、これら2つのウィジェットは手書きで作ります:

        elif char is "C":
            w.clear = W.Button((4+36*j,30+36*i,32,32), "C", do_clear)
            w.bind('c', w.clear.push)
            w.bind('shiftC', w.clear.push)
            w.bind(clearkey, w.clear.push)
        elif char is "=":
            w.eval = W.Button((4+36*j,30+36*i,32,32), "=", do_eval)
            w.bind('=', w.eval.push)
            w.bind(Wkeys.returnkey, w.eval.push)
            w.bind(Wkeys.enterkey, w.eval.push)

そして最後に、ウィンドウを開きます。

w.open()

これで計算機を表示でき、IDEウィンドウの一部として実行できます。 何回も実行すると、いくつも独立したウィンドウが作られます。 入力の手間が省けるよう、完成したソースコードを用意しました。

この計算機自体はあまり実用的ではありませんが、発展性があります。 使いやすくするためには、Pythonのインタラクティブウィンドウでは得られないものをもっと加える必要があります - たぶんgrapherウィジェットか、何か代数的な操作を加えるべきでしょう。 本格的なものにするためには、mathモジュールの残りの関数はもちろん、

練習

  1. 科学計算がもっとできるよう、exploglog10といったボタンを加えましょう。 Add in some new buttons to allow more scientific math, like exp, log or log10.
  2. エラー処理は理想的ではありません。なぜなら、エラー処理はエラーを起こしたテキスト入力をオーバーライトするからです。(訳注:?)ダイアログボックスを出してエラーが起きたことを示し、さらにエラーが起きたところにカーソルを移動させるようにしましょう。
  3. このプログラムでは、割り算の中のどちらの数も整数なら、Pythonのように整数同志の割り算がなされます。また、大きな数値でも自動的にはlong型の整数に変換されません。 do_evalを修正して、計算機が自然な動作をするようしましょう。 簡単な方法は、十分大きな数値の後に"L"を加えるような仕組みを新たに加えて、文字列を事前に操作することです。さらに洗練された方法は、計算値の算出の前に、標準ライブラリの中のモジュール(訳注:reとかstringとか?)を使って品詞分解してテキストを操作することです。