例題1-2:スペースキー押しで順番に文字列を表示する

A: 今回はキーボードのスペースキーが押されるのを待って、画面を書き換えるプログラムに挑戦しよう。前回の「一定時間待つ」と今回の「押されるまで待つ」は心理実験には頻出する処理で、これらをマスターすれば実験プログラムの5%くらいはマスターしたようなものだ。

B: たったの5%ですか。道のりは遠いですね…。

A: 時間待ちやキー押し待ちはすごく出番が多いのでほぼ毎回のように使うのは間違いないんだけどね。適当な数字を出したけど実際のところはどのくらいの割合だろうなあ。まあ脱線はこれくらいにして今回の例題に対するサンプルプログラムはこれだ。

  • 行番号なしのソースファイルをダウンロード→ 01-2.py

 1#!/usr/bin/env python
 2# -*- coding:shift_jis -*-
 3
 4from VisionEgg import *
 5from VisionEgg.Core import *
 6from pygame import *
 7from pygame.locals import *
 8
 9from VisionEgg.Text import Text
10
11
12VisionEgg.start_default_logging()
13VisionEgg.watch_exceptions()
14VisionEgg.config.VISIONEGG_GUI_INIT = 1
15
16screen = get_default_screen()
17
18sentenceList = [u"お腹すいたなぁ",u"あ、財布忘れた",u"しょんぼり"]
19
20textobj = Text(text="",anchor='center',position=(512,384),
21               font_name=r'C:\Windows\Fonts\MSGOTHIC.TTC',
22               font_size=40)
23
24viewport = Viewport(screen=screen, stimuli = [textobj])
25
26for s in sentenceList:
27	textobj.parameters.text = s
28	waitingKeyPress = True
29	while waitingKeyPress:
30		for e in event.get():
31			if e.type == KEYDOWN and e.key == K_SPACE:
32				waitingKeyPress = False
33		screen.clear()
34		viewport.draw()
35		swap_buffers()
36
37
38textobj.parameters.text = u"再生してください"
39ct = st = VisionEgg.time_func()
40while ct-st<10.0:
41	ct = VisionEgg.time_func()
42	event.get()
43	screen.clear()
44	viewport.draw()
45	swap_buffers()

B: 前回が19行でしたから一気に倍以上ですね。ついていけるかな。

A: 実は前回のプログラムと同じ部分が多いんだよ。例えば16行目まではimportしているクラスが少し増えただけ。39~45行目は44行目のviewport.draw()というのだけが新顔で、あとは前回と同じだ。では18行目から見ていこう。

B: なんか日本語を見るとほっとしますね。で、なんですかこれ?

A: これはスペースキーが押された表示される文章を順番に並べてsentenceListという変数に代入しているところだ。pythonでは、複数の値を順番に並べて[ ]でまとめてひとつの変数に代入する事が出来る。このような変数を リスト という。

B: 順番って? 何で決まるんですか?

A: 自分が使いやすい順番に並べたらいい。今回の例題では、最初に「お腹すいたなぁ」という文章を表示して、スペースキーが押されると「あ、財布忘れた」、もう一度押されると「しょんぼり」という具合に文章を切り替えていく。 だからこの順番に並べているんだよ。

B: 値って3.0とか-7.2みたいな数字じゃなくて文章でもいいんですか? あとu"とか"とかって何ですか?

A: いっぺんに聞くな。まず最初の質問については、実は文章はリストと同じように文字のデータを順番に並べた 文字列 というデータ型の値となる。その事を説明する前にリストの使い方を説明しておこう。 リストに収められた値の一部を参照したい場合は、[ ]という演算子を使う。次の例を見てほしい。出来れば準備編2で説明したpythonインタプリタで試してみよう。

>>> var = [100,200,300,400,500] # 変数varにリストを代入<
>>> var[2]   # varに収められたリストの2番目の値。pythonでは0番目から数えるので2番目は300。
300
>>> var[-1]  # 負の数字を指定すると、最後から数える。
500
>>> var[2:4] #2つの数字をコロンで区切って書くとリストの一部分を取り出す事が出来る。
[300,400]

B: ??? var[-1]とかvar[2:4]がわけわかりません。var[2]は300、var[4]は500なんですよね。じゃあなんでvar[2:4]は[300,400,500]じゃないんですか?

