Python コーディング指針 2007年12月14日版

Written by Rob Knight for the Cogent project

なぜコーディング指針 (ガイドライン) を設けるのでしょう?

プロジェクトが大規模化するほどに一貫性の重要性は増します。プロジェクトは単独のタスクとして開始するのでしょうが、完成度が上がってくると、断片的な成果物を統合して共有ライブラリにしようという話になるものです。ユニットテストと (コーディング) スタイルの一貫性は、信頼できるコードを統合する際にはきわめて重要です。また、ネーミングとインターフェースに関する推測もより正確になります。

良いコードは手許に置く価値があります。本書で述べる標準に従って書かれたコードは教育目的に有用ですし、雇い主となるかもしれない人に対して面接の際に見せるのにも役立ちます。コードサンプルを見せるのを嫌がる人は多いのですが、良いコードを書いてテストしておけば、他人に先んずることができます。また、再利用可能なコンポーネントがあれば、要求事項の変更や新規の分析の実行がとても簡単になります。

どんな変数名をつければよいのでしょう?

他人が最もアタリをつけられそうな名前を選びましょう。わかりやすく長すぎないものにします: curr_record の方が ccurr あるいは current_genbank_record_from_database よりもよい。コーディング指針を設けるのは、誰もが同じようにアタリをつけることができるようにするためでもあります。数ヶ月たてば、アタリをつけているのはあなたかもしれません。

良い名前は見つけづらいものです。他人も利用しているインターフェースの一部である場合は別ですが、変数名を変更することを恐れることはありません。すべてについて適切な名前を見つけ出すには多少の時間をとってコードをいじる必要があるかもしれません: ユニットテストがあれば、それらを変更するのは容易なことです。 全文検索・置換があれば特に容易です。

個別のものには単数形を、コレクションには複数形を使いましょう。たとえば、self.Name であればひとつの文字列を格納することを想定しますが、self.Names であればリストや辞書のように走査できるものを格納することを想定します。決め方がトリッキーである場合もあります: self.Index は位置を格納する整数でしょうか、それとも参照容易なように名前でキー付けされた辞書でしょうか? どちらなのか自信が持てないならば、たぶんその名前を変更して問題を回避すべきです: self.Position あるいは self.LookUp を検討しましょう。

名前に型を含めるのはやめましょう。後で実装を変更するかもしれません。RecordDictRecordList でなく Records を使うなどします。ハンガリアン記法(型を名前の先頭に付ける方式)も使わないようにします。

できるだけ正確な名前をつけましょう。変数が入力ファイルの名前なら、inputfile ではなく (これらは予約語ですからいずれにせよ使えませんが)、infile_name とします。infile ともしません (ファイル名ではなくファイルオブジェクトに見えるからです)。

メソッドや関数からの戻り値を格納するには result を使いましょう。関数/メソッドが扱う入力データの型が一意に決まらない場合 (シーケンスや数値リストなど) は、もっとわかりやすい名前が適切である場合を除き、data を使います。

1文字の変数名は数学関数やスコープが限られているループ変数にのみ使用すべきです。スコープが限られているとは for k in keys: print k のような場合で、ここでは k は 1〜2 行しか存続しません。ループ変数 (イテレータ) は走査する対象となる変数に準じて命名します。たとえば: for k in keys, i in items あるいは for key in keys, item in items。ループ部分が長い場合や、同一スコープ内で有効な1文字変数がいくつもある場合は、変数名を変更しましょう。

短縮形の使用は限定しましょう。よく知られていて使用してもかまわない短縮形も少しはありますが、半年ぶりにコードを読んで sptxck2 って何だっけと解読したいとは思わないでしょう。species_taxon_check_2 と打ち込むことにもいくばくかの時間を費やす価値はありますが、それもまだぞっとすることです: check_1 は何でしたっけ? taxon_is_species_rank のように説明不要な書き方にしておいた方がはるかによい。この変数が 1〜2 回しか使われないならなおのことです。

使ってもよい短縮形。次の短縮形はよく知られており、大手を振って使ってかまいません(訳注:生物学用語が紛れ込んでいる:-):

