printが関数になった理由
printが関数になった理由として、PEP-3105(Make print a function)には次の5つが挙げられています。
PEP-3105の理由1
2.xでは、printは関数ではなく文として定義された唯一のアプリケーションレベルの機能です。Pythonの世界では、構文は一般に最後の手段として使われるもので、これが必要になるのは、コンパイラの補助がないと何らかの処理を実行できない場合のみです。printは、この範疇には当てはまりません。
たしかにそのとおりですが、printが特に役立つ対話型セッションでは、かっこを2つ余分に付けるのは面倒です。
PEP-3105の理由2
アプリケーション開発の最中に、printの出力を何かもっと複雑な、例えばロギング呼び出しや、ほかのI/Oライブラリの呼び出しに置き換えることがよくあります。print()関数を使用している場合、これは単純な文字列の置換で済みますが、文の場合は、かっこを追加し、場合によっては>>stream形式の構文に変換する必要があります。
この理由には同意できません。これらのprint文を置き換えることが問題だとは思いません。私は光栄にも(あるいは不運にも)、同じような(もっとひどい)変更を巨大なコードベース全体にわたって行ったことが何度もあります。それぞれのケースを検討し、元の単純な動作と新しい手の込んだ動作のどちらが必要かを判断しなければなりません。print関数を何か別のものに置き換えるのは巧妙なやり方ですが、「Explicit is better than implicit(明示は暗黙に勝る)」という原則に反します。今は、コードの中にprintやprint>>があれば、どうなるのかが正確に分かります(sys.stdoutがいじられていないと仮定した場合)。しかし、printを簡単に置き換えることができると、どこにあるかわからないログファイルにすべてが送られてしまい、出力が表示されずにかなり困惑することもあるでしょう。
PEP-3105の理由3
printに特別な構文があると、進化の大きな妨げになります。例えば将来的に、print()関数と共存する新しいprintf()関数を設けることも考えられます。
憶測にすぎない将来の変更を理由に、言語のこのような基本機能を変更するのは極めて不適切なように思えます。また、Pythonの設計理念にも反しています。
PEP-3105の理由4
スペース以外の区切り文字が必要な場合、または区切り文字が不要な場合に、print文を別の呼び出しに簡単に変換する方法がありません。また、スペース以外の区切り文字を持つオブジェクトをうまい具合に出力するための簡単な方法もありません。
この理由にも同意できません。オブジェクトに特別な書式設定が必要な場合は、オブジェクトを文字列にして書式設定し、その文字列を出力すれば済みます。後で、Python 2.xとPython 3.0を使った例を示しますが、どちらも見た目はほぼ同じで、簡潔なコードになっています。
PEP-3105の理由5
print()が関数であれば、置き換えがはるかに容易になります。1つのモジュール内での置き換えならばdef print(*args):...を使用し、プログラム全体にわたる置き換えならば__builtin__.printを書き換えれば済みます。現在のままでも、write()メソッドを持つクラスを作成し、そのクラスをsys.stdoutに割り当てるという方法で同じことが実現できますが、明らかにかなり大きな概念的飛躍であり、printとは異なるレベルで機能します。
これは2番目の理由とよく似ており、むしろ変更を歓迎すべきでない理由と思われます。sys.stdoutを使ってprintをオーバーライドするのが煩雑であるという理由で、あまり広く行われていないのであれば、それは良いことです。もし、まれにprintのオーバーライドが必要な状況(しかも単純に別の関数で代用することもできない状況――例えばテストなど)が発生したとしても、元のprintをオーバーライドすることが可能です。
このような長々とした議論をここで示したのは、この1つの問題からでさえ、Python 3.0が投げかけた波紋の大きさを実感できるだろうと考えたからです。私には、総じてprint関数は大した利点を持たず、printの動作方法を変更したい人にだけ役立つもののように思えます。Python 2.xでも、独立した関数を使うなり、sys.stdoutを操作するなりすれば、十分に用が足りるのではないでしょうか。いずれにしても、対話型セッションの構文が以前より煩雑になったのは残念です(この点については、2to3ツールを利用すると移行がスムーズに進むでしょう)。
そういうわけで、私はprint関数が気に入りませんが、これはもう決まったことです。Pythonの世界の偉大なる人々によって、そのように定められました。print関数のシグネチャは次のとおりです。
def print(*args, sep=' ', end='\n', file=None)
*argsは出力される引数(複数可)、sepは*argsの区切り文字として出力される文字列、endは最後の引数の後ろに出力される文字です。sepとendは、それぞれデフォルトではスペースと\nになります。これはPython 2.xのprint文のデフォルトの動作そのままです。file引数を指定した場合は、指定したファイル風オブジェクト(write()メソッドを持っている必要がある)に出力が送られ、print>>の機能を提供します。
例えば、次のような足し算の式を表示したいとします。
2 + 4 + 7 = 13
Python 2.xで式を出力するには、次のように記述します。
>>> numbers = [2, 4, 7] >>> print ' + '.join([repr(x) for x in numbers]) + ' = %d\n' % sum(numbers) 2 + 4 + 7 = 13
コード部分1行目に「>>> numbers = [2, 4, 7]」を追記しました。
しかしPython 3.0では、次のようになります。
>>> numbers = [2, 4, 7] >>> print(*numbers, sep=' + ', end=' =%d\n' % sum(numbers)) 2 + 4 + 7 = 13
どちらのバージョンもやや煩雑に見えます。次のPython 3.0のコードでは、このロジックをprint_numbers()という名前の関数でラップしています。元のprint関数をoriginal_printという変数に代入します。新しいprint_numbers()関数では、original_print関数を利用して実際の出力を行います。足し算の式を2つ出力した後で、元のprint関数を復活させます。
original_print = print def print_numbers(*numbers): sep = ' + ' end = ' = %d\n' % sum(numbers) original_print(*numbers, sep=sep, end=end) # Make print_numbers the current print function print = print_numbers print(1, 2, 3) print(3, 7) # Restore the original print function print = original_print print(1, 2, 3)
1 + 2 + 3 = 6 3 + 7 = 10 1 2 3
print関数についてあれだけ不平を並べましたが、いいこともあります。関数になったおかげで、Pythonスタートアップファイルで短い名前(pなど)の変数に代入できます。これにより、対話型セッションで次のように短い名前を使えるようになるので、かっこを入力する分の手間を帳消しにできます。
>>> p = print
>>> p('Yeah, it works!')
Yeah, it works!
>>> p(5 + 9)
14
この方法を利用するなら、printよりもpprintモジュールのpprint関数を使う方が便利でしょう。pprint関数では、ネストされたPythonデータ構造を見やすいレイアウトで表示できます。次のコードでは、両者の違いが分かるように、printとpprintの両方の関数を使って、複数のリストを含む辞書を出力しています。
>>> r = list(range(10))
>>> d=dict(a=r, b=r, c=r)
>>> print(d)
{'a': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'c': [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'b': [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
>>> from pprint import pprint as pp
>>> pp(d)
{'a': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'b': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
'c': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
>>>
pprintでも、辞書の要素がキーでソートされています(Python 2.5で採用された仕様)。このコードを記述する場合は、Pythonスタートアップファイルに次の文を追加します。
from pprint import pprint as pp
このような省略表現構造は、日々の仕事で複雑なデータ構造を調べるときに役立ちます。