A: うーん、私も初めてpythonを勉強した時に変だなあと思ったんだけど、そういう文法なんだから仕方ないな。慣れるしかない。一応Pythonの公式ドキュメントには、[ ]の中の数値は要素と要素の間を指すと考えなさいという記述がある。 図にするとこんな感じだな。

../_images/01-2-05.png

B: 図にされるとなんとなくわかったような、でもやっぱりわからないような…

A: ま、とにかくこの書き方をマスターしとくとすごく便利だから覚えておくように。var[2:4]のようにリストの部分を取り出す演算を スライス という。ちなみにリストの先頭からスライス、もしくはリストの末尾までスライスする時には以下のように省略できるんだ。

>>> var = [100,200,300,400,500]  # 変数varにリストを代入
>>> var[:4]                      # リストの最初から4番目の手前までスライス。
[100,200,300,400]
>>> var[3:]                      # リストの3番目の後から最後までスライス。
[400,500]

B: うわ、気持ち悪い。これ覚えないと先へ進めないんですか?

A: 他人が書いたプログラムを読んで理解しようとしたら必要になるかも知れないね。自分で書くときは、自分でわかる方法で書けばいいのさ。 そうやって場数を踏みながら、少しずつ文法をマスターしていけばいい。さて、これを踏まえてさっきの質問に戻るわけだが。

B: さっきの質問ってなんでしたっけ。

A: こらこら。自分で質問しといて忘れるな。リストに放り込む値は文章でもいいんですかって奴だよ。結論から言うと文章、すなわち 文字列 はリストと同様にひとつひとつの文字を並べたものなんだ。こんな具合に。

>>> var = "Hello, World!" # 変数varに文字列を代入
>>> var[9]                # 先ほど代入した文字列の9番目の文字、すなわち"r"が得られる。最初の文字を0番目として数えることと、HelloとWorldの間の空白も一文字として数える事に注意。
'r'
>>> var[3:9]              # 3番目から9番目の手前までの文字列。すなわち"lo, Wo"が得られる。
'lo, Wo'

B: リストと「同様に」って、リストと同じじゃないんですか?

A: 厳密に言うと、pythonにはsequence型と呼ばれるデータ型があって、リストと文字列はどちらもsequence型の一種だ。それぞれに固有の便利な機能があるんだが、それはまた機会があれば話すよ。

B: ふーん、難しいですねぇ。あの、文字はひとつの値と考えていいんですね? あとなんで"Hello, World!"なんですか?

A: "Hello, World!"を表示するってのは昔からプログラミング教科書で使われてきた有名な例題なのさ。 文字はひとつの値であるってのはその通りで、文字コードと言って、コンピュータでは全ての文字に数字が割り当てられている。例えば半角大文字のAは10進数の65(16進数の41)だ。 「文字コード」でwebを検索するといろいろ面白い話が出てくるからぜひ調べてみてくれ。

B: (またうまいこと言って自分で説明する手間を省いてるな…)

A: ん?何か言ったか? まあずいぶん脱線してしまったが、リストを作る時に、その要素としてリストを持ってくる事が出来る。例えばだな、

>>> var = [1,2,[100,200,300],4,[400,500]] # 2番目と4番目の要素がリストになっている
>>> var[2:4]                              # 2番目から4番目の手前までの要素、すなわち[[100,200,300],4]というリストを得る
[[100,200,300],4]

B: リストと普通の数値を混ぜてもいいんですね。ところでこの例で200という値を取り出したければどう書けばいいんですか?

A: 200が入っているリストはvarに収められたリストの2番目の要素だから、まずvar[2]でそのリストを取り出す。 で、200はそのリストの1番目だから、数学で計算の順番を指定するように括弧を使って(var[2])[1]と書く。(3+7)×2を計算する時に括弧の中の3+7を先に計算するのと同じだね。 もっとも、いちいち括弧を書くのも面倒くさいのでvar[2][1]と続けて書いて良い事になっている。

B: リストの中にリストが入っていて、その中にリストが、さらにその中にリストが入っていたら?

A: [ ]をどんどん連ねていけば良いのさ。var[2][1][3][5]とかいった具合に。こういう多重リストもよく使われるので、そのうち嫌でも慣れるよ。

B: …まだ1行も解説が済んでいないのにもう疲れてきました。

