コース進捗 コース目次 24レッスン中 24件を公開中
Pythonの基礎
データとコレクション
信頼できるプログラムを作る
オブジェクトでモデル化する
プロフェッショナルなPython
上級Python
本棚としおりは別の物
リストは、何度も使える値の本棚です。**iterable(反復可能オブジェクト)**は、しおりを渡せる物です。**iterator(イテレーター)**は、次の値がどこにあるかを覚える、状態付きのしおりです。
- Iterable 訪ねられる本棚
- iter() しおりを作る
- next() 進んで一つ受け取る
topics = ["iterables", "iterators", "generators"]
bookmark = iter(topics)
print(next(bookmark))
print(next(bookmark))
print(list(bookmark))
print(list(bookmark))
出力:
iterables
iterators
['generators']
[]
イテレーターは一回限りなので、二回目のlist(bookmark)は空です。元のリストは再利用できます。もう一度iter(topics)を呼べば、新しいイテレーターを作れるからです。ファイル、文字列、rangeなどもiterableです。
StopIterationは「道の終わり」
次の値がないと、next()はStopIterationを送出します。forループは、この普通の終了合図を内側で処理してくれます。
- iter(iterable) イテレーターを一つ得る
- next(iterator) 値を一つ受け取る
- 繰り返す もう一度たずねる
- StopIteration ループを終える
bookmark = iter([10, 20])
while True:
try:
value = next(bookmark)
except StopIteration:
print("終了")
break
else:
print(value)
出力:
10
20
終了
普通の仕事には普通のforを使います。正確な境界が大切なときだけ、next()を直接使いましょう。next(bookmark, "空")なら既定値を返せますが、本物のデータと混同しない印を選びます。
ジェネレーターの中からStopIterationを直接送出してはいけません。returnするか末尾まで進みます。外へ漏れたStopIterationはRuntimeErrorになります。
yieldは一時停止ボタン
yieldを含む関数はジェネレーター関数です。呼び出すとジェネレーターを作りますが、まだ本体は動きません。最初に引かれると開始し、yieldのたびに値を一つ渡してlocalな状態を凍らせます。
- 動く yieldまで仕事をする
- 止まる localな値を覚える
- 渡す 値を一つ外へ出す
def count_to(limit):
print("開始")
for number in range(1, limit + 1):
yield number
numbers = count_to(3)
print("作成")
print(next(numbers))
print(list(numbers))
出力:
作成
開始
1
[2, 3]
リスト内包表記はすべてを今すぐ作ります。(n * n for n in range(5))のようなジェネレーター式は、引かれた分だけ作ります。繰り返し走査、長さ、添字、スナップショットが必要ならリストを選びます。一回でよく、作業を遅らせたり最大メモリを減らしたりしたいなら遅延ストリームを選びます。遅延だから自動的に速いわけではありません。
小さなストリームを注意してつなぐ
yield fromは、別のiterableから来る値をそのまま渡します。
def whole_course():
yield from ["基礎", "コレクション"]
yield from ["ジェネレーター", "テスト"]
print(list(whole_course()))
出力:
['基礎', 'コレクション', 'ジェネレーター', 'テスト']
遅延処理はエラーも遅らせます。不正な行は、ジェネレーターを作った場所から離れた所で失敗するかもしれません。作成だけでなく、消費もテストします。ジェネレーターがファイルを開くと、一時停止中も開いたままになることがあります。リソースのwith内で消費するか、所有者とtry / finallyによる閉じ方を明確にします。ストリームが分類できる例外だけを捕捉しましょう。
generator.send(value)なら双方向に話せますが、道具箱の端に置いてください。新しいジェネレーターは、まずnext(generator)で最初のyieldまで進めます。その前にNone以外を送るとTypeErrorです。普通のパイプラインでは、設定は引数で入れ、値はyieldで出す一方向の形がずっと明確です。
遅延読書コンベヤーを作る
次の標準ライブラリだけの完全なプロジェクトをreading_conveyor.pyとして保存します。
from dataclasses import dataclass
from itertools import islice
@dataclass(frozen=True)
class Reading:
topic: str
minutes: int
def joined(*groups):
for group in groups:
yield from group
def parse_readings(lines):
for line_number, line in enumerate(lines, start=1):
parts = [part.strip() for part in line.split("|")]
if len(parts) != 2 or not parts[0]:
raise ValueError(f"行{line_number}:topic|minutesの形が必要です")
try:
minutes = int(parts[1])
except ValueError as cause:
raise ValueError(f"行{line_number}:分数が不正です") from cause
if minutes <= 0:
raise ValueError(f"行{line_number}:分数は正である必要があります")
yield Reading(parts[0], minutes)
def at_least(readings, minimum):
for reading in readings:
if reading.minutes >= minimum:
yield reading
raw_lines = joined(
["イテレーター | 25", "ジェネレーター | 40"],
["壊れた行"],
)
selected = at_least(parse_readings(raw_lines), minimum=20)
first_two = list(islice(selected, 2))
assert first_two == [Reading("イテレーター", 25), Reading("ジェネレーター", 40)]
print("最初の取得:", first_two)
try:
next(selected)
except ValueError as error:
print("次の取得:", error)
python3 reading_conveyor.pyで実行します。
最初の取得: [Reading(topic='イテレーター', minutes=25), Reading(topic='ジェネレーター', minutes=40)]
次の取得: 行3:topic|minutesの形が必要です
islice()が要求するのは二つだけなので、最初は壊れた三行目に触れません。次の取得でそこへ到達し、遅れてエラーになります。assertは、情報源全体をリストにせず、順序と中身を確かめます。
3つの小さなチャレンジ
- イテレーターを一つ作り、
next()を二回呼び、残りを二回リストへ変換する。 joined()へ三つ目のグループを足し、実行前に全体の順番を予想する。islice(selected, 2)をislice(selected, 1)へ変え、次の取得がどの行へ進むか説明する。
第18章へ進めるかな?
- iterableとiteratorを区別できる
-
forの後ろにあるiter()、next()、StopIterationを説明できる - ジェネレーターがいつ始まり、
yieldが何を止めるか分かる - 再利用できるリストと、一回限りの遅延ストリームを選び分けられる
- リソース所有を隠さず、
yield fromで段階をつなげられる - 読書コンベヤーを実行し、引かれたときだけエラーが届くのを確認した
次章では、デコレーターで関数呼び出しの周りに、コンテキストマネージャーでコードブロックの周りに方針を置きます。