M.Hiroi's Home Page

Python3 Programming

お気楽 Python3 プログラミング超入門

Copyright (C) 2022-2026 Makoto Hiroi
All rights reserved.

●ジェネレータ (2)

>>> def foo():
...     a = yield "foo"
...     print("foo: ", a)
...     b = yield "bar"
...     print("foo: ", b)
...     c = yield "baz"
...     print("foo: ", c)
...
>>> g = foo()
>>> print(g.send(None))
foo
>>> print(g.send("foo1"))
foo:  foo1
bar
>>> print(g.send("foo2"))
foo:  foo2
baz
>>> print(g.send("foo3"))
foo:  foo3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

>>> for x in foo(): print(x)
...
foo
foo:  None
bar
foo:  None
baz
foo:  None
リスト : 複数のコルーチンを呼び出す

def make_coroutine(code):
    while True:
        print(code, end="")
        yield None

def test1(n):
    a = make_coroutine("h")
    b = make_coroutine("e")
    c = make_coroutine("y")
    d = make_coroutine("!")
    e = make_coroutine(" ")
    for _ in range(n):
        for g in [a, b, c, d, e]: next(g)
    for g in [a, b, c, d, e]: g.close()
>>> test1(5)
hey! hey! hey! hey! hey! >>>
>>> test1(10)
hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! >>>
リスト : 子コルーチンから子コルーチンを呼び出す

def make_coroutine2(code, g):
    while True:
        print(code, end="")
        if g: next(g)
        yield None

def test2(n):
    e = make_coroutine2(" ", None)
    d = make_coroutine2("!", e)
    c = make_coroutine2("y", d)
    b = make_coroutine2("e", c)
    a = make_coroutine2("h", b)
    for _ in range(n): next(a)
    for g in [a, b, c, d, e]: g.close()
>>> test2(5)
hey! hey! hey! hey! hey! >>>
>>> test2(15)
hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! hey! hey!
リスト : エラトステネスの篩

# n から始まる整数列
def integers(n):
    while True:
        yield n
        n += 1

# フィルター
def stream_filter(pred, s):
    while True:
        x = next(s)
        if pred(x): yield x

# n 個の素数を求める
def sieve(n):
    # 変数 x の値をクロージャ内に保持する
    def make_pred(x):
        return lambda y: y % x != 0
    #
    nums = integers(2)
    ps = []
    for _ in range(n):
        x = next(nums)
        ps.append(x)
        nums = stream_filter(make_pred(x), nums)
    return ps

# 再帰にすると lambda だけでも動作する
def sieve1(n, nums, ps):
    if n == 0:
        return ps
    else:
        x = next(nums)
        ps.append(x)
        return sieve1(n - 1, stream_filter(lambda y: y % x != 0, nums), ps)
>>> sieve(25)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
 83, 89, 97]
>>> sieve(100)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167,
 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263,
 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]

>>> sieve1(25, integers(2), [])
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
 83, 89, 97]
>>> sieve1(100, integers(2), [])
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 
 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263,
 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
リスト :  簡単なマルチプロセス

# 中断中のプロセスを格納するキュー
proc_queue = []

# キューに追加
def enqueue(x):
    proc_queue.append(x)

# キューからデータを取り出す
def dequeue():
    x = proc_queue[0]
    del proc_queue[0]
    return x

# キューは空か?
def isempty():
    return len(proc_queue) == 0

# プロセスの生成
def fork(fn):
    g = fn()
    g.send(None)
    proc_queue.append(g)

# メインプロセス
def main_process(*args):
    # プロセスの登録
    for fn in args: fork(fn)
    # 実行
    while not isempty():
        p = dequeue()
        if p.send(False):
            enqueue(p)
        else:
            p.close()

# 子プロセス
def mtest0(name, n):
    while n > 0:
        print(name, n)
        yield True
        n -= 1
    yield False
>>> main_process(lambda : mtest0("foo", 5), lambda : mtest0("bar", 7))
foo 5
bar 7
foo 4
bar 6
foo 3
bar 5
foo 2
bar 4
foo 1
bar 3
bar 2
bar 1
>>> main_process(lambda : mtest0("foo", 5), lambda : mtest0("bar", 7),
 lambda : mtest0("oops", 6))