A: まだもう一つ質問が残っているぞ。覚えているか?

B: ええと…そうそう、18行目の「お腹すいたなぁ」とかの前後についているu"とか"って何ですか、でしたっけ。 A: そう。これは"とuに分けて話をしないといけない。まず"は文字列の範囲を指定する。 例えばwhileっていう文字列を指定したい時に、プログラムの中にいきなりvar = whileって書いてあったら制御構文のwhileと区別がつかないでしょ。 だからvar = "while"と書いてここからここまでは文字列ですよ~というのをコンピュータに教えてやらないといけないわけだ。

B: 文字列中に"を入れたい場合はどうすればいいんですか?

A: "の代わりに'を使っても文字列の範囲を指定する事が出来る。'He said, "I'm hungry."'とか。 他にもエスケープ文字というのを使う方法もあるんだが、それを言い出すと長ーくなるんで次回以降に説明するよ。

B: ホントに説明するんですね?

A: ○○を参考に自習してほしいと言いたいところだけど、重要なポイントだからきちんと説明するよ。たぶん。きっと。

B:

A: えー、ごほごほ。で、文字列の前のuだが。これは文字列がUnicodeである事を示している。Unicodeは知っているか?

B: ぜーんぜん知りませーん。

A: さすがに疲れてきたか? まあもうちょっと頑張れ。さっき文字コードという話が出たけど、文字コードには何種類もあるんだ。例えば日本語の文字コードにはShift-JISとかISO-2022-JPとかEUC-JPとかいうのがある。 欧文だとISO-8859-1とか。これは数字と文字の対応を定めた暗号表みたいなものだから、当然複数の文字コードを混ぜて使う事は出来ない。それじゃ不便だというので出てきたのがUnicodeというやつで、 世界中のさまざまな言語で使用される文字を網羅している。まあ万能じゃないんだけど、それでもpythonでの日本語の扱う上でいろいろ便利なコードなのさ。試しに後でこのサンプルプログラムのuを抜いて実行してみたらありがたみがわかると思うよ。

B: 気力が残ってたらやってみます…

A: これで18行目の解説は終わりだね。一休みするか。

------------------------------ 休憩中 ------------------------------

A: さて、気分を入れ替えて20行目、から、…

B: どうしたんですか?

A: いや、この行もまともに説明しようとしたら、すんごい時間かかるなと思って…。どうするかな。

B: なんでもいいからさっさとプログラムを動かすところまで行ってほしいです。

A: そうか、じゃあここは次回詳しく説明する事にしよう。一応簡単に説明しておくと、ここでは9行目でimportしたVisionEgg.TextのTextという機能を利用して、画面に文字列を表示するための準備をしている。 そして24行目では文字列を画面に描画するために登録しているんだ。そして26~35行目、ようやく今回の例題の中心部分だ。

B: やっとですか。

A: まあそう言うな。この部分でスペースキーが押されるのを待ち、押されたら文字列を書き換えている。実は、スペースキー押しを待つ部分は前回の「5秒待つ」とよく似てるんだよ。対応させて書いてみるとこのようになる。 (略)ってのは長いから省略しただけでもちろん本当のプログラムではこう書くんじゃないぞ。

#スペースキー押しを待つ                 #5秒待つ
waitingKeyPress = True                  ct = st = VisionEgg.time_func()
while waitingKeyPress:                  while ct-st<5.0:
    for e in event.get():                   event.get()
        if e.type == KEYDOWN and ()       ct = VisionEgg.time_func()
            waitingKeyPress = False
    screen.clear()                          screen.clear()
    viewport.draw()                         viewport.draw()
    swap_buffers()                          swap_buffers()

A: ポイントは二つ。whileの終了判定のために「スペースキーを待っています」という状態を保持する変数waitingKeyPressを用意していること。 それからwhileの中でスペースキーが押されているのを確認する方法だ。

B: waitingKeyPress = Trueって、Trueは値なんですか?

A: その通り。Falseも値として使用できる。で、次のwhile文だが、式のところにwaitingKeyPressとだけ書いてあるだろ? こういう場合、変数に入っている値がそのまま式の評価になる。Trueが入っていれば評価はTrueだ。

B: ふんふん。

