SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

Python 3.0開発者ガイド:コア言語の変更点

Python 3.0の主要な言語機能

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(明示は暗黙に勝る)」という原則に反します。今は、コードの中にprintprint>>があれば、どうなるのかが正確に分かります(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は最後の引数の後ろに出力される文字です。sependは、それぞれデフォルトではスペースと\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データ構造を見やすいレイアウトで表示できます。次のコードでは、両者の違いが分かるように、printpprintの両方の関数を使って、複数のリストを含む辞書を出力しています。

>>> 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

 このような省略表現構造は、日々の仕事で複雑なデータ構造を調べるときに役立ちます。

次のページ
新しい型システムの探究

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Gigi Sayfan(Gigi Sayfan)

 主に大規模分散システムを対象とした、C/C++/C#/Python/Javaによるクロスプラットフォームのオブジェクト指向プログラミングに精通。現在は、Numenta社で脳の働きを模した人工知能システムの開発に取り組んでいる。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/4051 2009/06/25 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー