Pythonを教えるためのいくつかの提案

私は最近PyCon Australia 2014におけるクイーンズランド大学のチューターのプレゼンテーションを見るだけではなく、クイーンズランド大学のSoftware Carpentryブートキャンプに参加する機会がありました(ティーチングアシスタントとして)。

彼らが出くわした課題の多くはプログラミングを教えるときの固有の複雑さによるものでしたが、いくらかは避けられることのように思えました。

整数の除算から浮動小数点の値を得る

デフォルトのPython 2では、整数の除算は答えの小数点以下を切り捨てた商を表示します。

$ python -c "print(3/4)"
0

浮動小数点の表示は、コマンドラインのフラグやfuture importによる、型の強制を必要とします。

$ python -c "print(float(3)/4)"
0.75
$ python -Qnew -c "print(3/4)"
0.75
$ python -c "from __future__ import division; print(3/4)"
0.75

Python 3はデフォルトでまさにこのように動作します。そのためこの問題を完全に避ける一つの方法はPython 2の代わりにPython 3を教えることです。

$ python3 -c "print(3/4)"
0.75

(Python 2と3のいずれも、必要なら//floor division演算子で除算の切捨てを明確に指示します)

値の表示に関するPython 2と3の一般的な構文

私はPython 2と3を平行しながら今まで8年以上使ってきました(Python 3.0は2008年にリリースされましたが、そのプロジェクトはそれより数年早く本格的に始まりました。そのときPython 2.5はまだ開発中でした)。

それらをたびたび交互に切り替えるために学んだ必須の習慣の一つは、両方のバージョンで同様に動作する一般的なprint構文の使用へと私自身を制限することです。つまり丸括弧で囲まれた単一の引数を渡します。

$ python -c 'print("Hello world!")'
Hello world!
$ python3 -c 'print("Hello world!")'
Hello world!

もし複数の引数を渡す必要があれば、私は暗黙の連結機能よりも文字列の書式指定を使うでしょう。

$ python -c 'print("{} {}{}".format("Hello", "world", "!"))'
Hello world!
$ python3 -c 'print("{} {}{}".format("Hello", "world", "!"))'
Hello world!

このようにするのではなく、私が参加したブートキャンプで使われたSoftware Carpentryの教材はprint構文のみのレガシーなPython 2を広範囲で使いました。
そのためたまたまPython 3を実行していた学生にとって、うまく動いてきたはずなのに、いずれのバージョンでも失敗するといった例を引き起こしました。値の出力の共通構文を選ぶことで、Pythonのバージョンに依存しないようになります.講義をする分にはこれで十分でしょう。

戻り値と出力値の区別

ブートキャンプそしてPycon Australiaのプレゼンの両方で言及されていた問題が、出力値と戻り値の違いを学生に教えることの難しさでした。問題となるのは、Pythonの対話型インタプリタで提供されるRead-Eval-Print-Loopの”Print”の部分です。

>>> def print_arg(x):
...     print(x)
...
>>> def return_arg(x):
...     return x
...
>>> print_arg(10)
10
>>> return_arg(10)
10

対話型プロンプトの出力には、明らかな違いは見られません。特にstrreprの結果である数値を表す型は、どちらも同じです。これらの数値が違っていたとしても、学生にはこの違いが明確ではないでしょう。

>>> print_arg("Hello world")
Hello world
>>> return_arg("Hello world")
'Hello world'

私自身、この問題に対する明確な回答を持っているわけではありませんが、試してみる価値がありそうなのは、sys.displayhookの置換方法を生徒に教えてみることです。具体的には、次の変更を実際にやって見せ、ユーザに表示するための出力値とさらなる処理のための戻り値とがどう違うのかを説明してみせることです。

>>> def new_displayhook(obj):
...     if obj is not None:
...         print("-> {!r}".format(obj))
...
>>> import sys
>>> sys.displayhook = new_displayhook
>>> print_arg(10)
10
>>> return_arg(10)
-> 10

出力値と戻り値の違いを理解することは、関数を効果的に使うのに必要不可欠な学習です。この方法で結果を表示させることで、これらの違いを少しでも明確にすることができるでしょう。

追記:IPython(とIPython Notebook)

上に挙げた例は標準的なCPythonの実行環境に特化したもので、CPythonはデフォルトで対話型インタプリタを装備しています。これに対し、IPython(IPython Notebookも含む)の対話型インタプリタにはなかなか興味深いいくつかの相違点があります。

1つには、戻り値と出力値が違う形で表示されるという点です。結果の前に出力の参照番号が表示されます。

In [1]: print 10
10

In [2]: 10
Out[2]: 10

そして、オプションとして”autocall”機能が装備されています。これは関数を呼び出す時にユーザが括弧を除外していた場合、自動的にIPythonが括弧を付加してくれるよう設定しておける機能です。

$ ipython3 --autocall=1 -c "print 10"
-> print(10)
10

これはIPythonのセッションを、第一級関数を持たない言語と同じように動作させることができる汎用の機能です(なかでも注目に値するのは、 IPythonのautocall機能はMATLABの”コマンド構文”の関数呼び出しの表記法に酷似しているという点です)。

これによる弊害と言えば、”autocall”を有効にしているIPythonユーザがPython 2のあまり知られていないprint命令文(例えばstream redirectionや、suppressing the trailing newlineなど)を全く使っていない場合、Python 3でprintが標準のビルトイン関数になったことに気づきもしないという点でしょう。