パターンマッチング。今Pythonで

ねえ!



パターンマッチングがついに3番目のPythonのアニバーサリーマイナーにもたらされました。コンセプト自体はほとんど新しいとは言えません。新しい世代(Rust、Golang)とすでに0x18を超えている言語(Java)の両方で、すでに多くの言語で実装されています。





パターンマッチングは、Pythonプログラミング言語の作者であり「寛大な生涯独裁者」であるGuido vanRossumによって発表されました



私の名前はDenisKaishevで、MiddlePython開発者コースのコードレビュー担当者です この投稿では、Pythonにパターンマッチングがある理由とその操作方法について説明します。



構文的には、パターンマッチングは、他の多くの言語と基本的に同じです。



match_expr:
    | star_named_expression ',' star_named_expressions?
    | named_expression
match_stmt: "match" match_expr ':' NEWLINE INDENT case_block+ DEDENT
case_block: "case" patterns [guard] ':' block
guard: 'if' named_expression
patterns: value_pattern ',' [values_pattern] | pattern
pattern: walrus_pattern | or_pattern
walrus_pattern: NAME ':=' or_pattern
or_pattern: '|'.closed_pattern+
closed_pattern:
    | capture_pattern
    | literal_pattern
    | constant_pattern
    | group_pattern
    | sequence_pattern
    | mapping_pattern
    | class_pattern
capture_pattern: NAME !('.' | '(' | '=')
literal_pattern:
    | signed_number !('+' | '-')
    | signed_number '+' NUMBER
    | signed_number '-' NUMBER
    | strings
    | 'None'
    | 'True'
    | 'False'
constant_pattern: attr !('.' | '(' | '=')
group_pattern: '(' patterns ')'
sequence_pattern: '[' [values_pattern] ']' | '(' ')'
mapping_pattern: '{' items_pattern? '}'
class_pattern:
    | name_or_attr '(' ')'
    | name_or_attr '(' ','.pattern+ ','? ')'
    | name_or_attr '(' ','.keyword_pattern+ ','? ')'
    | name_or_attr '(' ','.pattern+ ',' ','.keyword_pattern+ ','? ')'
signed_number: NUMBER | '-' NUMBER
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME
values_pattern: ','.value_pattern+ ','?
items_pattern: ','.key_value_pattern+ ','?
keyword_pattern: NAME '=' or_pattern
value_pattern: '*' capture_pattern | pattern
key_value_pattern:
    | (literal_pattern | constant_pattern) ':' or_pattern
    | '**' capture_pattern

      
      





複雑で紛らわしいように見えるかもしれませんが、実際には、すべて次のようになります。



match some_expression:
    case pattern_1:
        ...
    case pattern_2:
        ...

      
      





それははるかに明確で、目にはより心地よいように見えます。



テンプレート自体はいくつかのグループに分けられます。



  • リテラルパターン;
  • キャプチャパターン;
  • ワイルドカードパターン;
  • 定数値パターン;
  • シーケンスパターン;
  • マッピングパターン;
  • クラスパターン。


それぞれについて少しお話しします。



リテラルパターン



リテラルパターンは、その名前が示すように、一連の値、つまり文字列、数値、ブール値、およびNULLなしの照合を含み ますメソッドが使用されている



よう です。 string == 'string'



__eq__







match number:
    case 42:
        print('answer')
    case 43:
        print('not answer')

      
      





キャプチャパターン



キャプチャテンプレートを使用すると、テンプレートで指定された名前で変数をバインドし、ローカルスコープ内でその名前を使用できます。



match greeting:
    case "":
        print('Hello my friend')
    case name:
        print(f'Hello  {name}')
      
      







ワイルドカードパターン



一致するオプションが多すぎる場合は _



、を使用できます 。これは特定のデフォルト値であり、構造内のすべての要素に一致します。 match






match number:
    case 42:
        print("Its’s forty two")
    case _:
        print("I don’t know, what it is")
      
      





定数値パターン



定数を使用する場合は、列挙などの点線の名前を使用する必要があります。そうしないと、キャプチャパターンが機能します。



OK = 200
CONFLICT = 409

