コードスタイル

PythonプログラマーにPythonについて最も好きなものを尋ねると、彼らはしばしば高い可読性を挙げるでしょう。 確かに、コードは書かれたよりもずっと頻繁に読み込まれるという認識された事実に従うことで、高いレベルの可読性がPython言語の設計の中心にあります。

Pythonコードの可読性が高い理由の1つは、コードスタイルガイドラインとPythonのイディオムの比較的完全なセットです。

ベテランのPython開発者(Pythonista)が、コードの部分について “Pythonic” ではないと呼ぶとき、通常は、これらのコード行が共通のガイドラインに従っておらず、その意図を最良(最も読み取りやすい)と考えられる方法で表現できていないことを意味します。

いくつかの境界ケースでは、Pythonコードで意図を表現するための最良の方法については合意されていませんが、これらのケースはまれです。

一般的な概念

明示的なコード

Pythonではあらゆる種類の黒魔術が可能ですが、最も明快で簡単な方法が好まれます。

悪い

def make_complex(*args):
    x, y = args
    return dict(**locals())

良い

def make_complex(x, y):
    return {'x': x, 'y': y}

上記の良いコードでは、xとyは呼び出し元から明示的に受け取られ、明示的な辞書が返されます。 この関数を使用している開発者は、最初と最後の行を読むことによって何をすべきかを正確に知っていますが、悪い例ではそうではありません。

行ごとに1つのステートメント

リスト内包表記のようないくつかの複合文は、その簡潔さと表現力のために許可され、評価されますが、同じコード行に2つの分離した文を持つことは悪い習慣です。

悪い

print 'one'; print 'two'

if x == 1: print 'one'

if <complex comparison> and <other complex comparison>:
    # do something

良い

print 'one'
print 'two'

if x == 1:
    print 'one'

cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2:
    # do something

関数の引数

引数は4つの異なる方法で関数に渡すことができます。

  1. 位置引数 は必須であり、デフォルト値はありません。それらは引数の最も単純な形式であり、関数の意味の一部であり、順序は自然であるいくつかの関数引数に対して使用できます。例えば、 send(message, recipient)point(x, y) では、関数のユーザは2つの引数を必要とします。

この2つのケースでは、関数を呼び出すときに引数名を使用することができます。そうすることで、引数の順序を切り替えることができます。例えば、send(recipient='World', message='Hello')point(y=2, x=1) を呼び出す引数の順序を send('Hello', 'World')Point(1,2) に切り替えることが可能ですが、これは可読性を低下させ、不必要で冗長です。

  1. キーワード引数 は必須ではなく、デフォルト値を持ちます。これらは、関数に送信されるオプションのパラメータによく使用されます。関数に2つまたは3つ以上の位置パラメータがある場合、そのシグネチャは覚えにくいため、キーワード引数をデフォルト値で使用すると便利です。例えば、より完全な send 関数は send(message, to, cc=None, bcc=None) として定義できます。ここで、 ccbcc はオプションで、別の値が渡されないときは None と評価されます。

キーワード引数を持つ関数をPythonで複数の方法で呼び出すことができます。たとえば、 send('Hello', 'World', 'Cthulhu', 'God') のように、引数に明示的に名前を付けずにカーボンコピーを送る。 send('Hello again', 'World', bcc='God', cc='Cthulhu') のように、別の順序で引数を指定することもできます。 send('Hello', 'World', cc='Cthulhu', bcc='God') 関数の定義に最も近い構文に従わないという強い理由がなくても。

副作用として、 YAGNI 原則の後で、オプションの引数(およびその関数内のロジック)を削除することは難しい場合があります。必要に応じて新しいオプションの引数とそのロジックを追加するよりも、まるで使用されていて一見無意味です。

  1. 任意の引数リスト は、引数を関数に渡す3番目の方法です。関数の意図が、拡張可能な数の位置引数を持つシグネチャによってうまく表現されている場合は、 * args 構造体で定義できます。関数本体では、 args は残りのすべての位置引数のタプルになります。たとえば、 send('Hello', 'God', 'Mom', 'Cthulhu')send(message, *args) のように、関数本体 args('God', 'Mom', 'Cthulhu') に等しくなります。

