渡邉一郎日記

趣味かつ独学の成人男性がPython の様々な内容を記事にして発信しております

【Python入門🔰】コーディングのすすめ

Pythonには、PEPs(Python Enhancement Proposals)というドキュメントが存在します。

PEPsとは、Pythonに関するこれまでの歴史や機能の技術仕様、推奨される規則などが文書化されたものです。

その中に、PEP 8 - Style Guide for Python Code というコーディングスタイルに関するガイドが用意されています。

この記事では、Pythonのコーディングスタイルに関して「PEP 8 からより重要な部分を抜粋したもの」と独学趣味野郎の「私からの薦め」を紹介します。

PEP8 からのすすめ

ガイドラインが用意されている理由として、
「コードの可読性を向上させること」+「様々なPythonコードに一貫性を持たせること」
が挙げられます。

これは、コード自体が”書かれる”よりも”読まれる”ことの方が多いという考えから、読みやすさが重要であることを謳っています。

ここでは、PEP 8 に書かれているガイドラインの中から、初心者の方でもすぐに取り入れられることを抜粋して紹介します。

⚠ チームやプロジェクト内にコーディングの規範やルールがある場合はそちらを優先してください。PEP 8 でも「スタイルガイドによる一貫性 < プロジェクト内での一貫性」であると書かれています。

インデントは空白4つ、タブはNG!

for number in range(0, 11):
    pass
# ↑半角スペース4つ + pass

PEP 8 では、インデントにはタブではなく4つのスペースを使うことが推奨されています。

一般的に、
インデントが小さい:ネストを深くすることができる
インデントが大きい:段落がわかりやすく読みやすい

4つのスペースは、これらの中間に当たるため推奨されているようです。

ちなみに、私はインデントを2つのスペースにしています。理由は、モニターの半面にエディターを配置した時に4つだと少し大きいかなと感じたためです。

特に何もなければ4つにしておきましょう。

1行の文字数は「79文字以内」

list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]

in_list = [number for number in list if len(number) < 5]

print(in_list)

"""80 words
01234567890123456789012345678901234567890123456789012345678901234567890123456789
"""

PEP 8 では、すべての行に対して文字数を79文字以内に制限することを推奨しています。

これは、複数のファイルを並べた場合や小さいディスプレイを使っているユーザーに対して、読みやすくするためです。

上記コードは、79文字が大体どれくらいかを示すために書いてみました。

リストやタプル、辞書といった配列を書こうとすると79文字を超過する可能性が高くなります。

長くなってしまった場合は、改行+インデントを使って読みやすい配置を見つけましょう。

「クラス内の関数」「関数内の大きなブロック」の区切りに空行を使う

# ランダム関数の一部
class Random(_random.Random):
    """Random number generator base class used by bound module functions.

    Used to instantiate instances of Random to get generators that don't
    share state.

    Class Random can also be subclassed if you want to use a different basic
    generator of your own devising: in that case, override the following
    methods:  random(), seed(), getstate(), and setstate().
    Optionally, implement a getrandbits() method so that randrange()
    can cover arbitrarily large ranges.

    """

    VERSION = 3     # used by getstate/setstate

    def __init__(self, x=None):
        """Initialize an instance.

        Optional argument x controls seeding, as for Random.seed().
        """

        self.seed(x)
        self.gauss_next = None

    def seed(self, a=None, version=2):
        """Initialize internal state from a seed.

        The only supported seed types are None, int, float,
        str, bytes, and bytearray.

        None or no argument seeds from current time or from an operating
        system specific randomness source if available.

        If *a* is an int, all bits are used.

        For version 2 (the default), all of the bits are used if *a* is a str,
        bytes, or bytearray.  For version 1 (provided for reproducing random
        sequences from older versions of Python), the algorithm for str and
        bytes generates a narrower range of seeds.

        """

        if version == 1 and isinstance(a, (str, bytes)):
            a = a.decode('latin-1') if isinstance(a, bytes) else a
            x = ord(a[0]) << 7 if a else 0
            for c in map(ord, a):
                x = ((1000003 * x) ^ c) & 0xFFFFFFFFFFFFFFFF
            x ^= len(a)
            a = -2 if x == -1 else x

        elif version == 2 and isinstance(a, (str, bytes, bytearray)):
            if isinstance(a, str):
                a = a.encode()
            a = int.from_bytes(a + _sha512(a).digest(), 'big')

        elif not isinstance(a, (type(None), int, float, str, bytes, bytearray)):
            _warn('Seeding based on hashing is deprecated\n'
                  'since Python 3.9 and will be removed in a subsequent '
                  'version. The only \n'
                  'supported seed types are: None, '
                  'int, float, str, bytes, and bytearray.',
                  DeprecationWarning, 2)

        super().seed(a)
        self.gauss_next = None

    def getstate(self):
        """Return internal state; can be passed to setstate() later."""
        return self.VERSION, super().getstate(), self.gauss_next

    def setstate(self, state):
        """Restore internal state from object returned by getstate()."""
        version = state[0]
        if version == 3:
            version, internalstate, self.gauss_next = state
            super().setstate(internalstate)
        elif version == 2:
            version, internalstate, self.gauss_next = state
            # In version 2, the state was saved as signed ints, which causes
            #   inconsistencies between 32/64-bit systems. The state is
            #   really unsigned 32-bit ints, so we convert negative ints from
            #   version 2 to positive longs for version 3.
            try:
                internalstate = tuple(x % (2 ** 32) for x in internalstate)
            except ValueError as e:
                raise TypeError from e
            super().setstate(internalstate)
        else:
            raise ValueError("state with version %s passed to "
                             "Random.setstate() of version %s" %
                             (version, self.VERSION))