foo 5
bar 7
oops 6
foo 4
bar 6
oops 5
foo 3
bar 5
oops 4
foo 2
bar 4
oops 3
foo 1
bar 3
oops 2
bar 2
oops 1
bar 1

初版 2024 年 4 月 28 日

●asyncio

●非同期ジェネレータ


初版 2024 年 5 月 3, 5 日

●仮想環境とパッケージ


初版 2026 年 2 月 2 日

●パターンマッチング

「パターンマッチング (pattern matching)」は Python 3.10 で追加された新しい機能です。パターンマッチングは関数型言語では基本的な機能の一つで、データとパターンを照合 (マッチング) し、マッチングに成功した節を選択して実行します。値の比較だけでなく、データ構造や型の判定、値の取り出しなどを直感的に記述することができます。

●match 文

Python のパターンマッチングは match 文で行います。match 文の構文を以下に示します。

match expr:
    case pattern_1:
        処理_1
        ...
    case pattern_2:
        処理_2
        ...
    ...
    case _:
        デフォルトの処理

match の後ろに評価したい式 expr を指定します。case の後ろにマッチさせるパターン (pattern) を記述します。match 文は expr の評価結果とパターンを照合し、マッチングする節を選択して処理を実行します。

たとえば、expr の評価結果と pattern_1 がマッチングした場合、処理_1 を実行します。マッチングしない場合は次のパターンを調べます。_ はワイルドカードパターンと呼ばれ、他のどのケースにも当てはまらない場合に一致します。なお、一度マッチング節が選択された場合、それ以降の節は選択されません。

簡単な例を示しましょう。match 文を使って階乗を求める関数 fact を定義すると次のようになります。

>>> def fact(n):
...     match n:
...         case 0: return 1
...         case m: return m * fact(m - 1)
...
>>> for x in range(14): print(fact(x))
...
1
1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600
6227020800

パターンが定数の場合、同じ値とマッチングします。これを「リテラルパターン」といいます。最初の定義はパターンが 0 なので、n が 0 の場合にマッチングします。これは if n == 0: return 1 と同じ処理です。

パターンが「変数」の場合はどんな値とでもマッチングします。この場合、変数にはその値が代入されます。これを「キャプチャパターン」といいます。したがって、n が 0 以外の場合は 2 番目のパターンと一致し、変数 m の値は n になります。ここで再帰呼び出しが行われます。

パターンマッチングを使うときは、マッチング節を定義する順番に気をつけてください。fact の場合、最初にキャプチャパターンを定義すると、引数が 0 の場合でもマッチングするので、パターン 0 のマッチング節が実行されることはありません。特定のパターンから定義するように注意してください。

ところで、変数 m は n と同じ値なので、ワイルドカードパターン ( _ ) を使って次のように定義してもかまいません。

case _: return n * fact(n - 1)

_ はどんな値ともマッチングしますが、マッチングした値は捨てられます。

●シーケンスパターン

パターンはタプルやリストを使って記述することができます。これを「シーケンスパターン」といいます。タプルやリストの要素にパターンを記述することができるので、いろいろなパターンを表現することができます。簡単な例を示しましょう。

リスト : シーケンスパターン (1)

def rgb(r, g, b):
    match (r, g, b):
        case (False, False, False): return 'black'
        case (False, False, True):  return 'blue'
        case (False, True,  False): return 'green'
        case (False, True,  True):  return 'cyan'
        case (True,  False, False): return 'red'
        case (True,  False, True):  return 'magenta'
        case (True,  True,  False): return 'yellow'
        case (True,  True,  True):  return 'white'
>>> rgb(True, True, True)
'white'
>>> rgb(True, True, False)
'yellow'
>>> rgb(True, False, False)
'red'
>>> rgb(False, False, False)
'black'
>>> rgb(False, True, False)
'green'
>>> rgb(False, True, True)
'cyan'

関数 rgb は三原色 (赤、緑、青) の有無から 8 種類の色を文字列で返します。引数 r, g, b をタプルに格納し、match 文でその値とパターンを照合します。パターンはタプルで、各要素は True と False のリテラルパターンになります。