しかしながら、この構築物にはいくつかの欠点があり、慎重に使用すべきです。ある関数が同じ性質の引数のリストを受け取った場合、それを1つの引数の関数として定義することがより明確であり、その引数はリストまたは任意のシーケンスです。ここで send に複数の受信者がある場合、send('Hello', ['God', 'Mom', 'Cthulhu']) で明示的に send(message, recipients)。この方法では、関数のユーザーは受信者リストをあらかじめリストとして操作し、イテレーターを含む他のシーケンスとして解凍できないシーケンスを渡す可能性を開きます。

  1. 任意のキーワード引数辞書 は、関数に引数を渡す最後の方法です。 関数が未定義の一連の名前付き引数を必要とする場合は、 ** kwargs 構造体を使用することができます。 関数本体では、 kwargs は、関数シグネチャ内の他のキーワード引数によってキャッチされていない、渡されたすべての名前付き引数の辞書になります。

同様の理由から、任意の引数リスト の場合と同じ注意が必要です。これらの強力な手法は、実証された必要性がある場合に使用されるものであり、よりシンプルで明確な構成が関数の意図を十分に表現することが可能です。

どの引数が定位置引数であり、かつオプションのキーワード引数であるかを決定し、任意の引数渡しの高度な技術を使用するかどうかを決定するのは、関数を記述するプログラマの責任です。上記のアドバイスが賢明に守られれば、Pythonの関数を書くことが楽しくなるでしょう:

  • 読みやすい(名前と引数は説明が不要)
  • 簡単に変更することができます(新しいキーワード引数を追加することでコードの他の部分が破られることはありません)

魔法の杖を避ける

ハッカー向けの強力なツールであるPythonには、非常に豊富なフックやツールが付属しており、あらゆる種類のトリッキーなトリックを行うことができます。 例えば、以下について行うことができます。

  • オブジェクトの作成およびインスタンス化の方法を変更する
  • Pythonインタープリタがどのようにモジュールをインポートするかを変更する
  • CのルーチンをPythonに埋め込むことも可能です(必要に応じてお勧めします)

しかし、これらのオプションには多くの欠点があります。目標を達成するためには、最も簡単な方法を使用する方が常に優れています。 主な欠点は、これらのコンストラクトを使用すると可読性が大幅に低下することです。 pylintやpyflakesなどの多くのコード解析ツールは、この「魔法の」コードを解析できません。

私たちは、Pythonの開発者は、これらの無限の可能性について知っておくべきだと考えています。なぜなら、途方もなく問題が起こらないという自信があるからです。 しかし、どのように、特に使用 しない かを知ることは非常に重要です。

カンフーのマスターのように、Pythonistaは単一の指で殺す方法を知っています。 実際にそれをすることは決してありません。

私たちはすべて責任あるユーザーです

上で見たように、Pythonは多くのトリックを許し、そのうちのいくつかは潜在的に危険です。良い例は、どんなクライアントコードでも、オブジェクトのプロパティとメソッドをオーバーライドすることができるということです。Pythonでは “private”キーワードはありません。このような哲学は、誤用を防ぐための多くの仕組みを提供するJavaのような高度に防御的な言語とは異なり、「私たちはすべての責任あるユーザーです」と表現されています。

これは、例えばプロパティがプライベートであるとはみなされず、Pythonでは適切なカプセル化ができないことを意味しません。 Pythonコミュニティは、開発者がコードと他のコードの間に構築したコンクリートの壁に頼るのではなく、これらの要素に直接アクセスすべきではないことを示す一連の規則に頼っています。

プライベートプロパティと実装の詳細の主な慣例は、すべての “内部”にアンダースコアを付けることです。 クライアントコードがこのルールを破ってこれらのマークされた要素にアクセスする場合、コードが変更された場合に遭遇する不正行為や問題は、クライアントコードの責任です。

