関数の第一級オブジェクトとしての性質
Pythonでは関数はprint()に渡します。
# 関数オブジェクトの識別子確認
def greet():
print("hello")
print(greet) # <function greet at 0x...>変数への代入と間接呼び出し
def calculate():
return 42
alias = calculate # 関数名を変数に束縛
result = alias() # alias経由で関数を実行
print(result) # 42高階関数としての利用
def multiplier(factor):
def inner(value):
return value * factor
return inner # 内部関数を返す
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15コレクションでの管理とディスパッチ
def action_a(): return "処理A"
def action_b(): return "処理B"
def action_c(): return "処理C"
handlers = [action_a, action_b, action_c]
for handler in handlers:
print(handler()) # 各関数を順次実行クロージャのメカニズム
クロージャとは、入れ子関数の内側関数が、外側関数のスコープにある変数を参照し、その変数が内側関数の実行時まで存続する仕組みです。ただし、参照する変数はグローバル変数であってはいけません。
クロージャの形成条件
def outer(x):
def inner():
# outerのローカル変数xを参照
return x ** 2
return inner
square = outer(10) # outer実行後、xは本来消滅するが...
print(square()) # 100 — クロージャによりxが保持されるクロージャの検証方法
def create_counter(start):
count = start
def increment():
nonlocal count
count += 1
return count
return increment
counter = create_counter(0)
# __closure__属性で確認(セルオブジェクトの有無)
print(counter.__closure__) # (<cell ...>,)
print(counter.__closure__[0].cell_contents) # 0
# 非クロージャの場合
def standalone():
return 123
print(standalone.__closure__) # None多層クロージャの例
def level_one(a):
def level_two(b):
def level_three(c):
return a + b + c
return level_three
return level_two
builder = level_one(1)
final = builder(2)
print(final(3)) # 6 — a=1, b=2, c=3が全て保持されるイテレータプロトコル
Pythonのイテレータは__iter__()と__next__()の両方を実装したオブジェクトです。__iter__のみを持つIterableと区別されます。
IterableとIteratorの違い
# dir()でメソッド確認
data = [1, 2, 3]
print('__iter__' in dir(data)) # True — Iterable
print('__next__' in dir(data)) # False — Iteratorではない
it = iter(data) # __iter__()でIterator生成
print('__next__' in dir(it)) # True — これがIterator手動でのイテレーション
items = ['alpha', 'beta', 'gamma']
iterator = items.__iter__()
print(iterator.__next__()) # 'alpha'
print(iterator.__next__()) # 'beta'
print(iterator.__next__()) # 'gamma'
# 次回呼び出しで StopIteration 例外発生Iterator生成の落とし穴
values = [10, 20, 30]
# 毎回新しいIteratorを生成しているため、先頭要素を繰り返す
print(values.__iter__().__next__()) # 10
print(values.__iter__().__next__()) # 10
print(values.__iter__().__next__()) # 10
# 正しい方法:同一Iteratorを保持
it = values.__iter__()
print(it.__next__()) # 10
print(it.__next__()) # 20for文の内部動作
collection = ['x', 'y', 'z']
# 以下のfor文は...
for element in collection:
print(element)
# 内部的には以下と等価
_idx = 0
_itr = collection.__iter__()
while _idx < len(collection):
print(_itr.__next__())
_idx += 1型チェックの方法
from collections.abc import Iterable, Iterator
text = "sample"
print(isinstance(text, Iterable)) # True — 文字列はIterable
print(isinstance(text, Iterator)) # False — 文字列自体はIteratorでない
text_iter = iter(text)
print(isinstance(text_iter, Iterator)) # True — iter()適用後はIteratorイテレータの特性
- 消費型:__next__()は前方のみ、一度読んだ要素には戻れない
- 遅延評価:必要な時点まで値の生成を延期する
- 状態保持:現在位置を内部に保持する
# 遅延評の実例:無限シーケンス
import itertools
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 無限だが、必要な分だけ評価
fib = fibonacci()
for _ in range(5):
print(next(fib)) # 0, 1, 1, 2, 3