【Python】スター式(*)を使ったアンパックの基本と応用を徹底解説
はじめに
Pythonのアンパック(unpacking)には、ひとつの基本的な制限があります。それは、アンパック対象となるシーケンスの長さを事前に把握しておく必要があるという点です。
通常のアンパックで起こる問題
まず、降順にソートした数値リストを用意します。
random_numbers = [0, 1, 5, 9, 17, 12, 7, 10, 3, 2]
random_numbers_descending = sorted(random_numbers, reverse=True)
print(f"Output \n*** {random_numbers_descending}")
出力結果
*** [17, 12, 10, 9, 7, 5, 3, 2, 1, 0]
このリストから「最大値」と「2番目に大きい値」を取り出そうとして、次のように書くとどうなるでしょうか。
print(f"Output \n*** Getting the largest and second largest") largest, second_largest = random_numbers_descending
出力結果
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in
1 print(f"Output \n*** Getting the largest and second largest")
----> 2 largest, second_largest = random_numbers_descending
ValueError: too many values to unpack (expected 2)
リストには10個の要素があるのに、受け取る変数は2つしかないため、「too many values to unpack(アンパックする値が多すぎる)」というValueErrorが発生します。通常のアンパックでは、左辺の変数の数と右側のシーケンスの要素数が完全に一致していなければならないのです。
インデックスとスライスによる回避策とその課題
Pythonでは従来、このような場面でインデックスやスライスに頼ることがよくありました。たとえば、最大値・2番目に大きい値・残りの数値をそれぞれ取り出すには、次のように書きます。
largest = random_numbers_descending[0]
print(f"Output \n*** Getting the largest - {largest}")
出力結果
*** Getting the largest - 17
second_largest = random_numbers_descending[1]
print(f"Output \n*** Getting the second largest - {second_largest}")
出力結果
*** Getting the second largest - 12
rest_of_numbers = random_numbers_descending[2:]
print(f"Output \n*** Getting the rest of numbers - {rest_of_numbers}")
出力結果
*** Getting the rest of numbers - [10, 9, 7, 5, 3, 2, 1, 0]
確かにこれは動作しますが、インデックスやスライスの指定が並ぶとコードが視覚的にノイズだらけになり、読みにくくなります。さらに実務面では、この方法でシーケンスの要素をいくつもの部分集合に分割すると、インデックスの指定ミスなどのバグが発生しやすいという問題があります。
スター式(catch-allアンパック)による解決
そこで役立つのが、Pythonがサポートするスター式(starred expression)によるcatch-allアンパックです。
スター構文を使うと、アンパック代入の一部が、他のパターンにマッチしなかったすべての値を受け取るようになります。
largest,second_largest, *rest_of_numbers = random_numbers_descending
print(f"Output \n largest: {largest} \n second_largest:{second_largest} \n rest_of_numbers:{rest_of_numbers}")
出力結果
largest: 17 second_largest:12 rest_of_numbers:[10, 9, 7, 5, 3, 2, 1, 0]
いかがでしょうか。たった1行の、しかも読みやすいコードで同じ結果が得られました。
スター式はどの位置にも置ける
スター式はパターン内の任意の位置に記述できるため、特定のスライスだけを取り出したいあらゆる場面でcatch-allアンパックの恩恵を受けられます。たとえば、先頭と末尾の値を取り出し、残りをまとめて受け取ることもできます。
*rest_of_numbers, second_smallest, smallest = random_numbers_descending
print(f"Output \n rest_of_numbers:{rest_of_numbers} \n second_smallest: {second_smallest} \n smallest:{smallest}")
出力結果
rest_of_numbers:[17, 12, 10, 9, 7, 5, 3, 2] second_smallest: 1 smallest:0
スター式使用時の注意点
1. 必須部分が最低1つ必要
スター式を含むアンパック代入では、少なくとも1つの必須部分が必要です。これがないとSyntaxErrorになります。つまり、catch-all式を単独で使うことはできません。
*rest_of_numbers = random_numbers_descending
出力結果
File "", line 1
*rest_of_numbers = random_numbers_descending
^
SyntaxError: starred assignment target must be in a list or tuple
2. 同一レベルで複数のスター式は使えない
また、単一レベルのアンパックパターンの中に、複数のcatch-all式を含めることもできません。これも重要な注意点です。
*rest_of_numbers, *more_smallest, smallest = random_numbers_descending
出力結果
File "", line 1
*rest_of_numbers, *more_smallest, smallest = random_numbers_descending
^
SyntaxError: two starred expressions in assignment
応用:ネストした構造での複数スター式
ただし、アンパックされる多層構造の異なる部分に対するcatch-allであれば、1つのアンパック代入文の中に複数のスター式を含めることが可能です。
player_grandslame_and_atptitles = {
'Federer': (20, 103),
'Nadal': (20,84)}
((player1, (grandslam1, *atptitles1)), (player2, (grandslam2, *atptitles2))) = player_grandslame_and_atptitles.items()
print(f'Output \nPlayer - {player1} Have acheived {grandslam1} grandslams and , {atptitles1} atp tour titles')
print(f'Player - {player2} Have acheived {grandslam2} grandslams and , {atptitles2} atp tour titles')
出力結果
Player - Federer Have acheived 20 grandslams and , [103] atp tour titles Player - Nadal Have acheived 20 grandslams and , [84] atp tour titles
スター式は常にリストになる
スター式の評価結果は、どんな場合でもリスト型のインスタンスになります。もしアンパック対象のシーケンスに余った要素がなければ、catch-all部分は空のリストになります。これは、少なくともN個の要素を持つことが事前に分かっているシーケンスを処理する際に特に便利です。
random_numbers = [0, 1]
first, second, *rest = random_numbers
print(f"Output \n{first, second, rest}")
出力結果
(0, 1, [])
まとめ
スター式を使うことで、シーケンスの長さを気にせず、柔軟かつ可読性の高いアンパックが可能になります。押さえておきたいポイントは以下の通りです。
- スター式は「残りのすべて」を受け取るcatch-allとして機能する
- スター式はパターン内の任意の位置に置ける
- 必須部分が最低1つ必要(スター式のみの代入はSyntaxErrorになる)
- 同一レベルで複数のスター式は不可だが、ネストした異なるレベルなら複数OK
- スター式の結果は常にリストになり、余りがなければ空リストになる
-
Pythonの正規表現を使って文字列から数字のみを抽出・マッチングする方法
Pythonでは、標準ライブラリの re モジュールを使うことで、正規表現によるパターンマッチングが簡単に行えます。文字列から「数字のみ」を取り出したい場合は、メタ文字 \d(任意の1桁の数字に一致)を使用します。ここでは、代表的な2つの方法——re.search() と re.findall() を使った例を紹介します。なお、コードはPython 3系の記法に準拠しています。例1:re.search() で最初に見つかった数字の並びを取得する次のコードは、指定した文字列の中から最初に出現する連続した数字(1桁以上)を検索し、マッチオブジェクトとして返します。import re m = re
-
Pythonでファイルを削除する方法をわかりやすく解説
Pythonでファイルを削除する基本の方法 Pythonでは、標準ライブラリのosモジュールを使うことで、単一のファイルや空のフォルダ(ディレクトリ)を簡単に削除できます。外部ライブラリのインストールは不要で、import osと記述するだけですぐに利用できるのが魅力です。 os.remove() でファイルを削除する たとえば、カレントディレクトリにある my_file.txt というファイルを削除したい場合は、次のように記述します。 >>> import os >>> os.remove(my_file.txt) os.remove() の引数には、削