プロジェクト

全般

プロフィール

操作

第3章 Python の言語仕様

Python 実践レシピ (技術評論社)

3.2 with 文

  • with 文へ渡すオブジェクトを context manager という
    • 特殊メソッド __enter__(), __exit__() を実装したクラスのインスタンス
    • 処理の流れは以下の通り
      1. with ブロック実行前に __enter__() が実行される
      2. with ブロック内の実行
      3. 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
    • 受け取った引数は辞書として扱う
  • 引数定義は次の順に行う
    1. 位置引数
    2. 可変位置引数
    3. デフォルト値付き引数 (呼び出しでキーワード指定したい場合、可変位置引数より後に定義の必要がある)
    4. 可変キーワード引数
  • キーワード専用引数 : * の後に定義することで、キーワード引数として指定しなければ呼び出せない制限を付けられる
    >>> 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件の履歴