もう一つ簡単な例を示しましょう。FizzBuzz 問題をパターンマッチングを使って解いてみましょう。FizzBuzz 問題は 1 から 100 までの値を表示するとき、3 の倍数のときは Fizz を、5 の倍数ときは Buzz を表示するというものです。プログラムは次のようになります。

リスト : FIzzBuzz 問題

def fizzbuzz():
    for n in range(1, 101):
        match (n % 3, n % 5):
            case (0, 0): print('FizzBuzz', end=' ')
            case (0, _): print('Fizz', end=' ')
            case (_, 0): print('Buzz', end=' ')
            case _: print(n, end=' ')

n % 3 と n % 5 の結果をタプルに格納し、それをパターンとマッチングします。(0, 0) と一致すれば 3 と 5 の両方で割り切れるので 'FizzBuzz' を出力します。(0, _) と一致すれば 3 で割り切れるので 'Fizz' を出力します。(_, 0) と一致すれば 5 で割り切れるので 'Buzz' を出力します。それ以外の場合は、数値をそのまま出力します。

>>> fizzbuzz()
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz
26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz 
Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 
76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz

タプルと同様に、リストもパターンとマッチングすることができます。よく使われるパターンを次に示します。

(1) [x]            要素が 1 つのリストとマッチング
(2) [x, y]         要素が 2 つのリストとマッチング
(3) [x, *xs]       要素が 1 つ以上あるリストとマッチング
(4) [x1, x2, *xs}  要素が 2 つ以上あるリストとマッチング
(5) [x1, x1, *xs]  エラー

変数の前に * を付けると、残りを要素をリストに格納して変数に代入します。(5) のように、パターンの中に同名の変数を使うことはできません。この場合、[x1, x2, *xs] とマッチングさせてから x1 と x2 が等しいかチェックします。このような場合、キーワード if を使うと便利です。if の構文を示します。

case pattern if 条件式: ...

このようなマッチング節を「ガード付き節」といいます。パターンとの照合に成功して、かつ if の条件式が真を返す場合に限り、その節が選択されます。たとえば、if を使って (5) を実現すると次のようになります。

case [x1, x2, *xs] if x1 == x2: 処理1
case [x1, x2, *xs] if x1 < x2:  処理2
case [x1, x2, *xs] 処理3

パターンとの照合に成功して、x1 == x2 の場合は処理 1 が実行されます。x1 < x2 の場合は処理 2 が実行され、それ以外の場合は処理 3 が実行されます。

簡単な使用例を示します。

>>> def foo(xs):
...     match xs:
...         case ['one', a, *zs]: print('one', a, zs)
...         case ['two', a, b, *zs]: print('two', a, b, zs)
...         case _: print('oops!')
...
>>> foo(['one', 1])
one 1 []
>>> foo(['one', 1, 2])
one 1 [2]
>>> foo(['one', 1, 2, 3])
one 1 [2, 3]
>>> foo(['one'])
oops!

>>> foo(['two', 1, 2])
two 1 2 []
>>> foo(['two', 1, 2, 3, 4])
two 1 2 [3, 4]
>>> foo(['two', 1])
oops!

>>> foo(['etc', 1, 2, 3, 4])
oops!

また、次に示すようにもっと複雑なリストもパターンで表すことができます。

(1) [(x, y), *xs]      要素がタプルのリストとマッチング
    ex) [(1, 2), (3, 4), (5, 6)] => x = 1, y = 2, xs = [(3, 4), (5, 6)]

(2) [[x, *xs)], *ys]   要素がリストのリストとマッチング
    ex) [[1, 2, 3], [4, 5], [6]] => x = 1, xs = [2, 3], ys = [[4, 5], [6]]

簡単な使用例を示します。

>>> def bar(xs):
...     match xs:
...         case [(a, b), *ys]: print(a, b, ys)
...         case _: print('oops')
...
>>> bar([(1, 2), (3, 4), (5, 6)])
1 2 [(3, 4), (5, 6)]
>>> bar([(1, 2)])
1 2 []
>>> bar([(1, 2, 3)])
oops

>>> def baz(xs):
...     match xs:
...         case [[a, *b], *ys]: print(a, b, ys)
...         case _: print('oops')
...
>>> baz([[1, 2], [3, 4], [5, 6]])
1 [2] [[3, 4], [5, 6]]
>>> baz([[11, 12, 13], [3, 4], [5, 6]])
11 [12, 13] [[3, 4], [5, 6]]
>>> baz([[11], [3, 4], [5, 6]])
11 [] [[3, 4], [5, 6]]
>>> baz([[], [3, 4], [5, 6]])
oops

