*********************************
  Python 良い慣用句、悪い慣用句
*********************************

:Author: Moshe Zadka

This document is placed in the public domain.
(この文書はパブリックドメインです。)


.. topic:: 概要

   この文書はチュートリアルのおまけと考えてもらって結構です。
   Python をどう使うべきか、そして更に重要なこととして、どう
   使うべきで *ない* かを例示しています。


使うべきでない構文
==================

Python の落とし穴は他言語と比べればほとんどないようなものですが、
中には特殊な場面でしか役に立たなかったり、単に危険なだけの構文も
存在しています。


from モジュール import \*
-------------------------


関数定義の中で
^^^^^^^^^^^^^^

関数定義内での ``from モジュール import *`` は *不正* です。
多くの古い Python では不正としてチェックされないものの、
だからと言って有効になるわけではありません。
やり手の弁護士を雇って無罪になっても、潔白になれるわけではないのと同じですね。
ですから絶対にそういう使い方をしないでください。
不正にされないバージョンでも、コンパイラはどの名前がローカルで
どの名前がグローバルなのか判然としなくなるので、関数の実行が
遅くなってしまいます。Python 2.1 で、この構文は警告や、
場合によってはエラーも出すようになりました。


モジュールレベルで
^^^^^^^^^^^^^^^^^^

モジュールレベルで ``from モジュール import *`` を使うのは
有効ではありますが、大抵は、やめたほうが良いですよ。
理由のひとつとして、それをやると本来 Python が持っている
大事な特徴を失ってしまうということが挙げられます。
その特徴とは、トップレベルの名前がそれぞれどこで定義されているのか、
エディタの検索機能だけでわかるというものです。
それに、将来モジュールに関数やクラスが増えていくと、
ややこしいことになりかねません。

ニュースグループで出てくる最低の質問には、なぜこのコード::

   f = open("www")
   f.read()

が動かないのか、というものがあります。もちろんこれで動きますよ
(ただし "www" というファイルがあれば)。でもモジュールのどこかに
``from os import *`` があればダメです。 :mod:`os` モジュール
は、整数を返す :func:`open` 関数を持っているのです。これは
とても便利ではありますが、ビルトインを隠してしまうのは、
非常に不便な特徴のひとつと言えます。

モジュールがエクスポートする名前は確実にはわからないのですから、必要なものだけ
``from モジュール import 名前1, 名前2`` で取るか、モジュールから出さずに
``import モジュール;print モジュール.name`` としておいて必要に応じてアクセスする
ようにしましょう。


問題ない状況
^^^^^^^^^^^^

``from モジュール import *`` が問題とならない状況もあります:

* 対話的プロンプト。たとえば ``from math import *`` すれば
  Python がスーパー関数電卓に変身します。

* C のモジュールを Python のモジュールで拡張するとき。

* そのモジュールは ``from import *`` 可だ、と作者が言っているとき。


そのまんまな :keyword:`exec`, :func:`execfile` と仲間たち
---------------------------------------------------------

「そのまんま」という言葉は辞書を明示せずに使うという意味で、
そういう構文ではコードが *その時点の* 環境に対して評価されます。
これは ``from import *`` と同じ理由で危険です --- 使っている最中の変数を
土足で踏んで行って、コード全体をメチャクチャにしてしまう可能性があるからです。
これは、とにかくやめてください。

悪い見本::

   >>> for name in sys.argv[1:]:
   >>>     exec "%s=1" % name
   >>> def func(s, **kw):
   >>>     for var, val in kw.items():
   >>>         exec "s.%s=val" % var  # ダメ!
   >>> execfile("handler.py")
   >>> handle()

良い見本::

   >>> d = {}
   >>> for name in sys.argv[1:]:
   >>>     d[name] = 1
   >>> def func(s, **kw):
   >>>     for var, val in kw.items():
   >>>         setattr(s, var, val)
   >>> d={}
   >>> execfile("handle.py", d, d)
   >>> handle = d['handle']
   >>> handle()


from モジュール import 名前1, 名前2
-----------------------------------

今回のは、これまでの「ダメ」よりかなり弱い「ダメ」ですが、やはりそれなりの
理由がなければ、やめておいたほうが良いことに変わりありません。
これが大抵うまくないのは、いつの間にか
二つ別々の名前空間に住む一つのオブジェクトを持つことになるからです。
一方の名前空間でそのバインディングが変更されたとき、もう一方の
バインディングは変更されないので、食い違いができてしまいます。
これが起こるのは、たとえば、モジュールを読み直したり、
ランタイムで関数の定義を変更したときなどです。

悪い見本::

   # foo.py
   a = 1

   # bar.py
   from foo import a
   if something():
       a = 2 # 危険: foo.a != a

良い見本::

   # foo.py
   a = 1

   # bar.py
   import foo
   if something():
       foo.a = 2


except:
-------

Python には ``except:`` 節があり、これはあらゆる例外を捕捉します。
Python のエラーは *すべて* 例外を出しますから、こんなことをすれば
各種のプログラミングエラーがランタイムの問題のように見えてしまい、
デバグの邪魔になります。

以下のコードが好例です::

   try:
       foo = opne("file") # "open" を打ち間違えた
   except:
       sys.exit("could not open file!")

この 2 行目は :exc:`NameError` を引き起こし、続く except 節で捕捉されます。
プログラムがエラー終了しますが、実際の問題は ``"file"`` の読み出しと
関係ないことなど、これではまったくわかりません。

前述の例はこう書けばマシになります::

   try:
       foo = opne("file") # 実行すればすぐ "open" に直すことになる
   except IOError:
       sys.exit("could not open file")