完全な表現 短縮形
alignment aln
archaeal (古細菌の) arch
auxillary aux
bacterial (バクテリアの)
bact
citation cite
current curr
database db
dictionary dict
directory dir
end of file eof
eukaryotic (真核性の) euk
frequency freq
expected exp
index idx
input in
maximum max
minimum min
mitochondrial (ミトコンドリアの) mt
number num
observed obs
original orig
output out
phylogeny (系統発生論) phylo
previous prev
protein (蛋白質) prot
record rec
reference ref
sequence seq
standard deviation stdev
statistics stats
string str
structure struct
temporary temp
taxonomic tax
variance var

import modulefrom module import * でなく、 常に from module import Name, Name2, Name3... という形で書きます。この書き方はより効果的で、後のコードでのタイプ量も少なくなり、 名前の衝突の検出や実装の置き換えを大幅に容易にします。

命名習慣はどうなっているのでしょう?

Summary of Naming Conventions (命名習慣の要約版)
命名習慣
function action_with_underscores find_all
variable noun_with_underscores curr_index
constant NOUN_ALL_CAPS ALLOWED_RNA_PAIRS
class MixedCaseNoun RnaSequence
public property MixedCaseNoun IsPaired
private property _noun_with_leading_underscore _is_updated
public method mixedCaseExceptFirstWordVerb stripDegenerate
private method _verb_with_leading_underscore _check_if_paired
really private data __two_leading_underscores __delegator_object_ref
parameters that match properties SameAsProperty def __init__(data, Alphabet=None)
factory function MixedCase InverseDict
module lowercase_with_underscores unit_test
global variables gMixedCaseWithLeadingG no examples in evo - should be rare!

命名習慣に従うことは重要です。ある名前が何を指しているかを推測するのが とても容易になるからです。とりわけ、名前の定義スコープの推測や参照している先、値の変更の可否、参照先が callable であるかどうかを推測しやすいようにすべきです。次の各ルールに従うとこうした区別をつけやすくなります。

  • モジュール内部変数(関数/メソッドの引数を含む)には lowercase_with_underscores を用います。 例外: __init__ では、オブジェクトの属性を初期化するのに用いられるパラメーターはその属性そのものと厳密に(大文字/小文字の別を含めて)同じ綴りにすべきです。こうしておくと、**kwargs のような正しいフィールドを持つ辞書で簡単にデータの初期化を行えるようになります。
  • クラスパブリック属性には MixedCase を用います。ファクトリ関数は クラスのコンストラクタが増えたようなものなので やはり MixedCase を用います。
  • パブリックメソッド/関数には mixedCaseExceptFirstWord を用います。
  • プライベート関数/メソッド/属性には _lowercase_with_leading_underscore を用います。
  • サブクラスでオーバーライドされてはならないプライベート属性・関数には __lowercase_with_two_leading_underscores を用います。
  • 定数には CAPS_WITH_UNDERSCORES を用います。
  • グローバル変数には gMixedCase (つまり、mixedCase に 'g' を前置した形) を用います。グローバル変数はごく稀に注意深く使用すべきです。Singleton パターンなどでこっそり使うときでもです。
  • 複数の単語の連接でも読みやすければアンダースコアなしでかまいません。in_fileout_file よりも infileoutfile を使用します。in_file_name や out_file_name あるいは infilename や outfilename (これらはパッと読むには長過ぎます) よりも infile_name and outfile_name を使用します。

モジュール (ソースファイル) はどうしたらよいでしょう?

各ファイルの1行目は #!/usr/bin/env python とします。こうしておけば (例えば CGI コンテキストでの) インタプリタの暗黙的な起動ができます。

次の行は説明を書いた docstring とします。説明が長くなる場合は、1 行目に要点を手短かに書き、1 行空行をおいて残りの説明から分離します。

すべてのコード (import 文も含む) は docstring の後に書きます。さもないと docstring はインタープリターに認識されず、対話セッション内や自動化ツールでドキュメント生成する際にそれにアクセスする (すなわち obj.__doc__) こともできません。

ビルトインモジュールを最初に import し、次にサードパーティー製モジュールを置き、 path の変更や自分のモジュールを最後に置きます。 とりわけ、path への追加や独自モジュール名は頻繁に変更されるものです: そういうものは1箇所にまとめ、見つけやすくしましょう。

次に作成者情報を書きます。次の形式に従うようにします:

__author__ = "Rob Knight, Gavin Huttley, and Peter Maxwell"
__copyright__ = "Copyright 2007, The Cogent Project"
__credits__ = ["Rob Knight", "Peter Maxwell", "Gavin Huttley",
"Matthew Wakefield"]
__license__ = "GPL"
__version__ = "1.0.1"
__maintainer__ = "Rob Knight"
__email__ = "rob@spot.colorado.edu"
__status__ = "Production"

