操作
第3章 Python の言語仕様¶
Python 実践レシピ (技術評論社)
3.2 with 文¶
- with 文へ渡すオブジェクトを context manager という
- 特殊メソッド
__enter__(),__exit__()を実装したクラスのインスタンス - 処理の流れは以下の通り
- with ブロック実行前に __enter__() が実行される
- with ブロック内の実行
- with ブロック終了後に __exit__() が実行される
- __enter__() で返した値は as で参照できる
- with ブロックで例外発生の場合は __exit__() の引数で例外情報受け取れる
- 特殊メソッド
-
@contextlib.contextmanager デコレーター
- ジェネレータ関数を書くと、context manager として使用できる
-
yieldの前が with ブロック前に、yieldより後が with ブロック後に実行される - yield で返す値は as 参照できる
- with 文での例外は、yield 箇所で再送出される
- その他 contextlib モジュールの context manager
-
contextlib.suppress(Exceptions): 指定した例外を無視 -
contextlib.redirect_stdout(new_target): 標準出力先を一時的に変更 -
contextlib.redirect_stderr(new_target): 標準エラー先を一時的に変更
-
3.3 関数の引数¶
- 位置引数
- キーワード引数 (仮引数名=値 で指定)
- 実引数に位置引数とキーワード引数を混在させる場合は、位置引数を先に指定
- デフォルト値付き引数 : 定義時に
仮引数名=値を指定 - 可変長位置引数 : 定義時に仮引数名にアスタリスクを前置する
*args- 受け取った引数はタプルとして扱う
- 可変長キーワード引数 : 定義時に仮引数名にアスタリスク2個を前置する
**kwargs- 受け取った引数は辞書として扱う
- 引数定義は次の順に行う
- 位置引数
- 可変位置引数
- デフォルト値付き引数 (呼び出しでキーワード指定したい場合、可変位置引数より後に定義の必要がある)
- 可変キーワード引数
- キーワード専用引数 :
*の後に定義することで、キーワード引数として指定しなければ呼び出せない制限を付けられる>>> def sample_func(param1, *, keyword1): ... print(f'{param1}, {keyword1}') ... >>> sample_func(1, keyword1=False) 1, False >>> samele_func(1, False) # エラー (略) - 位置専用引数 :
/の前に定義することで、位置引数として指定しなければ呼び出せない制限を付けられる>>> def add(x, y, /): ... return x + y ... >>> add(1, 2) 3 >>> add(x=1, y=2) # エラー (略) - デフォルト値付き引数の式は 関数が定義されるときにただ一度だけしか評価されない
>>> def sample_func(a, b, c=[]): ... c.append(a + b) ... return c ... >>> sample_func(1, 2) [3] >>> sample_func(3, 4) [3, 7] >>> sample_func(5, 6) [3, 7, 11]>>> def sample_func(a, b, c=None): ... if c is None ... c = [] ... c.append(a + b) ... return c ... >>> sample_func(1, 2) [3] >>> sample_func(3, 4) [7] >>> sample_func(5, 6) [11]
3.4 アンパック¶
- タプル、リスト、辞書から複数の値を変数へ展開する
>>> tp = (1, 2, 3) >>> a, b, c = tp # タプル >>> print(f'{a}, {b}, {c}') 1, 2, 3>>> lt = [1, 2, 3] >>> a, b, c = lt # リスト >>> print(f'{a}, {b}, {c}') 1, 2, 3>>> dic = {"a": 1, "b": 2, "c": 3} >>> k1, k2, k3 = dic # 辞書のキー >>> print(f'{k1}, {k2}, {k3}') a, b, c >>> v1, v2, v3 = dic.values() # 辞書の値 >>> print(f'{v1}, {v2}, {v3}') 1, 2, 3 - 数が合わないとエラー
>>> a, b = (1, 2, 3) # エラー (略) >>> a, b, c = (1, 2) # エラー (略) - ネストタプル、ネストリスト (例はタプルのみ)
>>> tp = (1, 2, (3, 4, 5)) >>> a, b, c = tp >>> print(f'{a}, {b}, {c}') 1, 2, (3, 4, 5) >>>a, b, (c, d, e) = tp >>> print(f'{a}, {b}, {c}, {d}, {e}') 1, 2, 3, 4, 5 - アスタリスクでのアンパック
>>> tp = (1, 2, 3, 4, 5) >>> a, b, *c = tp >>> print(f'{a}, {b}, {c}') 1, 2, [3, 4, 5] >>> a, *b, c = tp >>> print(f'{a}, {b}, {c}') 1, [2, 3, 4], 5- アスタリスクは一つの変数のみ
- 辞書のキーと値
- 辞書の
items()メソッドは、キーと値のタプルを返すので、アンパックして同時に扱える
- 辞書の
- タプル、リストのインデックスと値
- 組み込み関数
enumerate()はイテラブルオブジェクトを引数にとり、インデックスと値のタプルを返すので、アンパックして同時に扱える
- 組み込み関数
3.5 内包表記、ジェネレーター式¶
- リスト、辞書、集合、ジェネレーターで内包表記が使える
- リスト内包表記
[変数使用の処理 for 変数 in イテラブルオブジェクト] - リスト内包表記
[変数使用の処理 for 変数 in イテラブルオブジェクト if 条件式]>>> num_lt = [i**2 for i in range(10)] >>> num_lt [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> num_lt = [i**2 for i in range(10) if i % 2 == 0] >>> num_lt [0, 4, 16, 36, 64]
- リスト内包表記
- ネストした内包表記
>>> rows = ("A", "B", "C") >>> cols = ("1", "2", "3", "4") >>> mat = [f'{row}{col}' for row in rows for col in cols] >>> mat ['A1', 'A2', 'A3', 'A4', 'B1', 'B2', 'B3', 'B4', 'C1', 'C2', 'C3', 'C4']- 後ろに書いた内容が内側のループ (外側1:内側全部 が繰り返される)
- リスト以外の内包表記
- 集合
st = {i**2 for i in range(10)} - 辞書
dc = {i:i**2 for i in range(10)} - ジェネレーター
gt = (i**2 for i in range(10))- タプルではない (※タプルの内包表記は存在しない)
- 集合
3.6 ジェネレーター¶
- ほぼ関数だが、
return文の代わりにyield文を使用する -
yieldは厳密には 式であるが文として使用できる もの - Python は関数に yield が使用されていると、それを ジェネレーター として扱う
- yield 文で値が返されると、処理を一時中断して次の呼び出しを待つ
- この状態は 状態が保持されたイテラブルオブジェクト である
- さらに詳細には 計算途中の状態を保持し、必要なデータを1つずつ返すことができるイテレーター が返されている
-
イテレーターオブジェクト は次の値を返すメソッド
__next()__を持っており、ジェネレーターが返すのはイテレーターなので、このメソッドが使用でき、効果は次のようになる- (初回)「実行開始 (1回目の yield での値返却、中断)」
- (2回目以降)「処理の再開 (次の yield での値返却、中断)」
- (返却値が無い場合)「StopIteration例外の送出」
- 組み込み関数
next()にジェネレーターを渡すことで、__next()__呼び出しと同等の結果が得られる -
list()では、ジェネレーターの返却値すべてを取得したリストを得られる -
next()使用時の様にひとつの値を取得した際には、一時停止であることを念頭に置く必要がある- ジェネレーター内部のループが中断している場合など、処理済み、未処理の内容を把握しておく
- ジェネレーター定義の全処理実行を保証したいなら、
list()で全部の値を取得するなどを考慮する
# generators.py
def multiplier(value):
for i in value:
yield i ** 2
def multiplier_unlimit():
num = 1
while True:
yield num
num *= 2
>>> from generators import multiplier
>>>
>>> gen1 = multiplier([1, 2, 3, 4, 4, 10])
>>> gen1
<generator object multiplier at 0x000001C345779630>
>>> for i in gen1:
... print(i)
...
1
4
9
16
16
100
>>> next(gen1)
Traceback (most recent call last):
File "<python-input-5>", line 1, in <module>
next(gen1)
~~~~^^^^^^
StopIteration
>>>
>>> gen1 = multiplier([1, 2, 3, 4, 4, 10])
>>> next(gen1)
1
>>> next(gen1)
4
>>> next(gen1)
9
>>> next(gen1)
16
>>> next(gen1)
16
>>> next(gen1)
100
>>> next(gen1)
Traceback (most recent call last):
File "<python-input-13>", line 1, in <module>
next(gen1)
~~~~^^^^^^
StopIteration
>>> from generators import multiplier_unlimit
>>>
>>> gen2 = multiplier_unlimit()
>>> gen2.__next__()
1
>>> next(gen2)
2
>>> next(gen2)
4
>>> next(gen2)
8
>>> next(gen2)
16
>>> next(gen2)
32
>>> next(gen2)
64
>>> next(gen2)
128
>>> next(gen2)
256
>>> next(gen2)
512
>>> next(gen2)
1024
>>> next(gen2)
2048
>>> next(gen2)
4096
>>> next(gen2)
8192
>>>
3.7 デコレーター¶
- 構文
-
@デコレーター名を関数、メソッド、クラス定義の前行に記載
@my_decorator def func(): pass- 関数デコレーター構文はシンタックスシュガーであり、前文は以下と同意
def func(): pass func = my_decorator(func) -
- つまり関数デコレーターは、関数を引数にして関数を返す関数であり、引数とする関数の処理を変更 (装飾、追加) するもの
- 関数への
retryデコレーター試用
pip install retrying
>>> import random
>>> from retrying import retry
>>>
>>> @retry
... def my_func():
... if random.randint(0, 10) == 5:
... print('it is 5')
... else:
... print('raise ValueError')
... raise ValueError("it is not 5")
...
>>> my_func()
raise ValueError
raise ValueError
raise ValueError
raise ValueError
raise ValueError
it is 5
- 引数も使用できる (再度、
retryデコレーターの例)
(略)
>>> @retry(stop_attempt_max_num=2)
... def my_func():
... if random.randint(0, 10) == 5:
... print('it is 5')
... else:
... print('raise ValueError')
... raise ValueError("it is not 5")
(略)
# 2回までリトライする (3回目は例外をそのまま送出)
- クラスデコレーター (
dataclassデコレーターの詳細は4章)
from dataclasses import dataclass
@dataclass
class User:
pass
@dataclass(frozen=True)
class User2:
pass
- メソッドデコレーター
class User:
@staticmethod
def func():
pass
- 複数のデコレーター適用
- 1行に1つのデコレーターを指定する
@my_decorator1 @my_decorator2 def func(): pass- 前文と等価の代入文 (下に書いたものから適用されていく)
def func(): pass func = my_decorator1(my_decorator2(func)) - 関数デコレーターの自作
- デコレート対象の関数を引数とするデコレーター関数を定義
- デコレーター関数の中に、デコレート対象の関数の代わりに呼び出されるラッパー関数を定義
- ラッパー関数で、デコレート対象の関数の処理へ変化をつける (前後に追加、元関数の結果を変更等)
- デコレーター関数は戻り値としてラッパー関数を返す
def my_decorator(func): def wrap_func(): func() print(f'function: {func.__name__} called') return wrap_func>>> from decorators import my_decorator >>> >>> def sayhello(): ... print("こんにちは") ... >>> sayhello <function sayhello at 0x000002A3B2A94680> >>> sayhello() こんにちは >>> sayhello = my_decorator(sayhello) >>> sayhello <function my_decorator.<locals>.wrap_func at 0x000002A3B2A94220> >>> sayhello() こんにちは function: sayhello called >>>- デコレーターを使用すると関数が置き換わる (前コード
<function my_decorator.<locals>.wrap_func at 0x000002A3B2A94220>の部分参考)- 回避には
functools.wrapsデコレーターを、作成デコレーター関数内のラッパー関数へ指定する
- 回避には
Tatsuya ISHIGAKI さんが6ヶ月前に更新 · 1件の履歴