``except:`` 節が役立つ状況もあります: たとえば、フレームワークで
コールバックを実行するとき、コールバックがフレームワーク自体の
邪魔をしないようにするのは良いことです。


例外
====

例外は Python の有用な機能です。何か期待している以外のことが
起これば例外を出す、という習慣を持つべきですが、それと同時に、
何か対処できるときにだけ捕捉する、ということも習慣にしてください。

以下は非常にありがちな悪い見本です::

   def get_status(file):
       if not os.path.exists(file):
           print "file not found"
           sys.exit(1)
       return open(file).readline()

ここで、 :func:`os.path.exists` を呼んでから :func:`open` を呼ぶまでの間に
ファイルが消された場合を考えてください。そうなれば最後の行は :exc:`IOError` を
投げるでしょう。同じことは、 *file* は存在しているけれど読み出し権限がなかった、
という場合にも起こります。これをテストする際、ふつうのマシンで、存在するファイル
と存在しないファイルに対してだけやったのではバグがないように見えてしまい、テスト
結果が大丈夫そうなのでコードはそのまま出荷されてしまうことになります。こうして、
対処されない :exc:`IOError` はユーザの所まで逃げのびて、
汚いトレースバックを見せることになるのです。

もっと良い方法はこちら::

   def get_status(file):
       try:
           return open(file).readline()
       except (IOError, OSError):
           print "file not found"
           sys.exit(1)

このバージョンでは、ファイルが開かれて一行目も読まれる (だから、あてにならない
NFS や SMB 接続でも動く) か、あるいはメッセージが表示されてアプリケーションが
強制終了するかの \*いずれか\* 一方しか起こりません。

とはいえ、このバージョンの :func:`get_status` でさえ、前提としている条件が
多過ぎます --- すぐ終わるスクリプトでだけ使って、長く動作させる、いわゆる
サーバでは使わない前提なのです。もちろん呼び出し側はこうすることもできます::

   try:
       status = get_status(log)
   except SystemExit:
       status = None

でも、もっと良い方法があります。コードで使う ``except`` 節を、
できるだけ少なくするのです --- 使うとすれば、
必ず成功するはずの呼び出し内か、main 関数での全捕捉ですね。

というわけで、たぶんもっと良い :func:`get_status` はこちら::

   def get_status(file):
       return open(file).readline()

呼び出した側は、望むなら例外を処理することもできますし (たとえばループで
複数ファイルに試行するときとか)、そのまま *自分の* 呼び出し親まで上げる
こともできます。

しかし、この最終バージョンにも深刻な問題があります --- CPython 実装の
細部に原因があるのですが、例外が起きたときにはその例外ハンドラが
終了するまでファイルが閉じられないのです; しかも、なお悪いことに、
他の実装 (たとえば Jython) では例外の有無に関わらず閉じられません。

この関数の一番良いバージョンでは :func:`open` をコンテクストマネジャ
として使って、関数が返るとすぐにファイルが閉じられるようにしています::

   def get_status(file):
       with open(file) as fp:
           return fp.readline()


使い捨てじゃなくて充電池を使う
==============================

どうも皆、Python ライブラリに最初からあるものを自分で書こうとして、
大抵うまくいっていないようです。そういう場当たりなモジュールには
貧弱なインタフェースしかないことを考えると、ふつうは Python に付いてくる
高機能な標準ライブラリとデータ型を使うほうが、自分でひねり出すより
格段に良いですよ。

便利なのにほとんど知られていないモジュールに :mod:`os.path` があります。
このモジュールには OS に合ったパス演算が備わっていて、大抵は
自分でどれだけ苦労して作ったものよりもずっと良いものです。

比べてください::

   # うげえ!
   return dir+"/"+file
   # 改良版
   return os.path.join(dir, file)

:mod:`os.path` にはさらに便利な関数が他にもあります: :func:`basename` や
:func:`dirname`, :func:`splitext` などです。

加えて、なぜか気付かれていない多くのビルトイン関数があります:
たとえば :func:`min` と :func:`max` を使えば、大小比較のできるシーケンスなら
何からでも最小値/最大値を見つけることができるのに、多くの人々が自家製の
:func:`max`/:func:`min` を書いています。また別の超便利な関数は
:func:`reduce` です。古典的な使い方はこういった感じです::

   import sys, operator
   nums = map(float, sys.argv[1:])
   print reduce(operator.add, nums)/len(nums)

このキュートな小っちゃいスクリプトが、コマンドラインで与えられた数値
すべての平均値を表示するのです。 :func:`reduce` がすべての数を
合計する部分で、残りは単なる準備や後処理に過ぎません。

ついでにメモ。 :func:`float` と :func:`int` と :func:`long` はすべて
文字列型の引数を受け付けますから、パース処理にピッタリです --- ただし
:exc:`ValueError` に対応できるよう準備は必要ですが。


バックスラッシュで文を続ける
============================

Python は改行を文の終わりとして扱いますので、そして文は一行
にうまく収まらないことがよくありますので、こうする人が多いです::

   if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \
      calculate_number(10, 20) != forbulate(500, 360):
         pass

これは危険だということに気づいたほうが良いですよ: はぐれスペースが ``\``
の後に来ればその行の意味が変わってしまいますが、
スペースはエディタで見えにくいことに定評があるのです。
今回の場合は構文エラーにはなりますが、もしこうなら::

   value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \
           + calculate_number(10, 20)*forbulate(500, 360)

微妙に違う意味になるだけで、エラーが出ません。

それで、ふつうは括弧に入れて暗黙のうちに行をつなげるほうが賢明です:

このバージョンで鉄壁です::

   value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]
           + calculate_number(10, 20)*forbulate(500, 360))