このコンベンションを惜しみなく使用することをお勧めします。クライアントコードで使用されないメソッドやプロパティには、アンダースコアを前に付ける必要があります。 これにより、任務の分離と既存のコードの変更が容易になります。 プライベートプロパティを公開することは常に可能ですが、パブリックプロパティをプライベートにすることは、非常に難しい操作になる可能性があります。

戻り値

関数が複雑になると、関数本体に複数のreturn文を使用することは珍しくありません。しかし、明確な意図と持続可能な可読性レベルを維持するためには、身体の多くの出力点から意味のある値を返すことを避けることが望ましいです。

関数内で値を返す主なケースが2つあります。関数の結果が正常に処理されたときの結果と、誤った入力パラメータを示すエラーケース、または関数が計算を完了できないその他の理由またはタスクです。

2番目のケースの例外を発生させたくない場合は、関数が正しく実行できなかったことを示すNoneやFalseなどの値を返す必要があります。 この場合、間違ったコンテキストが検出されたときに早く戻ってください。 関数の構造をフラット化するのに役立ちます。return-of-errorステートメントの後のすべてのコードは、関数の主な結果をさらに計算するために条件が満たされたとみなすことができます。 多くの場合、そのようなreturn文が必要です。

しかし、ある関数が通常のコースに対して複数のメイン出口点を持つ場合、返された結果をデバッグするのが難しくなるため、単一の出口点を保つことが望ましい場合があります。 これはまた、いくつかのコードパスを抽出するのにも役立ちます。また、複数の出口ポイントがそのようなリファクタリングが必要であることを示す可能性があります。

def complex_function(a, b, c):
    if not a:
        return None  # Raising an exception might be better
    if not b:
        return None  # Raising an exception might be better
    # Some complex code trying to compute x from a, b and c
    # Resist temptation to return x if succeeded
    if not x:
        # Some Plan-B computation of x
    return x  # One single exit point for the returned value x will help
              # when maintaining the code.

イディオム

簡単に言えば、プログラミングのイディオムは、コードを書く 方法 です。プログラミングイディオムの概念については、 c2Stack Overflow です。

慣用的なPythonコードは Pythonic と呼ばれることが多い。

通常は、それを実行するための1つの方法、好ましくは1つの方法しかありません。 慣用のPythonコードを書く 方法は、Pythonの初心者には明らかではありません。 ですから、良い熟語を意識的に獲得しなければなりません。

いくつかの一般的なPythonのイディオムが続きます:

解凍

リストやタプルの長さを知っている場合、その要素に名前をつけることができます。 たとえば、 enumerate() はlistの各項目に対して2つの要素のタプルを提供します:

for index, item in enumerate(some_list):
    # do something with index and item

変数をスワップするときにもこれを使うことができます:

a, b = b, a

ネストされたアンパックも機能します:

a, (b, c) = 1, (2, 3)

Python 3では、拡張アンパックの新しいメソッドが次のように導入されました PEP 3132:

a, *rest = [1, 2, 3]
# a = 1, rest = [2, 3]
a, *middle, c = [1, 2, 3, 4]
# a = 1, middle = [2, 3], c = 4

無視された変数を作成する

何かを割り当てる必要がある場合(例えば、unpacking-ref)、その変数は必要ないでしょう。__ を使ってください:

filename = 'foobar.txt'
basename, __, ext = filename.rpartition('.')

注釈

多くのPythonスタイルガイドでは、ここで推奨される二重アンダースコア “__” ではなく、使い捨て変数に単一のアンダースコア “_“を使用することを推奨しています。 問題は、 “_” は gettext() 関数のエイリアスとしてよく使われ、最後の操作の値を保持するために対話型プロンプトでも使われます。 代わりに二重のアンダースコアを使用することは、明らかであり、ほぼ同じくらい便利で、これらの他のユースケースのいずれかを誤って妨害するリスクを排除します。

同じものの長さNのリストを作成する

Pythonのリスト * 演算子を使う:

four_nones = [None] * 4

リストの長さNのリストを作成する

リストは変更可能であるため、 * 演算子(上記のように)は same リストに対するN個の参照のリストを作成します。 代わりに、リストの理解を使用します。