A: 次の文に出てくる for は今回新たに学ぶキーワードだ。forはwhileと同じく繰り返しを指示する制御構文だが、 for 変数 in リスト: という構文で、リストの要素を一つずつ取り出して変数に代入し、以下に続く字下げされている文を実行するという作業を リストの最後の要素に達するまで繰り返すんだ。

B: ???

A: つまりだな、日本語を使ってそれっぽく書くと

for x in [りんご,みかん,もも]:
    xを食べる

というfor文があった場合、順番にりんご、みかんを食べて最後にももを食べたら終了するんだよ。

B: これ、コンピュータで実行できるんですか!?

A: そんなわけないだろ!

B: 冗談です。なるほど、なんとなくforの動作がわかりました。でもサンプルプログラムでは for e in event.get():って書いてますよね。event.get()はリストなんですか?

A: むむ。察しがいいんだか悪いんだかわからないな。event.get()は実行されるとマウスやキーボードが操作されたというイベントのリストを返すんだ。

B: …「返す」? ??

A: 20行目の説明を次回に回したのが裏目に出たかな。具体的にはこんな感じのリストになるんだ。

[<Event(4-MouseMotion {'buttons': (0, 0, 0), 'pos': (366, 452), 'rel': (366, 452)})>,
 <Event(4-MouseMotion {'buttons': (0, 0, 0), 'pos': (352, 218), 'rel': (-14, -234)})>,
 <Event(4-MouseMotion {'buttons': (0, 0, 0), 'pos': (350, 212), 'rel': (-2, -6)})>,
 <Event(4-MouseMotion {'buttons': (0, 0, 0), 'pos': (340, 200), 'rel': (-10, -12)})>,
 <Event(17-VideoExpose {})>, <Event(1-ActiveEvent {'state': 2, 'gain': 0})>,
 <Event(1-ActiveEvent {'state': 1, 'gain': 0})>]

B: うわ、なんですかこれ!

A: まだ説明していない文法を使いまくっているのでわからなくて当然。とにかく、event.get()はここではリストなんだって思っておけば十分。

B: 説明されてないものを置き去りにしまくっている気がしますが、大丈夫なんですか。

A: まあ、まず文法からきっちり学びたい人向けの企画じゃないから、コレ。気になる人はきちんとした本で文法を学びながら読んでほしい。

B: また他力本願な…

A: うるさい。続いてforで繰り返す作業の解説。ここで今回新たに学ぶ制御構文がもうひとつ出てくる。 if だ。 ifは、その右に続く式を評価した結果が真であれば直後に続く字下げされた文を実行し、偽であればifの後に現れる else に続く字下げされた文を実行する。 これも日本語を使ってそれっぽく書いてみよう。

if 財布に1000円以上入っている:
    定食屋で食べる
else:
    コンビニでパンを買って食べる
家に帰って昼寝する

A: この場合、財布に1500円入っていれば定食屋で食べた後家に帰って昼寝する。700円しかなければコンビニでパンを買って食べた後、やはり帰宅して昼寝する。 「家に帰って昼寝する」が字下げされていないことに注意してほしい。

B: 財布に1000円以上入っていなければパンって、もし1円もはいっていなければどうするんですか! 僕の財布には1円も入ってないことがしょっちゅうあります!

A: そんなこと自慢するな。その場合はさらに条件分けすればいい。幸いifとelseをくっつけた elif という便利な構文がある。

if 財布に1000円以上入っている:
    定食屋で食べる
elif 財布にコンビニの一番安いパンを買えるだけお金が入っている:
    コンビニでパンを買って食べる
家に帰って昼寝する

B: ん? elseがなくなっちゃいましたがお金が1円もない場合はどうなるんですか?

A: 最初のifで1000円ないから後にelse(elif)がないか探す。elifが見つかるが、ここでもパンを買うお金がないから後にelse(elif)がないか探す。 すると字下げされていない「家に帰って昼寝する」が見つかる。この時点でif構文は終了となり、そのまま「家に帰って昼寝する」の処理に入る。 要するに、elseがない場合は、if(elif)に掲げられた条件に引っかからなければ何もせずに次の処理に入るんだよ。

B: むむむ。難しい。

A: まあ慣れだよ。慣れ。例題のサンプルプログラム31行目のifではまだ説明していないテクニックを使ってるんだけど、 キーボードのキーが押されている(KEYDOWN)という条件と、押されているキーがスペースキーである(K_SPACE)という条件をチェックしていると思ってほしい。

