プロジェクト

全般

プロフィール

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` デコレーターを、作成デコレーター関数内のラッパー関数へ指定する