four_lists = [[] for __ in xrange(4)]

Note: Python 3では xrange() の代わりに range() を使用してください

リストから文字列を作成する

文字列を作成する一般的な方法は、空の文字列に str.join() を使用することです。

letters = ['s', 'p', 'a', 'm']
word = ''.join(letters)

変数 word の値を ‘spam’ に設定します。 このイディオムは、リストやタプルに適用できます。

コレクション内のアイテムを検索する

時々、私たちは物事のコレクションを検索する必要があります。リストとセットの2つのオプションを見てみましょう。

例えば、次のコードを実行します:

s = set(['s', 'p', 'a', 'm'])
l = ['s', 'p', 'a', 'm']

def lookup_set(s):
    return 's' in s

def lookup_list(l):
    return 's' in l
  • lookup_set はPythonのセットがハッシュテーブルであるという事実を利用しているので、両方の関数が同じに見えますが、2つのルックアップのパフォーマンスは大きく異なります。項目がリストにあるかどうかを判断するには、Pythonは一致する項目が見つかるまで各項目を調べなければなりません。これは時間がかかります。長いリストの場合は特にそうです。一方、あるセットでは、アイテムのハッシュは、セット内のどこで一致するアイテムを探すかをPythonに指示します。その結果、セットが大きい場合であっても、迅速に検索を行うことができます。辞書での検索も同じように機能します。詳細は、この StackOverflow ページを参照してください。これらのデータ構造のそれぞれに共通するさまざまな操作の詳細については、 このページ を参照してください。これらのパフォーマンスの違いのため、リストの代わりにセットまたは辞書を使用することは、良い考えであると言えます。:

これらのパフォーマンスの違いにより、リストの代わりにセットまたは辞書を使用することがよくあります。

  • コレクションには多数のアイテムが含まれます
  • コレクション内のアイテムを繰り返し検索します
  • 重複アイテムはありません

小さなコレクション、または頻繁に検索しないコレクションの場合、ハッシュテーブルを設定するために必要な時間とメモリが、検索速度が向上した時間よりも長くなることがよくあります。

Pythonの禅

PEP 20 とも呼ばれ、Pythonの設計の基本原則です。

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

よいPythonスタイルのいくつかの例については、 Pythonユーザグループのこれらのスライド を参照してください。

PEP 8

PEP 8 はPythonの事実上のコードスタイルガイドです。 pep8.org には、高品質で読みやすいPEP 8のバージョンもあります。

これは強くお勧めします。 Pythonコミュニティ全体は、このドキュメント内に記載されているガイドラインを守るために最善を尽くしています。 プロジェクトの中には時々動揺するものもあれば、 その勧告を修正するものもあります

つまり、PythonコードをPEP 8に準拠させることは、一般的には良いアイデアであり、他の開発者と一緒にプロジェクトを作業する場合にコードをより一貫性のあるものにするのに役立ちます。 あなたのコードの適合性をチェックできるコマンドラインプログラム pep8 があります。 ターミナルで次のコマンドを実行してインストールします。

$ pip install pep8

次に、ファイルまたは一連のファイルに対して実行して、違反の報告を取得します。

$ pep8 optparse.py
optparse.py:69:11: E401 multiple imports on one line
optparse.py:77:1: E302 expected 2 blank lines, found 1
optparse.py:88:5: E301 expected 1 blank line, found 0
optparse.py:222:34: W602 deprecated form of raising exception
optparse.py:347:31: E211 whitespace before '('
optparse.py:357:17: E201 whitespace after '{'
optparse.py:472:29: E221 multiple spaces before operator
optparse.py:544:21: W601 .has_key() is deprecated, use 'in'

autopep8 プログラムを使って、PEP 8形式のコードを自動的に再フォーマットすることができます。 次のようにプログラムをインストールします。

$ pip install autopep8

これを使用して、次のようにファイルをインプレースでフォーマットします。

$ autopep8 --in-place optparse.py

--in-place フラグを除外すると、プログラムは変更されたコードをレビューのためにコンソールに直接出力します。 --aggressive フラグはより実質的な変更を行い、効果を高めるために複数回適用することができます。