__status__ は通常 "Prototype", "Development", "Production" のいずれかにします。 __maintainer__ はバグを修正し import した際に改善を行う人とします。 __credits____author__ とは意味が違います。 __credits__ にはバグ修正報告をした人や示唆を行った人などで 実際にコードを書いたわけではない人を書きます。

モジュールの構成例:

#!/usr/bin/env python
"""Provides NumberList and FrequencyDistribution, classes for statistics.
NumberList holds a sequence of numbers, and defines several statistical
operations (mean, stdev, etc.) FrequencyDistribution holds a mapping from
items (not necessarily numbers) to counts, and defines operations such as
Shannon entropy and frequency normalization.
"""

from math import sqrt, log, e
from random import choice, random
from Utils import indices

__author__ = "Rob Knight, Gavin Huttley, and Peter Maxwell"
__copyright__ = "Copyright 2007, The Cogent Project"
__credits__ = ["Rob Knight", "Peter Maxwell", "Gavin Huttley",
"Matthew Wakefield"]
__license__ = "GPL"
__version__ = "1.0.1"
__maintainer__ = "Rob Knight"
__email__ = "rob@spot.colorado.edu"
__status__ = "Production"


class NumberList(list):
pass #much code deleted


class FrequencyDistribution(dict):
pass #much code deleted


if __name__ == '__main__': #code to execute if called from command-line
pass #do nothing - code deleted

#use this either for a simple example of how to use the module,
#or when the module can meaningfully be called as a script.

コメントはどう書いたらいいでしょう?

コードを変更したら常にコメントも更新しましょう。 不正確なコメントはコメントがないよりもはるかに有害です。積極的にミスリードを招くからです。

コメントにはコードを読めばわかること以上のことを書きましょう。コメントは慎重に検討しましょう: コードを書き直す (特に、変数名を修正してコメントをなくす) 方がよいことがわかるかもしれません。とりわけ、マジックナンバーやその他の定数を撒き散らすのは避けましょう。コードを通して説明しなければならないからです。変数名自体が見ればわかるものである方が遥かに良い。特に同一の定数を何度も利用する場合はなおのことです。また、定数をクラスデータやインスタンスデータにすることも検討しましょう Also, think about making constants into class or instance data, since it's all too common for 'constants' to need to change or to be needed in several methods.

Wrong: win_size -= 20 # decrement win_size by 20
OK: win_size -= 20 # leave space for the scroll bar
Right: self._scroll_bar_size = 20
       win_size -= self._scroll_bar_size

コードのブロック内にあるコメントは # で書き始めます。文字列にするのは避けましょう。 Python は本当のコメントは無視しますが、文字列については記憶域の割当を行わねばなりません (階層の深いループでは実行性能上の大問題となることがあります)。