response = {'status': 409, 'msg': 'database error'}
match response['status'], response['msg']:
    case OK, ok_msg:
        print('handler 200')
    case CONFLICT, err_msg:
        print('handler 409')
    case _:
        print('idk this status')
      
      





そして、期待される結果は最も明白ではありません。



シーケンスパターン



それはあなたがからリスト、タプル、およびその他のオブジェクトを比較することができます collections.abc.Sequence



を除いて str



bytes



bytearray







answer = [42]
match answer:
    case []:   
        print('i do not find answer')
    case [x]:
        print('asnwer is 42')
    case [x, *_]:
        print('i find more than one answers')
      
      





これでlen()



、メソッドが呼び出されるため、リスト内のアイテムの数を確認するために 毎回呼び出す必要はありません __len__







マッピングパターン



このグループは前のグループと少し似ていますが、ここでのみ辞書、正確にはタイプのオブジェクトを照合しています collections.abc.Mapping



それらは互いに非常にうまく組み合わせることができます。



args = (1, 2)
kwargs = {'kwarg': 'kwarg', 'one_more_kwarg': 'one_more_kwarg'}

def match_something(*args, **kwargs):
    match (args, kwargs):
        case (arg1, arg2), {'kwarg': kwarg}:
            print('i find positional args and one keyword args')
        case (arg1, arg2), {'kwarg': kwarg, 'one_more_kwarg': one_more_kwarg}:
            print('i find a few keyword args')
        case _:
            print('i cannot match anything')

match_something(*args, **kwargs)
      
      





そして、すべてがうまくいくでしょうが、機能があります。このパターンは、このキーの辞書への入力を保証しますが、辞書の長さは重要ではありません。だから私は位置引数を見つけ、1つのキーワード引数が画面に表示されます



クラスパターン



ユーザー定義のデータ型の場合、構文はオブジェクトの初期化に似ています。



これは、データクラスの例でどのように見えるかです。



from dataclasses import dataclass

@dataclass
class Coordinate:
    x: int
    y: int
    z: int

coordinate = Coordinate(1, 2, 3)
match coordinate:
    case Coordinate(0, 0, 0):
        print('Zero point')
    case _:
        print('Another point')

      
      





if



、またはいわゆるを 使用することもでき guard



ます。条件がfalseの場合、パターンマッチングは続行されます。パターンが最初に一致し、その後で条件がチェックされることに注意してください。



case Coordinate(x, y, z) if z == 0:
    print('Point in the plane XY')
      
      





クラスを直接使用する場合は、__match_args__



位置引数が必要な属性 が必要です(namedtupleおよびdataclassesの場合、 __match_args__



自動的に生成されます)。



class Coordinate:
    __match_args__ = ['x', 'y', 'z']

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

oordinate = oordinate(1, 2, 3)
match oordinate:
    case oordinate(0, 0, 0):
        print('Zero oordinate')
    case oordinate(x, y, z) if z == 0:
        print('oordinate in the plane Z')
    case _:
        print('Another oordinate')

      
      





それ以外の場合は、TypeError例外が スローされます。Coordinate()は0個の位置サブパターンを受け入れます(3個指定)



収益は何ですか?



実際、最近のものと一緒に別のシンタックスシュガーのように見え walrus operator



ます。実装は、現状では、ステートメントブロックmatch



を同等の構造 if/else



、つまりバイトコードに変換し ます。これは同じ効果があります。





Python用のFlaskWebフレームワークの作成者であるArminRonacherは、パターンマッチングの現在の状態を非常に簡潔に説明しました



はい、議論するのは難しいです。コードはif/else



、スクリーンタワーの3分の1よりもいくらかクリーン になります。しかし、それをすごい効果を生み出すものと呼ぶこともできません。導入されたことは悪くありません。どこでも使用できると便利ですが、どこでも使用できるとは限りません。どういうわけか、この目新しさの主なことは、それをやり過ぎないことです。すべてのプロジェクトを3.10に更新し、すべてを書き直すために、より速く実行しないことです。理由は次のとおりです。

今は決してないよりはましです。多くの場合、現在よりも優れていることはありませんが。


使ってくれませんか?もしそうなら、どこ?



All Articles