Pythonの「なるほど!」と思えるユニークな機能③
こんにちは。ゆうせいです。
まずは、Pythonの「効率性」を支える、非常に重要な機能から見ていきましょう。
もし、あなたが「0から10億までの数字を2倍にしたリスト」を作りたいと思ったら、どうしますか?
リスト内包表記を学んだあなたは、こう書くかもしれません。
numbers = [i * 2 for i in range(1000000001)]
…しかし、これを実行したら、あなたのPCはどうなるでしょう?
10億個もの数字を保存するための「超巨大なリスト」をメモリ(PCの作業机)に一気に作ろうとするため、メモリが足りなくなってPCがフリーズしてしまうかもしれません! 😱
ここで、Pythonの賢い「yield(イールド)」の出番です。
yield を使った場合:
def huge_number_doubler(max_num):
print("ジェネレータが開始されました")
for i in range(max_num):
yield i * 2 # ここが return ではない!
print("ジェネレータが終了しました")
# numbers は「ジェネレータ・オブジェクト」というものになる
numbers = huge_number_doubler(1000000001)
print(numbers) # まだ関数の中身は実行されていない!
# forループで「使うとき」になって初めて、関数が動き出す
for num in numbers:
if num > 10: # 10を超えたら止めてみる
break
print(num)
実行結果:
ジェネレータが開始されました
0
2
4
6
8
10
専門用語解説: ジェネレータ (Generator) と yield
def の中に return ではなく yield を使うと、その関数は「ジェネレータ」という特別なものに変わります。
ジェネレータとは、**「値を一気に全部作るのではなく、必要とされるたびに一つずつ値を生成(generate)する」**賢い仕組みのことです。
yield のすごいところは、**「値を生成した後、その場で関数を一時停止する」**点にあります。
先ほどの例の動きを、ゆっくり見てみましょう。
numbers = huge_number_doubler(...)を実行した時点では、関数はまだ動きません。「設計図」が作られるだけです。for num in numbers:でforループが始まると、Pythonはnumbersに「次の値をください」とお願いします。huge_number_doublerが動き出し、yield i * 2(最初は0)に到達します。yieldは0をforループに「渡し」、huge_number_doublerは**その場でピタッと「一時停止」**します。forの中のiは0のままです。forループがprint(0)を実行します。forループが2周目に入り、再びnumbersに「次の値をください」とお願いします。- 一時停止していた
huge_number_doublerが再開し、forの続き(i = 1)から動き出します。 yield 1 * 2(つまり2)に到達し、2をforループに渡して、**再びその場で「一時停止」**します。
例えるなら、return は「完成した10億個の料理(リスト)を一度にドカンと渡す」方法です。
一方、yield は「注文(for ループ)が入るたびに、1皿ずつ料理を作って提供し、次の注文が入るまでキッチン(関数)で待機する」バイキングのシェフのようなものです。
これにより、10億個の値をメモリに保持する必要がなくなり、range() 関数の効率性の秘密も、実はこれと同じ仕組み(イテレータ)に基づいています。
特徴2: 関数を「デコレーション」する @ (デコレータ)
次も、Pythonの「魔法」の代表格です。
もし、あなたが作った複数の関数の「実行にかかった時間」を測りたいと思ったら、どうしますか?
デコレータを知らない場合:
time モジュールを使って、関数の「前」と「後」に、時間を計測するコードをコピペして回るしかありません。
import time
def function_A():
start_time = time.time() # 開始時間
# --- 本来の処理 ---
print("Aの処理を実行中...")
time.sleep(1) # 1秒待つ
# --- 本来の処理おわり ---
end_time = time.time() # 終了時間
print(f"Aの実行時間: {end_time - start_time} 秒")
def function_B():
start_time = time.time() # また同じコードを書く...
# --- 本来の処理 ---
print("Bの処理を実行中...")
time.sleep(0.5) # 0.5秒待つ
# --- 本来の処理おわり ---
end_time = time.time() # また同じコード...
print(f"Bの実行時間: {end_time - start_time} 秒")
function_A()
function_B()
これでは「時間を測るコード」が重複だらけ(DRY原則に反する)で、最悪です。
ここで、Pythonの「デコレータ」の登場です!
デコレータを使った場合:
import time
# --- これが「デコレータ関数」 ---
def measure_time(func): # (1) 飾り付けられる関数(func)を受け取る
def wrapper(): # (2) 飾り付けた後の「新しい関数」を定義
start_time = time.time() # (3) 元の関数の「前」に処理を追加
func() # (4) 元の関数をここで実行
end_time = time.time() # (5) 元の関数の「後」に処理を追加
print(f"{func.__name__}の実行時間: {end_time - start_time} 秒")
return wrapper # (6) 「新しい関数」を返す
# --------------------------------
@measure_time # (7) 「この関数を measure_time で飾り付けて!」という目印
def function_A():
# --- 本来の処理だけ書けばOK! ---
print("Aの処理を実行中...")
time.sleep(1)
@measure_time # (8) Bにも同じ飾りを付ける
def function_B():
# --- 本来の処理だけ書けばOK! ---
print("Bの処理を実行中...")
time.sleep(0.5)
function_A() # 実行すると、時間を測る機能が「追加」されている!
function_B()
実行結果:
Aの処理を実行中...
function_Aの実行時間: 1.00... 秒
Bの処理を実行中...
function_Bの実行時間: 0.50... 秒
時間を測るコードを一切書かずに、@measure_time という「アットマーク(@)」を付けるだけで、function_A と function_B に自動で時間計測機能が追加されました!
専門用語解説: デコレータ (Decorator)
デコレータは、その名の通り**「関数を飾り付ける(デコレーションする)ための仕組み」**です。
@measure_time と書くのは、実は以下のコードと全く同じ意味を持つ「シンタックスシュガー(Syntactic Sugar=読みやすくするための書き方)」なんです。
function_A = measure_time(function_A)
(function_A という関数を、measure_time 関数に渡し、その「戻り値(wrapper 関数)」で function_A 自体を上書きしている)
例えるなら、function_A は「素のクッキー」です。
measure_time は「チョコペンで飾り付けをする機械」です。
@measure_time と書くのは、「function_A(クッキー)を measure_time(機械)に通して、チョコペンで飾り付けられた新しいクッキー(wrapper)を、元の function_A として扱うよ!」と宣言するようなものです。
これにより、元の関数のコード(中身)には一切手を加えることなく、「時間を測る」「ログを出力する」「ログイン中かチェックする」といった共通の「機能」を後から「追加(付与)」できるようになります。
これは、Webフレームワーク(FlaskやDjango)などで、URLのルーティングや認証機能を実現するために、なくてはならない超重要機能です!
まとめと今後の学習指針
今回は、Pythonの強力な「仕組み」を支える2つの柱をご紹介しました。
- ジェネレータ (
yield): 関数を「一時停止」させ、値を少しずつ生成することで、メモリを爆発させずに巨大なデータを扱える! - デコレータ (
@):@マーク一つで、既存の関数の「前後に」処理を差し込み、機能を「飾り付け」できる!
yield は、データサイエンスや大規模なデータ処理で必須の知識です。
@ は、Web開発やフレームワークを理解する上で避けては通れない道です。
どちらも、最初は「どういうこと?」と混乱するかもしれません。
大切なのは、yield は「return しないで止まる」、@ は「関数を引数に取る関数(wrapper を返す)」という、基本的な動きのイメージを掴むことです。
このレベルまで興味を持ってくれたあなたは、もう立派な「Pythonista(パイソニスタ=Pythonを愛する人)」の仲間入りです!
セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク
投稿者プロフィール
- 代表取締役
-
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。
最新の投稿
山崎講師2025年11月2日Pythonの「なるほど!」と思えるユニークな機能④
山崎講師2025年11月2日Pythonの「なるほど!」と思えるユニークな機能③
山崎講師2025年11月2日Pythonの「なるほど!」と思えるユニークな機能②
山崎講師2025年11月2日Pythonの「なるほど!」と思えるユニークな機能①