エレガントなPythonコードの作成方法-第1部

19464 ワード

1.制御構造と関数
1.1 if文
1.1.1 True、FalseまたはNoneとの直接的な比較を避ける
任意のオブジェクトに対して,組み込みかユーザ定義か,それ自体に真偽の判断がある.条件が真か否かを判断する場合,主に条件文におけるオブジェクトの真偽性に依存する.真偽性の判断は非常にはっきりしている.以下の条件はすべて偽と判断します.
  • None
  • False
  • 数値タイプの0
  • 空のシーケンス
  • 空の辞書
  • オブジェクトのlenまたはnonzeroメソッドを呼び出すと、戻り値は0またはFalse
  • である.
    他のすべての状況は真と判断される(ほとんどの場合は暗黙的に真と判断される).最後にlenメソッドまたはnonzeroメソッドが値を返す判断条件をチェックすることで、カスタムクラスがオブジェクトの真偽をどのように決定するかを許可します.
    Pythonのif文のように,判定文に真偽を暗黙的に利用すべきである.例えば、変数fooが本物か否かを判断する文
    if foo == True:
    

    簡単に書ける
    if foo:
    

    以下に書く理由はたくさんあります.最も明らかなのは、コードが変化した場合、例えばfooがTrueまたはFalseからintになった場合でもif文は動作します.しかし,深層的には,真偽の判断は対象間のequalityとidentityに基づいている.==を使用して、2つのオブジェクトに同じ値(具体的には_eq属性によって定義される)が含まれているかどうかを判断する場合.isを使用して、2つのオブジェクトが同じオブジェクトであるかどうかを判断します(同じオブジェクトを適用します).
    注意:条件が成立する場合もありますが、比較対象の間の等化のように、これらは特殊な場合であり、依存しないでください.
    したがって,False,Nodeおよび[],{},()のような空のシーケンスと直接真偽を比較することは避ける.もしmy_Listリストタイプが空で、直接呼び出し
    if my_list:
    

    結果は偽物だ.
    時々、Noneと直接比較する必要がありますが、お勧めの方法ではありませんが.関数のいずれかのパラメータのデフォルト値がNoneの場合、実際のパラメータが空でない場合は、パラメータとNoneを比較する必要があります.
    def insert_value(value, position=None):
        """Inserts a value into my container, optionally at thespecified position"""
        if position is not None:
            ...
    

    書くなら
    if position:
    

    問題はどこですか.位置0で補間したい場合、positionが0の場合、偽と判断されるため、関数には付与操作はありません.注意noneと比較する場合、==ではなくisまたはis notを常に使用するべきである(PEP 8参照).
    1.1.1.1悪いスタイル
    def number_of_evil_robots_attacking():
        return 10
    def should_raise_shields():
        # "We only raise Shields when one or more giant robots attack,
        # so I can just return that value..."
        return number_of_evil_robots_attacking()
    if should_raise_shields() == True:
        raise_shields()
        print('Shields raised')
    else:
        print('Safe! No giant robots attacking')
    

    1.1.1.2 pythonのスタイル
    def number_of_evil_robots_attacking():
        return 10
    def should_raise_shields():
        # "We only raise Shields when one or more giant robots attack,
        # so I can just return that value..."
        return number_of_evil_robots_attacking()
    if should_raise_shields():
        raise_shields()
        print('Shields raised')
    else:
        print('Safe! No giant robots attacking')
    

    1.1.2複合条件文での変数名の重複を避ける
    変数がいくつかのカラム値に対応しているかどうかを検出する場合、変数を繰り返して数値比較する必要はありません.反復方式を使用すると、コードがより明確になり、可読性が向上します.
    1.1.2.1悪いスタイル
    is_generic_name = False
    name = 'Tom'
    if name == 'Tom' or name == 'Dick' or name == 'Harry':
        is_generic_name = True
    

    1.1.2.2 pythonのスタイル
    name = 'Tom'
    is_generic_name = name in ('Tom', 'Dick', 'Harry')
    

    1.1.3条件分岐コードとセミコロンを同じ行に置かない
    範囲をインデントで表すと(pythonはそうします)、コードが条件文の一部であるかどうかをより容易に知ることができます.if,elif,else文は単独で1行に書くべきで,コロンの後にコードを書くべきではない.
    1.1.3.1悪いスタイル
    name = 'Jeff'
    address = 'New York, NY'
    if name: print(name)
    print(address)
    

    1.1.3.2 pythonのスタイル
    name = 'Jeff'
    address = 'New York, NY'
    if name:
        print(name)
    print(address)

    1.2 forサイクル
    1.2.1 indexインデックス変数を作成するのではなく、ループでenumerate関数を使用する
    他のプログラミング言語のプログラマは、ループ内のコンテナを追跡するためにインデックス変数を明示的に宣言することが多い.例えば、C++では、
    for (int i=0; i < container.size(); ++i)
    {
        // Do stuff
    }
    

    pythonでは、この場合を処理するために組み込まれたenumerate関数があります.
    1.2.1.1悪いスタイル
    my_container = ['Larry', 'Moe', 'Curly']
    index = 0
    for element in my_container:
        print ('{} {}'.format(index, element))
        index += 1
    

    1.2.1.2 pythonのスタイル
    my_container = ['Larry', 'Moe', 'Curly']
    for index, element in enumerate(my_container):
        print ('{} {}'.format(index, element))
    

    1.2.2 inキーワードを使用して反復可能なオブジェクトを反復する
    欠落for_の使用eachスタイル言語のプログラマは、インデックスを介して要素反復コンテナにアクセスします.Pythonのinキーワードは優雅にこの状況を処理した.
    1.2.2.1悪いスタイル
    my_list = ['Larry', 'Moe', 'Curly']
    index = 0
    while index < len(my_list):
        print (my_list[index])
        index += 1
    

    1.2.2.2 pythonのスタイル
    my_list = ['Larry', 'Moe', 'Curly']
    for element in my_list:
        print (element)
    

    1.2.3 forループ終了後elseを使用してコードを実行
    forループ文の後ろにelse文が続くことを知っている人は少ない.反復器の実行が終了すると、break文が早期に終了したため、else文は実行されます.break文を使用すると、ループ内でいくつかの条件をチェックできます.条件が満たされた場合、ループを終了します.そうでなければ、ループが終了するまで実行を続行します(最終else文はいずれにしても実行する必要があります).これにより、サイクル内にフラグビットを追加することによって、条件が満たされているか否かを検査することを回避することができる.
    +
    次の場合、コードは、ユーザが登録したメールアドレスが正当であるか否かを検出するために使用される(1人のユーザが複数のアドレスを登録できる).pythonスタイルのコードは比較的簡潔で、has_を処理しないおかげです.malformed_email_addressフラグ.ましてや、プログラマーがforに詳しくなくても.else構文は、コードを簡単に理解することができます.
    1.2.3.1
    for user in get_all_users():
        has_malformed_email_address = False
        print ('Checking {}'.format(user))
        for email_address in user.get_all_email_addresses():
            if email_is_malformed(email_address):
                has_malformed_email_address = True
                print ('Has a malformed email address!')
                break
        if not has_malformed_email_address:
            print ('All email addresses are valid!')
    

    1.2.3.2
    for user in get_all_users():
        print ('Checking {}'.format(user))
        for email_address in user.get_all_email_addresses():
            if email_is_malformed(email_address):
                print ('Has a malformed email address!')
                break
        else:
            print ('All email addresses are valid!')

    1.3関数
    1.3.1関数パラメータのデフォルトとして',[]および{}の使用を避ける
    pythonチュートリアルではこれを明確に言及していますが、経験のある開発者でも驚く人が多いです.簡単に言えば、関数パラメータのデフォルト値は[]ではなく[]が優先されます.次はPythonチュートリアルのこの問題に対する処理です.
    1.3.1.1悪いスタイル
    # The default value [of a function] is evaluated only once.
    # This makes a difference when the default is a mutable object
    # such as a list, dictionary, or instances of most classes. For
    # example, the following function accumulates the arguments
    # passed to it on subsequent calls.
    def f(a, L=[]):
        L.append(a)
        return L
    print(f(1))
    print(f(2))
    print(f(3))
    # This will print
    #
    # [1]
    # [1, 2]
    # [1, 2, 3]
    

    1.3.1.2 pythonのスタイル
    # If you don't want the default to be shared between subsequent
    # calls, you can write the function like this instead:
    def f(a, L=None):
        if L is None:
            L = []
        L.append(a)
        return L
    print(f(1))
    print(f(2))
    print(f(3))
    # This will print
    # [1]
    # [2]
    # [3]
    

    1.3.2 argsと*kwargsを使用して任意の多くのパラメータを受け入れる
    通常、関数は任意の位置パラメータのリストまたはキーワードパラメータを受け入れ、その一部を使用して残りの部分を他の関数に渡す必要があります.argsおよび*kwargsをパラメータとして使用すると、関数は任意の位置およびキーワードパラメータのリストを受け入れることができます.この習性はAPIの後方互換性を維持する際にも有用である.関数が任意のパラメータを受け入れる場合は、既存のパラメータの少ないコードを破ることなく、新しいバージョンに新しいパラメータを自由に追加できます.すべてのドキュメントの記録が正しい限り、関数の実際のパラメータは何なのかはあまり影響しません.
    1.3.2.1悪いスタイル
    def make_api_call(foo, bar, baz):
        if baz in ('Unicorn', 'Oven', 'New York'):
            return foo(bar)
        else:
            return bar(foo)
    # I need to add another parameter to `make_api_call`
    # without breaking everyone's existing code.
    # I have two options...
    def so_many_options():
        # I can tack on new parameters, but only if I make
        # all of them optional...
        def make_api_call(foo, bar, baz, qux=None, foo_polarity=None,
                    baz_coefficient=None, quux_capacitor=None,
                    bar_has_hopped=None, true=None, false=None,
                    file_not_found=None):
            # ... and so on ad infinitum
            return file_not_found
    def version_graveyard():
        # ... or I can create a new function each time the signature
        # changes.
        def make_api_call_v2(foo, bar, baz, qux):
            return make_api_call(foo, bar, baz) - qux
        def make_api_call_v3(foo, bar, baz, qux, foo_polarity):
            if foo_polarity != 'reversed':
                return make_api_call_v2(foo, bar, baz, qux)
            return None
    
    def make_api_call_v4(
            foo, bar, baz, qux, foo_polarity, baz_coefficient):
        return make_api_call_v3(
            foo, bar, baz, qux, foo_polarity) * baz_coefficient
    def make_api_call_v5(
            foo, bar, baz, qux, foo_polarity,
            baz_coefficient, quux_capacitor):
        # I don't need 'foo', 'bar', or 'baz' anymore, but I have to
        # keep supporting them...
        return baz_coefficient * quux_capacitor
    def make_api_call_v6(
            foo, bar, baz, qux, foo_polarity, baz_coefficient,
            quux_capacitor, bar_has_hopped):
        if bar_has_hopped:
            baz_coefficient *= -1
        return make_api_call_v5(foo, bar, baz, qux,
                        foo_polarity, baz_coefficient,
                        quux_capacitor)
    def make_api_call_v7(
            foo, bar, baz, qux, foo_polarity, baz_coefficient,
            quux_capacitor, bar_has_hopped, true):
        return true
    def make_api_call_v8(
            foo, bar, baz, qux, foo_polarity, baz_coefficient,
            quux_capacitor, bar_has_hopped, true, false):
        return false
    def make_api_call_v9(
            foo, bar, baz, qux, foo_polarity, baz_coefficient,
            quux_capacitor, bar_has_hopped,
            true, false, file_not_found):
        return file_not_found
    

    1.3.2.2 pythonのスタイル
    def make_api_call(foo, bar, baz):
        if baz in ('Unicorn', 'Oven', 'New York'):
            return foo(bar)
        else:
            return bar(foo)
    # I need to add another parameter to `make_api_call`
    # without breaking everyone's existing code.
    # Easy...
    def new_hotness():
        def make_api_call(foo, bar, baz, *args, **kwargs):
            # Now I can accept any type and number of arguments
            # without worrying about breaking existing code.
            baz_coefficient = kwargs['the_baz']
            # I can even forward my args to a different function without
            # knowing their contents!
            return baz_coefficient in new_function(args)