共通の落とし穴

ほとんどの場合、Pythonは驚きを避けるきれいで一貫した言語を目指しています。 しかし、新入社員を混乱させる可能性のあるケースがいくつかあります。

これらのケースのいくつかは意図的ですが、潜在的に驚くことでしょう。 間違いなく、言語の欠点と見なすこともできます。 一般的には、一見すると奇妙に見える可能性のあるトリッキーな動作の集合ですが、驚きの根底にある原因を認識すると一般的には賢明です。

変更可能なデフォルト引数

おそらく新しいPythonプログラマが遭遇する最も驚くべき点は、関数定義におけるデフォルトの引数を変更できるPythonの扱いです。

あなたが書いたもの

def append_to(element, to=[]):
    to.append(element)
    return to

起こることが予想されること

my_list = append_to(12)
print my_list

my_other_list = append_to(42)
print my_other_list

2番目の引数が指定されていない場合、関数が呼び出されるたびに新しいリストが作成され、出力は次のようになります。

[12] [42]

何が起こるか

[12]
[12, 42]

関数が定義されているときに新しいリストが作成され、各リスト内で連続して同じリストが使用されます。

Pythonのデフォルト引数は、関数が定義されているときに評価されます。関数が呼び出されるたびに(例えば、Rubyのように)評価されるのではありません。 つまり、変更可能なデフォルトの引数を使用してそれを変更すると、その関数への今後のすべての呼び出しのためにそのオブジェクトを変更してしまいます。

代わりに何をすべきか

関数が呼び出されるたびに、デフォルトの引数を使用して引数が指定されていないことを伝える (None はしばしば良い選択です) 新しいオブジェクトを作成します。

def append_to(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

落とし穴が落とし穴でないとき

場合によっては、関数の呼び出し間で状態を維持するために、この動作を具体的に「悪用する」(読み込み:意図したとおりに使用する)ことができます。 これは、キャッシング機能を記述するときによく行われます。

遅延バインディングクロージャ

混乱のもう一つの一般的な原因は、Pythonがクロージャー(またはその周囲のグローバルスコープ)で変数をバインドする方法です。

あなたが書いたもの

def create_multipliers():
    return [lambda x : i * x for i in range(5)]

起こることが予想されること

for multiplier in create_multipliers():
    print multiplier(2)

引数を掛け合わせる独自のクローズドオーバー i 変数を持つ5つの関数を含むリストです:

0
2
4
6
8

何が起こるか

8
8
8
8
8

5つの関数が作成されます。 代わりにそれらのすべてが x を4倍します。

Pythonのクロージャは late binding です。 これは、内部関数が呼び出された時点でクロージャーで使用される変数の値が参照されることを意味します。

ここで、返される関数の any が呼び出されるたびに、呼び出し時に i の値が周囲のスコープで参照されます。 それまでにループが完了し、最終値が4のままの i が残されます。

この問題について特に厄介なのは、これが何かと関係していると思われる誤った情報です lambdas lambda 式で作成された関数は決して特別なものではなく、実際には普通の def を使うだけで同じ正確な振る舞いが現れます:

def create_multipliers():
    multipliers = []

    for i in range(5):
        def multiplier(x):
            return i * x
        multipliers.append(multiplier)

    return multipliers

代わりに何をすべきか

最も一般的な解決策は間違いなくハックのビットです。 関数へのデフォルト引数の評価に関するPythonの前述の動作 (default_args を参照) のために、デフォルト引数を使用してその引数にすぐにバインドするクロージャを作成することができます:

def create_multipliers():
    return [lambda x, i=i : i * x for i in range(5)]

代わりに、functools.partial関数を使用することもできます:

from functools import partial
from operator import mul

def create_multipliers():
    return [partial(mul, i) for i in range(5)]

落とし穴が落とし穴でないとき

場合によっては、クロージャーがこのように動作することを望みます。 最近のバインディングは、多くの状況で良好です。 ユニークな機能を作り出すためのループは、残念ながら、しゃっくりを引き起こす可能性があります。

どこでもバイトコード (.pyc) ファイル!

デフォルトでは、ファイルからPythonコードを実行すると、Pythonインタプリタは自動的にそのファイルのバイトコードバージョンをディスクに書き込みます。 module.pyc

これらの .pyc ファイルはあなたのソースコードリポジトリにチェックインされるべきではありません。

理論的には、パフォーマンス上の理由から、この動作は既定でオンになっています。 これらのバイトコードファイルがなければ、Pythonはファイルが読み込まれるたびにバイトコードを再生成します。

バイトコード (.pyc) ファイルの無効化

幸いにも、バイトコードを生成するプロセスは非常に高速であり、コードを開発する際に心配する必要はありません。

それらのファイルは迷惑なので、取り除いてみましょう!

$ export PYTHONDONTWRITEBYTECODE=1

$PYTHONDONTWRITEBYTECODE 環境変数を設定すると、Pythonはこれらのファイルをディスクに書き込まなくなり、開発環境はきれいになります。

あなたの ~/.profile にこの環境変数を設定することをお勧めします。

バイトコード (.pyc) ファイルの削除

すでに存在する場合、これらのファイルをすべて削除するための素晴らしいトリックです:

$ find . -type f -name “*.py[co]” -delete -or -type d -name “__pycache__” -delete

プロジェクトのルートディレクトリから実行すると、すべての .pyc ファイルが突然消えます。 はるかに良い。