コンベンション

あなたのコードを読みやすくするために従わなければならない規則がいくつかあります。

変数が定数に等しいかどうかをチェックする

明示的に値をTrue、None、または0と明示的に比較する必要はありません。if文に値を追加するだけです。 誤っていると思われるもののリストについては、 真理値テスト を参照してください。

悪い:

if attr == True:
    print 'True!'

if attr == None:
    print 'attr is None!'

良い:

# Just check the value
if attr:
    print 'attr is truthy!'

# or check for the opposite
if not attr:
    print 'attr is falsey!'

# or, since None is considered false, explicitly check for it
if attr is None:
    print 'attr is None!'

辞書要素へのアクセス

dict.has_key() メソッドを使わないでください。 その代わりに、 x in d 構文を使うか、デフォルト引数を dict.get() に渡します。

悪い:

d = {'hello': 'world'}
if d.has_key('hello'):
    print d['hello']    # prints 'world'
else:
    print 'default_value'

良い:

d = {'hello': 'world'}

print d.get('hello', 'default_value') # prints 'world'
print d.get('thingy', 'default_value') # prints 'default_value'

# Or:
if 'hello' in d:
    print d['hello']

リストを操作するための短い方法

List comprehensions は、リストを扱うための強力かつ簡潔な方法を提供します。 また、map()filter() 関数は、より簡潔で異なった構文を使ってリストに対して操作を実行できます。

悪い:

# Filter elements greater than 4
a = [3, 4, 5]
b = []
for i in a:
    if i > 4:
        b.append(i)

良い:

a = [3, 4, 5]
b = [i for i in a if i > 4]
# Or:
b = filter(lambda x: x > 4, a)

悪い

# Add three to all list members.
a = [3, 4, 5]
for i in range(len(a)):
    a[i] += 3

良い

a = [3, 4, 5]
a = [i + 3 for i in a]
# Or:
a = map(lambda i: i + 3, a)

使用 enumerate() リスト内のあなたの場所の数を保持します。

a = [3, 4, 5]
for i, item in enumerate(a):
    print i, item
# prints
# 0 3
# 1 4
# 2 5

enumerate() 関数はカウンタを手動で扱うよりも読み易いです。 さらに、イテレータの方が最適化されています。

ファイルからの読み取り

ファイルから読み込むには with open 構文を使用します。 これにより、自動的にファイルが閉じられます。

悪い:

f = open('file.txt')
a = f.read()
print a
f.close()

良い:

with open('file.txt') as f:
    for line in f:
        print line

with ステートメントは、 with ブロック内で例外が発生したとしても、ファイルを常に確実に閉じることができるので、より優れています。

行の継続

論理行のコードが許容限度より長い場合は、複数の物理行に分割する必要があります。 行の最後の文字がバックスラッシュの場合、Pythonインタプリタは連続する行を結合します。 これはいくつかの場合に役立ちますが、通常、その脆弱性のために回避する必要があります。バックスラッシュの後ろの行末に空白を追加すると、コードが壊れて予期しない結果になることがあります。

より良い解決策は、要素の周りに括弧を使用することです。 行末に閉じられていない括弧が残っていると、Pythonインタプリタは括弧が閉じられるまで次の行に結合します。 中括弧と中括弧も同じ動作をします。

悪い:

my_very_big_string = """For a long time I used to go to bed early. Sometimes, \
    when I had put out my candle, my eyes would close so quickly that I had not even \
    time to say “I’m going to sleep.”"""

from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \
    yet_another_nice_function

良い:

my_very_big_string = (
    "For a long time I used to go to bed early. Sometimes, "
    "when I had put out my candle, my eyes would close so quickly "
    "that I had not even time to say “I’m going to sleep.”"
)

from some.deep.module.inside.a.module import (
    a_nice_function, another_nice_function, yet_another_nice_function)

しかし、しばしば長い論理行を分割しなければならないことは、同時に多くのことをしようとしている兆候であり、読みやすさの妨げになりかねません。