検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
>>
effective_python

【 Effective Python, 2nd Edition 】リストを返す関数を作るなら、ジェネレータを返しちゃダメか1回立ち止まって考えよう、の巻 投稿一覧へ戻る

Published 2020年6月27日19:16 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

結果としてシーケンスを返す関数を作成する場合、最も一般的な選択肢は「リストを返す」ということでしょう。


次の例は、受け取った文字列について、文字列中の各単語の先頭インデックス番号と該当する単語のタプルからなるリストを返すものです。

sentence = "Nana is cute and very popular among my friends."


def index_words(text):
result = []
prev_index = 0

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


result_list = index_words(sentence)


print(result_list)
# [(0, 'Nana'), (5, 'is'), (8, 'cute'), (13, 'and'), (17, 'very'), (22, 'popular'), (30, 'among'), (36, 'my'), (39, 'friends.')]



この実装で気になるのは、見つかった単語をセットし結果的に呼び出し元に返す result リストがイヤに目に付くことです。
定義で1回 (result = [])、単語の追加で2回 (result.append)、返却で1回 (return result) ですね、目立ってます。


そしてより重視すべきは、メモリの消費量の問題です。
この実装方法だと、返却値は一旦リストに全てセットされます。つまり、そのリストを保持しておくだけのメモリが消費される、ということです。
もし入力されるデータが膨大なものだったら、おおっ恐ろしい、プログラムがクラッシュすることだってあるかもしれません!


これら2つの問題を解決するより良い実装方法はジェネレータ (generator) を利用することです。
ご存知のようにジェネレータは yield 書式を含む関数のことで、呼び出されてもすぐに実行されるわけではありません。
その代わりに直ちにイテレータを返します。
このイテレータは next() で呼び出される度に yield 書式の結果を返し、次の yield 書式の直前まで実行し、そこでまた次の呼び出しを待ちます。


さて、上の関数と同じ結果を返すジェネレータ関数を定義してみましょう。


def index_words_gen(text):
prev_index = 0

for index, letter in enumerate(text):
if letter == ' ':
yield prev_index, text[prev_index:index]
prev_index = index + 1
yield prev_index, text[prev_index:len(text)]


it = index_words_gen(sentence)
result_list = list(it)


print(result_list)
# [(0, 'Nana'), (5, 'is'), (8, 'cute'), (13, 'and'), (17, 'very'), (22, 'popular'), (30, 'among'), (36, 'my'), (39, 'friends.')]



どうでしょうか?リスト result の記述が全てなくなってスッキリしたのではないでしょうか?
また、その都度返す値を処理するメモリを消費するだけですから、リストを利用している場合と違って枕を高くして眠れます。


イテレータからは next() を利用して結果を1つずつ取得することも、この例のように list() に渡してリストに変換することも簡単にできます。


入力されるデータが大きい可能性を完全に否定できない場合はジェネレータの利用を考えてみましょう。
この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -
【Python 雑談・雑学 + coding challenge】iterator protocol の実装 --- __iter__ 特殊関数は何を返すべき? イテレータオブジェクト ( iterator object ) なら何でも、そう、generator expression でもOKです!
【 Effective Python, 2nd Edition 】ジェネレータ ( generator ) に値を注入したいなら、yield from 式と send() 関数の併用よりも、注入する値を提供するイテレータ ( iterator ) を渡しましょう、の巻
【 Effective Python, 2nd Edition 】throw() メソッドを利用したジェネレータ ( generator ) 内部での状態遷移はなるだけ避けましょう。ネストが深くなってコードの読解性が落ちちゃいますよ!
【 Effective Python, 2nd Edition 】入力元のデータサイズが大きい場合は、リスト内包表記 ( list comprehension ) ではなくジェネレータ式 ( generator expression ) の利用を検討しよう!
【 Effective Python, 2nd Edition 】プログラムを並列処理 ( concurrency ) パターンへ移行するタイミングとツールを考えるシリーズ 第 5 回 - 並列処理 ( concurrency ) のためにスレッド ( thread ) を利用する場合は concurrent.futures モジュールの ThreadPoolExecutor の導入を検討しましょう、の巻
【 Effective Python, 2nd Edition 】プログラムを並列処理 ( concurrency ) パターンへ移行するタイミングとツールを考えるシリーズ 第 4 回 - 並列処理 ( concurrency ) 実現のために queue を利用するとリファクタリング ( refactoring ) 作業が大変です、の巻
【 Effective Python, 2nd Edition 】引数として受け取った値を関数内で複数回「消費」する場合には要注意! イテレータ ( iterator ) とコンテナ ( container ) の違いをちゃんと認識しよう!