●OR パターンと AS パターン

OR パターンは「複数のパターンのうち、どれか 1 つでも一致すればマッチとする」という論理和を表現する機能です。OR パターンはパターンを縦棒 (|) でつないで表します。

case patternA | ... | patternZ : 処理 ...

patternA から patternZ までのどれか一つでも一致すれば、その節が選択されて「処理」が実行されます。簡単な例として、フィボナッチ数を求める関数 fibo を OR パターンを使ってプログラムすると、次のようになります。

>>> def fibo(n):
...     match n:
...         case 0 | 1: return n
...         case _: return fibo(n - 1) + fibo(n - 2)
...
>>> for x in range(10, 20): print(fibo(x))
...
55
89
144
233
377
610
987
1597
2584
4181

AS パターンは、「パターンにマッチしたデータ全体、またはその一部に別名をつけ、変数としてキャプチャする」ための機能です。

case パターン as 変数名: 処理 ...

たとえば、[y, *ys] as xs と [1, 2, 3] をマッチングさせると、次のようになります。

xs => [1, 2, 3]
y  => 1
ys => [2, 3]

このように、パターン [y, *ys] とマッチングした場合、変数 xs の値は分解する前の [1, 2, 3] になります。

OR パターンと AS パターンを組み合わせると、「OR パターンのどれとマッチしたか」を変数に取り出すことができます。

case patternA |  ... | patternZ as 変数: 処理 ...

簡単な実行例を示します。

>>> def foo(xs):
...     match xs[0]:
...         case 'foo' | 'bar' | 'baz' as x1: print(x1)
...         case _: print('oops!')
...
>>> foo(['foo', 'bar', 'baz'])
foo
>>> foo(['bar', 'baz'])
bar
>>> foo(['baz'])
baz
>>> foo(['oops'])
oops!

●マッピングパターン

マッピングパターンは、辞書の中に「特定のキーが存在するか」「そのキーの値が指定したパターンに一致するか」を判定する機能です。指定したキーさえ含まれていれば、他に余分なキーが含まれていてもマッチする、というところがポイントです。

case {key1: pattern1, key2: pattern2, ...}: 処理 ...

複数のキーを指定する場合、すべてのキーが存在し、その値がパターンと一致したとき、その節が選択されて「処理」が実行されます。簡単な例を示しましょう。

>>> def foo(zs):
...     match zs:
...         case {'foo': (a, b)}: print('foo', a, b)
...         case _: print('oops!')
...
>>> foo({'foo': (1, 2), 'bar': 200})
foo 1 2
>>> foo({'foo': 100, 'bar': 200})
oops!
>>> foo({'bar': 200})
oops!

●クラスパターン

クラスパターンは、オブジェクトの「データ型 (クラス)」を判定し、同時にそのオブジェクトが持つ「フィールド (インスタンス変数)」の値を取り出して変数に代入 (束縛) するための機能です。

case クラス(フィールド = pattern, ...): 処理 ...

照合するオブジェクトの型がクラスで、フィールドの値が pattern と一致するとき、その節が選択されて処理が実行されます。簡単な例を示しましょう。

>>> class Foo:
...     def __init__(self, foo, bar):
...         self.foo = foo
...         self.bar = bar
...
>>> a = Foo(1, 2)
>>> a
<__main__.Foo object at 0x7ae6811ff440>
>>> a.foo
1
>>> a.bar
2
>>> match a:
...     case Foo(foo = b): print(b)
...     case _: print('oops')
...
1
>>> match a:
...     case Foo(baz = b): print(b)
...     case _: print('oops')
...
oops

Python の組み込み型 (str, int, list など) もクラスなので、クラスパターンで型チェックを行うことができます。この場合、パターンは 型(), 型(変数) のように記述します。たとえば、int(num) とか float(num) のようになります。データ型だけチェックする場合は int(), float() とします。

簡単な例を示しましょう。