PEP 8 では、クラス内・関数内の区切りに空行を使うことが推奨されています。

上記コードは、ライブラリ:random の一部を抜粋したものです。

クラス内の関数の間には空行が使われています。また、行数や処理内容が多い関数にも空行が使われています。

こうすることで、各関数や関数内の処理内容が読みやすくなり、理解もしやすくなります。

ちなみに、私も空行を使用していますが、空行の上下でインデントが使われている場合(関数内やfor 文内など)は、# を入れています。

エディターによっては空行にインデントが入らず、そのままコピーして実行しようとするとエラーが発生することがあります。その対策として、インデント+# を使っています。

コメントは行に独立で書く

# ブロックコメント
def function():
  a = 3 + 5        # インラインコメント
  b = 6 + 9
  return a + b

コメントには、「ブロックコメント」と「インラインコメント」の2つが存在します。

PEP 8 では、ブロックコメントの使用が推奨されています。一方で、インラインコメントの使用は推奨されていません。

ブロックコメントは、その下のコード(またはブロック)に対するコメントを書きます。

インラインコメントは、その行のコードに対するコメントを書きます。

実際に使ってみるとわかりやすいですが、インラインコメントが途中途中で出てくると気が散ってしまいます。

これにより「コードの読みやすさ」が損なわれてしまうため、インラインコメントの使用は推奨されていないということになります。

docstring を使う

def function():
  """Do nothing, but document it.

  It doesn't do anything.
  """
  pass

print(function.__doc__)

docstring(:ドキュメンテーション文字列)とは、関数・クラス・メソッドなどの機能に関する説明を記述した文字列になります。

最初の行には、対象物の目的を短く簡潔にまとめた文章を書きます。また、最初の文字は大文字で始め、ピリオドで終わらせなければなりません。

もし、docstring にさらなる記述が必要な場合は、2行目に空行を挿入して3行目からその他の内容を記述してください。

docstring を表示させるには、print(関数名.__doc__) を使用します。
関数名.__doc__ のみでも出力されますが、改行自体もコードで出力されるため print() を使用したほうがしっかり出力してくれます。

演算子の前後 や コンマ・コロンの後 には空白を入れる & 括弧のすぐ内側 には空白を入れない

# 演算子の前後
num_1 = 2 + 4

# 演算子の優先度
# 括弧のすぐ内側
num_2 = (10-6) * (9+3)
num_3 = 9*4 + 3*8

# コンマ・コロンの後
# 括弧のすぐ内側
dic_1 = {"ten": 10, "twenty": 20, "thirty": 30, "forty": 40}

PEP 8 には、コード内の空白に関するガイドも記述されています。
その中でも使用頻度の高いケースをいくつか紹介します。

演算子の前後には空白(スペース)を入れましょう。ただし、演算子には優先度があり、優先度の最も低い演算子の前後に空白を入れてください。