B: また先送り…

A: まあ、いずれきちんとした解説を作るよ。いずれ。今は先へ進ませてもらうと、31行目のif文で式が真になった時に実行されるのは32行目のwaitingKeyPress = False。 waitingKeyPressという変数は29行目のwhile文で使われていたのを覚えてるかな。スペースキーが押されるとここでwaitingKeyPressはFalseとなり、次に29行目のwhileへ戻った時にwhileによる繰り返しが終了する。 elseが使われていないから、スペースキーが押されていなかった場合は何もせずに33行目以降の処理へ入る。

B: うーん、なんとなくわかったような。

A: よしよし。じゃあ次が今回の解説で最後のポイントだぞ。なんと、 whileの中にfor、ifの中にwhileといった具合に制御構文は組み合わせて使う事ができる のだっ!

B: (しらー)…そんなことわかってますが。

A: あら、やっぱり? 今forとifの解説の中でなんにも言わずに組み合わせて使っちゃったからね。まあでもこれは大事なポイントなので強調させておいてくれ。 B: はいはい。

A: 組み合わせて使う時に、ひとつ注意しないといけない点がある。制御構文は完全な入れ子構造になっていなければならない。

B:

A: つまりだな、例えばwhileの中にifとelseを入れる場合、ifとelseはwhileで繰り返されるブロックの中にすっぽり入っていなければならない。ifの対になるelseがwhileの外にあってはいけないんだよ。こんな感じ。 さっき、ifは後ろにelseがないか探すって言ったけど、whileの中にいる場合はwhileで繰り返されるブロックを越えて後ろへelseを探しに行ったりしないんだよ。だから右の例でelseはifのペアとは見なされない。

../_images/01-2-01.png

B: じゃあ右の例はエラーになるんですね。

A: あー。実はpythonの文法ではelseはwhileとペアにして使う事も出来る。だから、右の例の場合、elseはwhileのペアとして解釈される。whileにelseを組み合わせると何が出来るのかは置いといて、とにかく while、for、ifといった制御構文を使う時は、その範囲がどこまでおよぶのかをしっかり意識しながらプログラムを書くことが大切だ。pythonの場合、行の字下げでこの範囲が決まるので、 字下げにはくれぐれも気を付けてほしい。

B: はーい。

A: さて、いいかげん私も疲れてきた。一気に解説を終えるぞ。29~35行目でスペースキーが押されるのを待つ事が出来るようになった。 後はキーが押されたら画面に表示する文章を入れ替えるだけだ。26~27行目がその作業だ。 心理実験では、延々と同じ作業を繰り返すことが多い 。 だから、 まず一回だけ実行するプログラムを書いて、それをforやwhileの中へ入れて繰り返すように拡張する といいぞ。

B: ええと、26行目のforで、まずsentenceListの最初の「お腹がすいたなぁ」を変数sに代入する。そして繰り返しに入る…。

A:

B:

A: ん?どうした?

B: 27行目のtextobj.parameters.text = sがわかりません。=を使ってるんだから左は変数ですよね。 この長ったらしい名前の変数、この行以外どこにも出てこないんですが、何に使ってるんですか?

A: そこに疑問を持ったのは偉い。でも、その質問に答えるには説明をすっ飛ばしちゃった20行目を理解する必要があるんだよなぁ。 次回のお楽しみということにしておいて、今回は見逃してくれないか。ちなみに意味は「次に画面に表示する文字列を設定する」なんだけどね。

B: 僕もつかれましたんで、もう今日はこれでいいです。

A: よし。じゃあ実行してみるぞ。

../_images/01-2-02.png

A: スペースを押していくと…

../_images/01-2-03.png

A: 三文めが終わると、再生してくださいというメッセージが出て10秒待つ。10秒経過すれば自動的に終了する。

B: はあ、やっと終わりましたね。

A: 次回は 関数クラス を解説するぞ。また長くなると思われるので覚悟しておくように!

B: うへー。

補足

文字列の前のuを取ったらどうなるのか?という話は本文では飛ばしてしまいましたが、uがないと文字列がUnicodeで書かれているということがわからないため、文字化けを起こしてしまいます。 具体的には以下のような画面になります。

../_images/01-2-04.png