>>> def check_type(n):
...     match n:
...         case int(): print('int')
...         case float(): print('float')
...         case str(): print('string')
...         case _: print('oops!')
...
>>> check_type(123)
int
>>> check_type(1.2345)
float
>>> check_type('hello')
string
>>> check_type((1,2,3))
oops!

●データクラス

Python のデータクラス (dataclass) とは、Python 3.7 から導入された、主としてデータを保持するためのクラスを簡単に定義できる機能 (デコレータ) です。これを使うことで、通常のクラス定義で必要な特殊メソッド (__init__, __repr__, __eq__ など) を自動生成することができます。

データクラスを使うときは、標準ライブラリ dataclasses から dataclass をインポートしてください。そして、デコレータ @dataclass を使ってデータクラスを定義します。データクラスの構文を以下に示します。

from dataclasses import dataclass

@dataclass
class クラス名:
    フィールドA: 型アノテーション = default    # default は省略可
    フィールドB: 型アノテーション = default
     ...
    フィールドZ: 型アノテーション = default

フィールドに型アノテーションを記述するだけで、定義した順番で引数を受け取るコンストラクタや、文字列表現のメソッドなどが自動的に生成されます。型アノテーションは、変数や関数の引数・戻り値に対して、期待されるデータ型を明示する機能です。詳しい説明は拙作のページ 型ヒント をお読みくださいませ。

簡単な例を示しましょう。

>>> from dataclasses import dataclass
>>> @dataclass
... class Foo:
...     foo: int
...     bar: float
...     baz: str

>>> a = Foo(1, 1.234, 'hello')
>>> a
Foo(foo=1, bar=1.234, baz='hello')

>>> b = Foo(foo=123, bar=123.45, baz='world')
>>> b
Foo(foo=123, bar=123.45, baz='world')

コンストラクタの引数は、キーワード引数で指定してもかまいません。デフォルト値が指定されたフィールドは、コンストラクタの引数から除外することができます。ただし、デフォルト値を指定しないフィールドを、デフォルト値を指定したフィールドの後ろに定義すると、エラーが送出されるので注意してください。

特定のフィールドの値をコンストラクタの引数からは受け取らず、初期値の計算や後からの代入専用にしたい場合は、dataclasses.field(init=False) を使います。簡単な例を示しましょう。

>>> from dataclasses import field
>>> @dataclass
... class Foo1:
...     foo: int
...     bar: float = field(default=0.0, init=False)
...     baz: str
...
>>> c = Foo1(1, 'oops')
>>> c
Foo1(foo=1, bar=0.0, baz='oops')

初期化の直後に独自の処理を実行したい場合はメソッド __post_init__ を定義します。

>>> @dataclass
... class Foo2:
...     foo: int
...     bar: float = field(init=False)
...     baz: str
...     def __post_init__(self):
...         self.bar = self.foo * 1.2345
...
>>> d = Foo2(10, 'oops!!')
>>> d
Foo2(foo=10, bar=12.344999999999999, baz='oops!!')

リストや辞書をデフォルト値に指定する場合、[] や {} を書くとエラーになります。代わりに field(default_factory=...) を使用します。

フィールド : 型アノテーション = field(default_factory=list)   # リスト
フィールド : 型アノテーション = field(default_factory=dict)   # 辞書

簡単な例を示します。

>>> @dataclass
... class Bar:
...     xs: list[int] = field(default_factory=list)
...     ys: dict[str, int] = field(default_factory=dict)
...
>>> e = Bar()
>>> e
Bar(xs=[], ys={})

データクラスの場合、引数の位置でパターンマッチングを行うことができます。

case クラス名(pattern1, pattern2, ...): 処理 ...

普通のクラスのように、フィールド = pattern で指定してもかまいません。簡単な例を示します。

>>> @dataclass
... class Baz:
...     foo: int
...     bar: float
...
>>> f = Baz(100, 1.23)
>>> f
Baz(foo=100, bar=1.23)

>>> match f:
...     case Baz(a, b): print(a, b)
...     case _: print('oops!')
...
100 1.23

@dataclass の引数に frozen=True を指定すると、インスタンスを生成したあと、フィールドの値を書き換えることができなくなります。immutable なデータ構造を使用するときに便利です。

このほかにも、パターンマッチングやデータクラスには便利な機能があります。詳細は Python のドキュメントをお読みください。


初版 2026 年 9 月 16 日