また、コンマ( , )とコロン( : )の後ろにも空白を入れましょう。
コンマはリストやタプルなどで使うことが多いかと思います。コロンは辞書やラムダ式などで使われるので、その際には実践してみてください。

最後に、()・[]・{} などの括弧類のすぐ内側には空白を入れないでください。
空白を入れてしまうと、長くなりがちな配列が更に長くなってしまい、読みやすさが損なわれてしまう可能性があります。

エンコーディングUTF-8 or ASCII が理想

PEP 8 では、PythonコードのエンコーディングUTF-8 または ASCII の使用が推奨されています。
エンコーディングとは、任意の規則に沿ってデータを符号化することです。

エンコードは圧縮・符号化などの意味を持ち、対義語はデコード(解凍・復号)となります。
このエンコードとデコードの方法が異なると、文字化けなどが起こり正常にファイルを開けないということがあります。

といっても、Python3で採用されている標準のエンコーディングUTF-8 なのでそこまで心配はいりません。
強いて言えば、不要なエンコーディング宣言はしない方が良いということでしょうか。

また、識別子(変数名や関数名)に非ASCII文字を使用することも控えたほうが良いでしょう。
つまり、英数字を使用していれば問題ないということです。

UTF-8 や ASCII については、佐々木真(ササキマコト)さんの記事を読んでみてください。読みやすくてわかりやすいです。↓↓↓

wa3.i-3-i.info

wa3.i-3-i.info

私からのすすめ

ここからは私からのすすめなので、興味があれば読んでみてください。

関数定義はソースコードの上部が良き!

def で関数定義をする際は、ソースコードの上部がおすすめです。

「ライブラリを使った一般的な実行文」と「関数定義」の読み込み速度を比較してみると、関数定義の方が速く読み込まれます。(関数定義は、関数が呼び出された時に命令が実行されるため。)

よって、ソースコードの構成としては
①ライブラリのインポート → ②最低限の変数定義 → ③関数定義 → ④実行文の記述
という順番がおすすめです。

ソースコードの作り始めはコメントを書きまくれ!

ここで挙げるコメントとは、ブロックコメントのことになります。

皆さんもコーディングをする前には、大まかなフローを考えると思います。

そのフローをコメントで書きまくりましょう!(笑)
細かすぎるくらいが丁度いいと思います。

『コメントが多すぎると「読みやすさ」に影響するのではないだろうか?』

確かに思いっきり影響してくると思います。ただ、コメントの精査は後からでも可能です。

ソースコード全体から不要な空白を削除する、となると大変ですが、
不要なコメントを削除する または 書き換える くらいなら現実的かと思います。

きちんと動くソースコードを作った後に、
『ここのコメントは余分だったな』『このコメントは長いな』
というところを修正すれば、きっと「読みやすい」ソースコードになるはずです。

コメントに英語を使い過ぎない方が良いかも?

基本的に、プログラミングでは英語が使われています。

ただ、英語が苦手で英単語の知識もないという同士は結構いるのではないでしょうか?

もちろん、やっていくうちに覚える英語もありますし、英文よりもコードの方が読めるような気はします。

しかしながら、結局あとから調べることになります。何回も。
できあがったソースコードは、動けばOK。故に、前に書いた英語の記憶は遥か彼方へ吹き飛びます。

特に最近プログラミングを始めた方々には、『最初から英語でやらなくちゃ!』と気張らずに『時間を掛けて慣れていこう』くらいの気持ちで取り組んでほしいなと勝手に思っております。

まとめ

この記事では、Pythonのコーディングスタイルに関して「PEP 8 からより重要な部分を抜粋したもの」と「独学・趣味の私からの薦め」を紹介しました。

初心者の方の中には、『形から入るタイプです!』という方もいるかと思います。
また、Pythonに慣れてきた人の中には、『もうちょっと綺麗にコーディングしたい』という方もいるでしょう。

そういった方々にはピッタリのテーマだったかな、と思います。

Pythonの特徴の1つとして、コードが読みやすく自由度の高いコーディングが挙げられます。
故に、コーディングにはその人の性格が現れます。

そのため、今回紹介したようなコーディングのスタイルガイドが用意されているのは、Pythonユーザーにとって非常に助かります。

是非お時間があるときは、PEPsを覗いてみてください。私も読みます。