3章学習記録 » 履歴 » バージョン 1
Tatsuya ISHIGAKI, 2025/06/19 13:53
| 1 | 1 | Tatsuya ISHIGAKI | # 第3章 Python の言語仕様 |
|---|---|---|---|
| 2 | **Python 実践レシピ** (技術評論社) |
||
| 3 | |||
| 4 | ## 3.2 with 文 |
||
| 5 | - with 文へ渡すオブジェクトを **context manager** という |
||
| 6 | - 特殊メソッド `__enter__()`, `__exit__()` を実装したクラスのインスタンス |
||
| 7 | - 処理の流れは以下の通り |
||
| 8 | 1. with ブロック実行前に **\_\_enter\_\_()** が実行される |
||
| 9 | 1. with ブロック内の実行 |
||
| 10 | 1. with ブロック終了後に **\_\_exit\_\_()** が実行される |
||
| 11 | - **\_\_enter\_\_()** で返した値は as で参照できる |
||
| 12 | - with ブロックで例外発生の場合は **\_\_exit\_\_()** の引数で例外情報受け取れる |
||
| 13 | - **@contextlib.contextmanager** デコレーター |
||
| 14 | - ジェネレータ関数を書くと、context manager として使用できる |
||
| 15 | - `yield` の前が with ブロック前に、`yield` より後が with ブロック後に実行される |
||
| 16 | - **yield** で返す値は as 参照できる |
||
| 17 | - with 文での例外は、yield 箇所で再送出される |
||
| 18 | - その他 **contextlib** モジュールの context manager |
||
| 19 | - `contextlib.suppress(Exceptions)` : 指定した例外を無視 |
||
| 20 | - `contextlib.redirect_stdout(new_target)` : 標準出力先を一時的に変更 |
||
| 21 | - `contextlib.redirect_stderr(new_target)` : 標準エラー先を一時的に変更 |
||
| 22 | |||
| 23 | ## 3.3 関数の引数 |
||
| 24 | - 位置引数 |
||
| 25 | - キーワード引数 (仮引数名=値 で指定) |
||
| 26 | - 実引数に位置引数とキーワード引数を混在させる場合は、位置引数を先に指定 |
||
| 27 | - デフォルト値付き引数 : 定義時に `仮引数名=値` を指定 |
||
| 28 | - 可変長位置引数 : 定義時に仮引数名にアスタリスクを前置する `*args` |
||
| 29 | - 受け取った引数はタプルとして扱う |
||
| 30 | - 可変長キーワード引数 : 定義時に仮引数名にアスタリスク2個を前置する `**kwargs` |
||
| 31 | - 受け取った引数は辞書として扱う |
||
| 32 | - 引数定義は次の順に行う |
||
| 33 | 1. 位置引数 |
||
| 34 | 2. 可変位置引数 |
||
| 35 | 3. デフォルト値付き引数 (呼び出しでキーワード指定したい場合、可変位置引数より後に定義の必要がある) |
||
| 36 | 4. 可変キーワード引数 |
||
| 37 | - キーワード専用引数 : `*` の後に定義することで、キーワード引数として指定しなければ呼び出せない制限を付けられる |
||
| 38 | ```python |
||
| 39 | >>> def sample_func(param1, *, keyword1): |
||
| 40 | ... print(f'{param1}, {keyword1}') |
||
| 41 | ... |
||
| 42 | >>> sample_func(1, keyword1=False) |
||
| 43 | 1, False |
||
| 44 | >>> samele_func(1, False) # エラー |
||
| 45 | (略) |
||
| 46 | ``` |
||
| 47 | - 位置専用引数 : `/` の前に定義することで、位置引数として指定しなければ呼び出せない制限を付けられる |
||
| 48 | ```python |
||
| 49 | >>> def add(x, y, /): |
||
| 50 | ... return x + y |
||
| 51 | ... |
||
| 52 | >>> add(1, 2) |
||
| 53 | 3 |
||
| 54 | >>> add(x=1, y=2) # エラー |
||
| 55 | (略) |
||
| 56 | ``` |
||
| 57 | - デフォルト値付き引数の式は **関数が定義されるときにただ一度だけしか評価されない** |
||
| 58 | ```python |
||
| 59 | >>> def sample_func(a, b, c=[]): |
||
| 60 | ... c.append(a + b) |
||
| 61 | ... return c |
||
| 62 | ... |
||
| 63 | >>> sample_func(1, 2) |
||
| 64 | [3] |
||
| 65 | >>> sample_func(3, 4) |
||
| 66 | [3, 7] |
||
| 67 | >>> sample_func(5, 6) |
||
| 68 | [3, 7, 11] |
||
| 69 | ``` |
||
| 70 | ```python |
||
| 71 | >>> def sample_func(a, b, c=None): |
||
| 72 | ... if c is None |
||
| 73 | ... c = [] |
||
| 74 | ... c.append(a + b) |
||
| 75 | ... return c |
||
| 76 | ... |
||
| 77 | >>> sample_func(1, 2) |
||
| 78 | [3] |
||
| 79 | >>> sample_func(3, 4) |
||
| 80 | [7] |
||
| 81 | >>> sample_func(5, 6) |
||
| 82 | [11] |
||
| 83 | ``` |
||
| 84 | |||
| 85 | ## 3.4 アンパック |
||
| 86 | - タプル、リスト、辞書から複数の値を変数へ展開する |
||
| 87 | ```python |
||
| 88 | >>> tp = (1, 2, 3) |
||
| 89 | >>> a, b, c = tp # タプル |
||
| 90 | >>> print(f'{a}, {b}, {c}') |
||
| 91 | 1, 2, 3 |
||
| 92 | ``` |
||
| 93 | ```python |
||
| 94 | >>> lt = [1, 2, 3] |
||
| 95 | >>> a, b, c = lt # リスト |
||
| 96 | >>> print(f'{a}, {b}, {c}') |
||
| 97 | 1, 2, 3 |
||
| 98 | ``` |
||
| 99 | ```python |
||
| 100 | >>> dic = {"a": 1, "b": 2, "c": 3} |
||
| 101 | >>> k1, k2, k3 = dic # 辞書のキー |
||
| 102 | >>> print(f'{k1}, {k2}, {k3}') |
||
| 103 | a, b, c |
||
| 104 | >>> v1, v2, v3 = dic.values() # 辞書の値 |
||
| 105 | >>> print(f'{v1}, {v2}, {v3}') |
||
| 106 | 1, 2, 3 |
||
| 107 | ``` |
||
| 108 | - 数が合わないとエラー |
||
| 109 | ```python |
||
| 110 | >>> a, b = (1, 2, 3) # エラー |
||
| 111 | (略) |
||
| 112 | >>> a, b, c = (1, 2) # エラー |
||
| 113 | (略) |
||
| 114 | ``` |
||
| 115 | - ネストタプル、ネストリスト (例はタプルのみ) |
||
| 116 | ```python |
||
| 117 | >>> tp = (1, 2, (3, 4, 5)) |
||
| 118 | >>> a, b, c = tp |
||
| 119 | >>> print(f'{a}, {b}, {c}') |
||
| 120 | 1, 2, (3, 4, 5) |
||
| 121 | >>>a, b, (c, d, e) = tp |
||
| 122 | >>> print(f'{a}, {b}, {c}, {d}, {e}') |
||
| 123 | 1, 2, 3, 4, 5 |
||
| 124 | ``` |
||
| 125 | - アスタリスクでのアンパック |
||
| 126 | ```python |
||
| 127 | >>> tp = (1, 2, 3, 4, 5) |
||
| 128 | >>> a, b, *c = tp |
||
| 129 | >>> print(f'{a}, {b}, {c}') |
||
| 130 | 1, 2, [3, 4, 5] |
||
| 131 | >>> a, *b, c = tp |
||
| 132 | >>> print(f'{a}, {b}, {c}') |
||
| 133 | 1, [2, 3, 4], 5 |
||
| 134 | ``` |
||
| 135 | - アスタリスクは一つの変数のみ |
||
| 136 | - 辞書のキーと値 |
||
| 137 | - 辞書の `items()` メソッドは、キーと値のタプルを返すので、アンパックして同時に扱える |
||
| 138 | - タプル、リストのインデックスと値 |
||
| 139 | - 組み込み関数 `enumerate()` はイテラブルオブジェクトを引数にとり、インデックスと値のタプルを返すので、アンパックして同時に扱える |
||
| 140 | |||
| 141 | ## 3.5 内包表記、ジェネレーター式 |
||
| 142 | - リスト、辞書、集合、ジェネレーターで内包表記が使える |
||
| 143 | - リスト内包表記 `[変数使用の処理 for 変数 in イテラブルオブジェクト]` |
||
| 144 | - リスト内包表記 `[変数使用の処理 for 変数 in イテラブルオブジェクト if 条件式]` |
||
| 145 | ```python |
||
| 146 | >>> num_lt = [i**2 for i in range(10)] |
||
| 147 | >>> num_lt |
||
| 148 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] |
||
| 149 | ``` |
||
| 150 | ```python |
||
| 151 | >>> num_lt = [i**2 for i in range(10) if i % 2 == 0] |
||
| 152 | >>> num_lt |
||
| 153 | [0, 4, 16, 36, 64] |
||
| 154 | ``` |
||
| 155 | - ネストした内包表記 |
||
| 156 | ```python |
||
| 157 | >>> rows = ("A", "B", "C") |
||
| 158 | >>> cols = ("1", "2", "3", "4") |
||
| 159 | >>> mat = [f'{row}{col}' for row in rows for col in cols] |
||
| 160 | >>> mat |
||
| 161 | ['A1', 'A2', 'A3', 'A4', 'B1', 'B2', 'B3', 'B4', 'C1', 'C2', 'C3', 'C4'] |
||
| 162 | ``` |
||
| 163 | - 後ろに書いた内容が内側のループ (外側1:内側全部 が繰り返される) |
||
| 164 | - リスト以外の内包表記 |
||
| 165 | - 集合 `st = {i**2 for i in range(10)}` |
||
| 166 | - 辞書 `dc = {i:i**2 for i in range(10)}` |
||
| 167 | - ジェネレーター `gt = (i**2 for i in range(10))` |
||
| 168 | - タプルではない (※タプルの内包表記は存在しない) |
||
| 169 | |||
| 170 | ## 3.6 ジェネレーター |
||
| 171 | - ほぼ関数だが、`return` 文の代わりに `yield` 文を使用する |
||
| 172 | - `yield` は厳密には **式であるが文として使用できる** もの |
||
| 173 | - Python は関数に yield が使用されていると、それを **ジェネレーター** として扱う |
||
| 174 | - yield 文で値が返されると、処理を一時中断して次の呼び出しを待つ |
||
| 175 | - この状態は **状態が保持されたイテラブルオブジェクト** である |
||
| 176 | - さらに詳細には **計算途中の状態を保持し、必要なデータを1つずつ返すことができるイテレーター** が返されている |
||
| 177 | - **イテレーターオブジェクト** は次の値を返すメソッド `__next()__` を持っており、ジェネレーターが返すのはイテレーターなので、このメソッドが使用でき、効果は次のようになる |
||
| 178 | - (初回)「実行開始 (1回目の yield での値返却、中断)」 |
||
| 179 | - (2回目以降)「処理の再開 (次の yield での値返却、中断)」 |
||
| 180 | - (返却値が無い場合)「StopIteration例外の送出」 |
||
| 181 | - 組み込み関数 `next()` にジェネレーターを渡すことで、`__next()__` 呼び出しと同等の結果が得られる |
||
| 182 | - `list()` では、ジェネレーターの返却値すべてを取得したリストを得られる |
||
| 183 | - `next()` 使用時の様にひとつの値を取得した際には、一時停止であることを念頭に置く必要がある |
||
| 184 | - ジェネレーター内部のループが中断している場合など、処理済み、未処理の内容を把握しておく |
||
| 185 | - ジェネレーター定義の全処理実行を保証したいなら、`list()` で全部の値を取得するなどを考慮する |
||
| 186 | ```python |
||
| 187 | # generators.py |
||
| 188 | |||
| 189 | def multiplier(value): |
||
| 190 | for i in value: |
||
| 191 | yield i ** 2 |
||
| 192 | |||
| 193 | def multiplier_unlimit(): |
||
| 194 | num = 1 |
||
| 195 | while True: |
||
| 196 | yield num |
||
| 197 | num *= 2 |
||
| 198 | ``` |
||
| 199 | |||
| 200 | ```python |
||
| 201 | >>> from generators import multiplier |
||
| 202 | >>> |
||
| 203 | >>> gen1 = multiplier([1, 2, 3, 4, 4, 10]) |
||
| 204 | >>> gen1 |
||
| 205 | <generator object multiplier at 0x000001C345779630> |
||
| 206 | >>> for i in gen1: |
||
| 207 | ... print(i) |
||
| 208 | ... |
||
| 209 | 1 |
||
| 210 | 4 |
||
| 211 | 9 |
||
| 212 | 16 |
||
| 213 | 16 |
||
| 214 | 100 |
||
| 215 | >>> next(gen1) |
||
| 216 | Traceback (most recent call last): |
||
| 217 | File "<python-input-5>", line 1, in <module> |
||
| 218 | next(gen1) |
||
| 219 | ~~~~^^^^^^ |
||
| 220 | StopIteration |
||
| 221 | >>> |
||
| 222 | >>> gen1 = multiplier([1, 2, 3, 4, 4, 10]) |
||
| 223 | >>> next(gen1) |
||
| 224 | 1 |
||
| 225 | >>> next(gen1) |
||
| 226 | 4 |
||
| 227 | >>> next(gen1) |
||
| 228 | 9 |
||
| 229 | >>> next(gen1) |
||
| 230 | 16 |
||
| 231 | >>> next(gen1) |
||
| 232 | 16 |
||
| 233 | >>> next(gen1) |
||
| 234 | 100 |
||
| 235 | >>> next(gen1) |
||
| 236 | Traceback (most recent call last): |
||
| 237 | File "<python-input-13>", line 1, in <module> |
||
| 238 | next(gen1) |
||
| 239 | ~~~~^^^^^^ |
||
| 240 | StopIteration |
||
| 241 | ``` |
||
| 242 | ```python |
||
| 243 | >>> from generators import multiplier_unlimit |
||
| 244 | >>> |
||
| 245 | >>> gen2 = multiplier_unlimit() |
||
| 246 | >>> gen2.__next__() |
||
| 247 | 1 |
||
| 248 | >>> next(gen2) |
||
| 249 | 2 |
||
| 250 | >>> next(gen2) |
||
| 251 | 4 |
||
| 252 | >>> next(gen2) |
||
| 253 | 8 |
||
| 254 | >>> next(gen2) |
||
| 255 | 16 |
||
| 256 | >>> next(gen2) |
||
| 257 | 32 |
||
| 258 | >>> next(gen2) |
||
| 259 | 64 |
||
| 260 | >>> next(gen2) |
||
| 261 | 128 |
||
| 262 | >>> next(gen2) |
||
| 263 | 256 |
||
| 264 | >>> next(gen2) |
||
| 265 | 512 |
||
| 266 | >>> next(gen2) |
||
| 267 | 1024 |
||
| 268 | >>> next(gen2) |
||
| 269 | 2048 |
||
| 270 | >>> next(gen2) |
||
| 271 | 4096 |
||
| 272 | >>> next(gen2) |
||
| 273 | 8192 |
||
| 274 | >>> |
||
| 275 | ``` |
||
| 276 | |||
| 277 | ## 3.7 デコレーター |
||
| 278 | - 構文 |
||
| 279 | - `@デコレーター名` を関数、メソッド、クラス定義の前行に記載 |
||
| 280 | ```python |
||
| 281 | @my_decorator |
||
| 282 | def func(): |
||
| 283 | pass |
||
| 284 | ``` |
||
| 285 | - 関数デコレーター構文はシンタックスシュガーであり、前文は以下と同意 |
||
| 286 | ```python |
||
| 287 | def func(): |
||
| 288 | pass |
||
| 289 | func = my_decorator(func) |
||
| 290 | ``` |
||
| 291 | - つまり関数デコレーターは、関数を引数にして関数を返す関数であり、引数とする関数の処理を変更 (装飾、追加) するもの |
||
| 292 | - 関数への `retry` デコレーター試用 |
||
| 293 | ``` |
||
| 294 | pip install retrying |
||
| 295 | ``` |
||
| 296 | ```python |
||
| 297 | >>> import random |
||
| 298 | >>> from retrying import retry |
||
| 299 | >>> |
||
| 300 | >>> @retry |
||
| 301 | ... def my_func(): |
||
| 302 | ... if random.randint(0, 10) == 5: |
||
| 303 | ... print('it is 5') |
||
| 304 | ... else: |
||
| 305 | ... print('raise ValueError') |
||
| 306 | ... raise ValueError("it is not 5") |
||
| 307 | ... |
||
| 308 | >>> my_func() |
||
| 309 | raise ValueError |
||
| 310 | raise ValueError |
||
| 311 | raise ValueError |
||
| 312 | raise ValueError |
||
| 313 | raise ValueError |
||
| 314 | it is 5 |
||
| 315 | ``` |
||
| 316 | - 引数も使用できる (再度、`retry` デコレーターの例) |
||
| 317 | ```python |
||
| 318 | (略) |
||
| 319 | >>> @retry(stop_attempt_max_num=2) |
||
| 320 | ... def my_func(): |
||
| 321 | ... if random.randint(0, 10) == 5: |
||
| 322 | ... print('it is 5') |
||
| 323 | ... else: |
||
| 324 | ... print('raise ValueError') |
||
| 325 | ... raise ValueError("it is not 5") |
||
| 326 | (略) |
||
| 327 | # 2回までリトライする (3回目は例外をそのまま送出) |
||
| 328 | ``` |
||
| 329 | - クラスデコレーター (`dataclass` デコレーターの詳細は4章) |
||
| 330 | ```python |
||
| 331 | from dataclasses import dataclass |
||
| 332 | |||
| 333 | @dataclass |
||
| 334 | class User: |
||
| 335 | pass |
||
| 336 | |||
| 337 | @dataclass(frozen=True) |
||
| 338 | class User2: |
||
| 339 | pass |
||
| 340 | ``` |
||
| 341 | - メソッドデコレーター |
||
| 342 | ```python |
||
| 343 | class User: |
||
| 344 | |||
| 345 | @staticmethod |
||
| 346 | def func(): |
||
| 347 | pass |
||
| 348 | ``` |
||
| 349 | - 複数のデコレーター適用 |
||
| 350 | - 1行に1つのデコレーターを指定する |
||
| 351 | ```python |
||
| 352 | @my_decorator1 |
||
| 353 | @my_decorator2 |
||
| 354 | def func(): |
||
| 355 | pass |
||
| 356 | ``` |
||
| 357 | - 前文と等価の代入文 (下に書いたものから適用されていく) |
||
| 358 | ```python |
||
| 359 | def func(): |
||
| 360 | pass |
||
| 361 | |||
| 362 | func = my_decorator1(my_decorator2(func)) |
||
| 363 | ``` |
||
| 364 | - 関数デコレーターの自作 |
||
| 365 | - デコレート対象の関数を引数とするデコレーター関数を定義 |
||
| 366 | - デコレーター関数の中に、デコレート対象の関数の代わりに呼び出されるラッパー関数を定義 |
||
| 367 | - ラッパー関数で、デコレート対象の関数の処理へ変化をつける (前後に追加、元関数の結果を変更等) |
||
| 368 | - デコレーター関数は戻り値としてラッパー関数を返す |
||
| 369 | ```python |
||
| 370 | def my_decorator(func): |
||
| 371 | def wrap_func(): |
||
| 372 | func() |
||
| 373 | print(f'function: {func.__name__} called') |
||
| 374 | |||
| 375 | return wrap_func |
||
| 376 | ``` |
||
| 377 | ```python |
||
| 378 | >>> from decorators import my_decorator |
||
| 379 | >>> |
||
| 380 | >>> def sayhello(): |
||
| 381 | ... print("こんにちは") |
||
| 382 | ... |
||
| 383 | >>> sayhello |
||
| 384 | <function sayhello at 0x000002A3B2A94680> |
||
| 385 | >>> sayhello() |
||
| 386 | こんにちは |
||
| 387 | >>> sayhello = my_decorator(sayhello) |
||
| 388 | >>> sayhello |
||
| 389 | <function my_decorator.<locals>.wrap_func at 0x000002A3B2A94220> |
||
| 390 | >>> sayhello() |
||
| 391 | こんにちは |
||
| 392 | function: sayhello called |
||
| 393 | >>> |
||
| 394 | ``` |
||
| 395 | - デコレーターを使用すると関数が置き換わる (前コード `<function my_decorator.<locals>.wrap_func at 0x000002A3B2A94220>` の部分参考) |
||
| 396 | - 回避には `functools.wraps` デコレーターを、作成デコレーター関数内のラッパー関数へ指定する |