モブプロだった人のブログ

モブプログラミングに魅了されたエンジニアのブログ

Python で unittest ですべてをテストする

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第6弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で unittest ですべてをテストする

ひー!!

いや、ホントそうですよね。

こじんまりと Python 使っていたときはそこまで意識してなかったですが、やはり unittest 大事ですよね。(まぁ、Python に限らずすべての言語で必要だと思いますが。)

写経した結果をぺたりとすると、

# utils.py

def to_str(data):
    if isinstance(data, str):
        return data
    elif isinstance(data, bytes):
        return data.decode('utf-8')
    else:
        raise TypeError('Must supply str or bytes, found: %r' % data)

↑がテスト対象の関数で、

# utils_test.py

from unittest import TestCase, main
from utils import to_str


class UtilsTestCase(TestCase):
    def test_to_str_bytes(self):
        self.assertEqual('hello', to_str(b'hello'))

    def test_to_str_str(self):
        self.assertEqual('hello', to_str('hello'))

    def test_to_str_bad(self):
        self.assertRaises(TypeError, to_str, object())


if __name__ == '__main__':
    main()

↑がテストコードです。

うん、良いですね、unittest って。

Python で リストを返さずにジェネレータを返すことを考える

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第5弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で リストを返さずにジェネレータを返すことを考える

ジェネレータ! あまり使ったことなかったです。

リストだとメモリを食いつぶす恐れのある処理などで使うと便利そうというのは分かっていても、なかなかその実装にまでたどり着きませんでした・・・。

実際にコードで見てみると分かりやすいですね。

def index_words(text):
    result = []
    if text:
        result.append(0)
    for index, letter in enumerate(text):
        if letter == ' ':
            result.append(index + 1)
    return result

def index_words_iter(text):
    if text:
        yield 0
    for index, letter in enumerate(text):
        if letter == ' ':
            yield index + 1

if __name__ == '__main__':
    address = 'Four score and seven years ago...'

    result = index_words(address)
    print(result)

    result = index_words_iter(address)
    print(list(result))

index_words() がリストを返すメソッドで、index_words_iter() がジェネレータを返すメソッドです。

メモリ関係の話は抜きにしても、コード自体がスッキリとして、とても見やすいですね!

今後は、リストではなくジェネレータに出来ないか、検討する時間を設けてみたいと思います。

Python で try/except/else/finally の各ブロックを活用する

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第4弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で try/except/else/finally の各ブロックを活用する

tryexcept は良く使いますし、finally も使いますが、else をあまり使ったことがないことに気付きました。

else を使うことのメリットは、

どの例外がココのコードで扱われていて、どの例外が上に伝わっていくかを明確にできること

です。

import json

def load_json_key(data, key):
    try:
        result_dict = json.loads(data) # ValueError の可能性
    except ValueError as e:
        print('ValueError!!')
        raise
    else:
        return result_dict[key] # KeyError の可能性

if __name__ == '__main__':
    result_value = load_json_key('{"A": 10, "B": 20}', 'C')
    print(result_value)

上記の例でいうと、

  • json.loads(data) の部分で発生する可能性のある ValueError については、ココのコードで扱うというのが明確になる
  • result_dict[key] の部分で発生する可能性のある KeyError については、ココでは特に何もせず上に伝えるというのが明確になる

ということです。若干、例が分かり難いような気もしますが・・・。

今後は、積極的に try/except/else/finally の各ブロックを活用していきたいと思います。

Python で 可変長位置引数を使って、見た目をすっきりさせる

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第3弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で 可変長位置引数を使って、見た目をすっきりさせる

すっきりとはなんぞや? という感じかもしれませんが、具体的にコードを見ると分かりやすいのです。

def log(message, values):
    if not values:
        print(message)
    else:
        values_str = ', '.join(str(x) for x in values)
        print('{0}: {1}'.format(message, values_str))

def log_with_star_args(message, *values):
    if not values:
        print(message)
    else:
        values_str = ', '.join(str(x) for x in values)
        print('{0}: {1}'.format(message, values_str))

if __name__ == '__main__':
    my_numbers = [1, 2]

    # NG
    log('My numbers are', my_numbers)
    log('My numbers are', [3, 4])
    log('Hi there', [])

    # OK
    log_with_star_args('My numbers are', *my_numbers)
    log_with_star_args('My numbers are', 3, 4)
    log_with_star_args('Hi there')

log() を呼んでる方は、第2引数に渡すものがないときに空のリストを渡していますが、log_with_star_args() を呼んでる方は、渡すものがなければ渡さなくて良くなりました。

すっきりしてて良いですね。

なお、可変長位置引数は、仮引数が *args のような記法なので「スター引数(star args)」とも呼ばれます。

Python で map や filter の代わりにリスト内包表記を使う

「Effective Python」を久しぶりに読み返して、当時は特に何も思わなかった箇所に引っかかったのでメモを残しておきますシリーズ第2弾。

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

Python で map や filter の代わりにリスト内包表記を使う

うん、めちゃくちゃ map 使いますね。いや、めちゃくちゃは言い過ぎかもしれません。たまに使います。

リストに対して何かするとき、基本的にはリスト内包表記でなんとかするんですが、たまに何を思ったか map でガチャガチャしてしまうときがあります。(paiza とかで遊んでる時は特に。)

リスト内包表記の方が良い理由はとてもシンプルで、

  • 何か処理を施す時に lambda 式が必要ないので、見た目がごちゃごちゃしない
  • リストから要素を抜き出すのが簡単 (map の場合、filter が必要になる。)

といったところです。

例えば、スペース区切りの数字を文字列として受け取り、それらに5を足した数値をリストにしたい時、

input_string = '10 20 30'

# map の場合
numbers = list(map(lambda s: int(s) + 5, input_string.split()))
print(numbers) #=> [15, 25, 35]

# リスト内包表記の場合
numbers = [int(s) + 5 for s in input_string.split()]
print(numbers) #=> [15, 25, 35]

スッキリしますね、はい。