メソッド/クラス/関数の先頭には二重引用符3個 (""") を使用した docstring を置きましょう。 docstring の冒頭には、そこだけ読めばわかるような1行の説明を書きます (多くの自動整形ツールや IDE は、これを利用します)。それ以上の説明がある場合は、2行目は必ず空行にします。その後に、より詳しい情報を書きます。長い説明やアルゴリズムに関する注釈、パラメーターに関する詳しい注釈、といったものです。使用例がある場合は、最後に書きます。パラメーターの説明では綴りや大文字/小文字の別などを正確に記載するよう注意しましょう。

たとえば:

def __init__(self, data, Name='', Alphabet=None):
"""Returns new Sequence object with specified data, Name, Alphabet.
data: The sequence data. Should be a sequence of characters.
Name: Arbitrary label for the sequence. Should be string-like.
Alphabet: Set of allowed characters. Should support 'for x in y'
syntax. None by default.
Note: if Alphabet is None, performs no validation.
"""

コードを変更したら常に docstring も更新しましょう。 古くなったコメントと同じで、古くなった docstring は時間の浪費につながります。

コードはどう整形したらよいでしょう?

インデントにはスペース 4 個を使います。タブは使いません (タブをスペース4個へ変換するようエディタを設定しましょう)。 タブのふるまいはプラットフォームが変われば予測できません。 このことが文法エラーの原因となります。この問題に引っかかった人がたくさんいます。

1 行の長さは 79 字を超えないようにしましょう。 エディターによっては長い行は扱いが面倒ですし、印刷して行がバラバラになれば混乱のもとです。コードの一部をメールで送るのも難しくなります (メールクライアントや受取人が「よかれと思って」自動的に改行を行ってしまえばなおのことです)。行の継続には \ (訳注: バックスラッシュ) を使用します。\ の後にスペースは置けません。

クラス・メソッド定義を目立たせるために空行を利用しましょう。 クラス定義の間は2行空けます。メソッド定義の間は1行空けます。

演算子の前後のスペースの使い方には一貫性を持たせましょう。 スペースの使い方に一貫性がないと、何と何がグルーピングされているのかが一目でわからなくなります。

        Good:  ((a+b)*(c+d))
OK: ((a + b) * (c + d))
Bad: ( (a+ b) *(c +d ))

カッコのすぐ内側やスライスの [] の内側にはスペースを置きません。 ここにスペースを置くと、何と何が関連しているのかがわかりづらくなります。

        Good: (a+b), d[k]
Bad: ( a+b ), d [k], d[ k]

ユニットテストはどう書いたらよいでしょう?

コードの各行すべてがテストされるようにしましょう。科学分野での成果にとって、バグはあなたが会いもしないユーザーの不幸を意味するにはとどまりません。それは発表の撤回とキャリアの終わりを意味するのです。自分のコードを完全にテストした後にその生成結果から結論を引き出すようにすることが、決定的に重要です。

テストは、あればいいなと思われるインターフェースを発明する機会です。 Write the test for a method before you write the method: often, this helps you figure out what you would want to call it and what parameters it should take. Think of the tests as a story about what you wish the interface looked like. It's OK to write the tests a few methods at a time, and to change them as your ideas about the interface change. However, you shouldn't change them once you've told other people what the interface is.

プロトタイプをプロダクション(製品版)コードとして扱ってはなりません。 It's fine to write prototype code without tests to try things out, but when you've figured out the algorithm and interfaces you must rewrite it with tests to consider it finished. Often, this helps you decide what interfaces and functionality you actually need and what you can get rid of.

一度に書く量は少なくしましょう。 For production code, write a couple of tests, then a couple of methods, then a couple more tests, then a couple more methods, then maybe change some of the names or generalize some of the functionality. If you have a huge amount of code where 'all you have to do is write the tests', you're probably closer to 30% done than 90%. Testing vastly reduces the time spent debugging, since whatever went wrong has to be in the code you wrote since the last test suite.

なんであれ 変更を加えたら全テストを実行しましょう。 Even if a change seems trivial, it will only take a couple of seconds to run the tests and then you'll be sure. This can eliminate long and frustrating debugging sessions where the change turned out to have been made long ago, but didn't seem significant at the time.

各モジュールのファイル一つ一つについてユニットテストフレームワークを用いたテストを行いましょう。 テストファイルには test_module_name.py という名前を付けます。コードからテストを切り離しておけば、コードが動作しなかった際にテストの方を修正しようとする誘惑を小さくできます。また、完全に新しい実装が古い実装と同一のインターフェースを備えていることの確認が簡単になります。

Use evo.unit_test if you are doing anything with floating point numbers or permutations (use assertFloatEqual). Do not try to compare floating point numbers using assertEqual if you value your sanity. assertFloatEqualAbs and assertFloatEqualRel can specifically test for absolute and relative differences if the default behavior is not giving you what you want. Similarly, assertEqualItems, assertSameItems, etc. can be useful when testing permutations.

Test the interface of each class in your code by defining at least one TestCase with the name ClassNameTests. This should contain tests for everything in the public interface.

If the class is complicated, you may want to define additional tests with names ClassNameTests_test_type. These might subclass ClassNameTests in order to share setUp methods, etc.

Tests of private methods should be in a separate TestCase called ClassNameTests_private. Private methods may change if you change the implementation. It is not required that test cases for private methods pass when you change things (that's why they're private, after all), though it is often useful to have these tests for debugging.

クラス内の すべての メソッドをテストしましょう。 You should assume that any method you haven't tested has bugs. The convention for naming tests is test_method_name. Any leading and trailing underscores on the method name can be ignored for the purposes of the test; however, all tests must start with the literal substring test for unittest to find them. If the method is particularly complex, or has several discretely different cases you need to check, use test_method_name_suffix, e.g. test_init_empty, test_init_single, test_init_wrong_type, etc. for testing __init__.

すべてのテストメソッドに良い docstring を書きましょう。 When you run the test with the -v command-line switch for verbose output, the docstring for each test will be printed along with ...OK or ...FAILED on a single line. It is thus important that your docstring is short and descriptive, and makes sense in this context.

Good docstrings:
NumberList.var should raise ValueError on empty or 1-item list
NumberList.var should match values from R if list has >2 items
NumberList.__init__ should raise error on values that fail float()
FrequencyDistribution.var should match corresponding NumberList var

Bad docstrings:
var should calculate variance           #lacks class name, not descriptive
Check initialization of a NumberList    #doesn't say what's expected
Tests of the NumberList initialization. #ditto

モジュールレベル関数は独自のテストケース (modulenameTests という名前にします) でテストしましょう。 これらの関数が単純なものでも、ふれこみどおり機能することをチェックすることは重要です。

手作業で確認できる小規模な事例をテストする方が、 電卓が必要な大規模事例をひとつチェックするよりもはるかに重要です。数値計算にスプレッドシートを信頼してはなりません。代わりに R を使いましょう!

すべての境界事例をテストしたか確認しましょう。 入力が None や '' 、0、負値の場合はどうなりますか? 処理が分かれるような値についてどうなっていますか? 誤入力は妥当な例外を発生させていますか? 予期している入力値のタイプのサブクラスやスーパークラスを受け付けていますか? 巨大な値を入力したらどうなりますか?

To test permutations, check that the original and shuffled version are different, but that the sorted original and sorted shuffled version are the same. Make sure that you get different permutations on repeated runs and when starting from different points.

ランダム選択のテストには、大標本 (たとえば 1,000 件あるいは百万件) で各選択肢の発生頻度がどの程度になるか二項分布や正規推定により算出しておきましょう。 テストを何度も行い、たとえば平均値 (の標本誤差) が正規分布の3倍の範囲に収まることを 確認します (訳注:標本平均について t 検定を行うことを意図している)。

ユニットテストモジュールの構成例:

#!/usr/bin/env python
"""Tests NumberList and FrequencyDistribution, classes for statistics.
"""
from unittest import TestCase, main #use my unittestfp instead for floating point
from statistics import NumberList, FrequencyDistribution
__author__ = "Rob Knight, Gavin Huttley, and Peter Maxwell"
__copyright__ = "Copyright 2007, The Cogent Project"
__credits__ = ["Rob Knight", "Peter Maxwell", "Gavin Huttley",
"Matthew Wakefield"]
__license__ = "GPL"
__version__ = "1.0.1"
__maintainer__ = "Rob Knight"
__email__ = "rob@spot.colorado.edu"
__status__ = "Production"


class NumberListTests(TestCase): #must remember to subclass TestCase
"""Tests of the NumberList class."""
def setUp(self):
"""Define a few standard NumberLists."""
self.Null = NumberList() #test empty init
self.Empty = NumberList([]) #test init with empty sequence
self.Single = NumberList([5]) #single item
self.Zero = NumberList([0]) #single, False item
self.Three = NumberList([1,2,3]) #multiple items
self.ZeroMean = NumberList([1,-1]) #items nonzero, mean zero
self.ZeroVar = NumberList([1,1,1]) #items nonzero, mean nonzero, variance zero
#etc. These objects shared by all tests, and created new each time a method
#starting with the string 'test' is called (i.e. the same object does not
#persist between tests: rather, you get separate copies).

def test_mean_empty(self):
"""NumberList.mean() should raise ValueError on empty object"""
for empty in (self.Null, self.Empty):
self.assertRaises(ValueError, empty.mean)

def test_mean_single(self):
"""NumberList.mean() should return item if only 1 item in list"""
for single in (self.Single, self.Zero):
self.assertEqual(single.mean(), single[0])

#other tests of mean
def test_var_failures(self):
"""NumberList.var() should raise ZeroDivisionError if <2 items"""
for small in (self.Null, self.Empty, self.Single, self.Zero):
self.assertRaises(ZeroDivisionError, small.var)

#other tests of var
#tests of other methods


class FrequencyDistributionTests(TestCase):
pass #much code deleted

#tests of other classes

if __name__ == '__main__': #run tests if called from command-line
main()
ċ
pep8.py
(43k)
Hideki HAYASI,
2010/05/24 4:59
Comments