検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
  • ただいまサイドメニューのテスト中/ただいまサイドメニューのテスト中
>>
effective_python

【 Effective Python, 2nd Edition 】* 書式 (starred expression) をアンパックで活用しよう! 投稿一覧へ戻る

Published 2020年6月16日17:42 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

通常のアンパックでは、事前にアンパック対象のシーケンスの長さを知っている必要があります。


ですから、次のようなリストから高得点上位2つだけをアンパック構文で取得しようとするとエラーになります。


scores = [78, 93, 61, 94, 88, 59, 71, 98, 66]
scores_descending = sorted(scores, reverse=True)
highest, second_highest = scores_descending

# Traceback:
# ValueError: too many values to unpack (expected 2)




それじゃぁ、と、インデックスとスライスを利用して、上位2つそれぞれと、残り全部を取得します。


highest = scores_descending[0]
second_highest = scores_descending[1]
others = scores_descending[2:]
print(f"{highest}, {second_highest}, {others}") # 98, 94, [93, 88, 78, 71, 66, 61, 59]



もちろん問題なく動作します。
が、1つのシーケンスをこのようにインデックス番号やスライスを利用して複数のサブセットに分割する場合、
いずれかのインデックス番号を変更した場合には他の部分も全て書き換える必要が生じる等、
将来的にエラーを発生させる余地を大きくしてしまいます。


そこで...


highest, second_highest, *others = scores_descending
print(f"{highest}, {second_highest}, {others}") # 98, 94, [93, 88, 78, 71, 66, 61, 59]



アンパックで * 書式 (starred expression) を利用することで、上の例のようにスッキリと取り出すことが可能です。


また * 書式はアンパック構文中の何処にでも記述することができます。


higehest, *others, lowest = scores_descending
print(f"{highest}, {lowest}, {others}") # 98, 59, [94, 93, 88, 78, 71, 66, 61]

*others, second_lowest, lowest = scores_descending
print(f"{lowest}, {second_lowest}, {others}") # 59, 61, [98, 94, 93, 88, 78, 71, 66]



ただし * 書式だけでシーケンス要素の全てを受けることはできません (これはシーケンス全体=シーケンスそのものを取得しようという行為ですからほぼ意味はないですね)。
必ず1つ以上の特定要素と共に利用する必要があります。


*all = scores_descending # SyntaxError: starred assignment target must be in a list or tuple



また、2つ以上の * 書式を1つのアンパック構文中に含めることもできません(切れ目が分かりませんから当然ですね)。


highest, *middle, *second_middle, lowest = scores_descending # SyntaxError: two starred expressions in assignment



ただし、おススメはできませんが、多階層構造になっているものをアンパックする場合には複数の * 書式を1つの構文中に記述できます。
繰り返しますが、おススメできません。あくまでアンパック中で * 書式を利用する際の可能性、と考えてご覧ください。。


high_grades = {
'Math': ('Nana', 'Yuka', 'Megu'),
'Programming': ('Saki', 'Hana'),
'German': ('Hana', 'Nana', 'Eri'),
'Finance': ('Saki', 'Megu'),
}

((sub1, (highest1, *others1)), (sub2, (highest2, *others2)), *other_sub) = high_grades.items()

print(f"{sub1} での最高得点者は {highest1}, その他 {len(others1)} 人。") # Math での最高得点者は Nana, その他 2 人。
print(f"{sub2} での最高得点者は {highest2}, その他 {len(others2)} 人。") # Programming での最高得点者は Saki, その他 1 人。
print(f"他の教科: {other_sub}") # 他の教科: [('German', ('Hana', 'Nana', 'Eri')), ('Finance', ('Saki', 'Megu'))]



ここまでの例でもお分かりのように、アンパック中の * 書式はリストを返します。
もし該当する要素がない場合でも、空のリストが返ってきます。


short_list = [1, 2]
first, second, *rest = short_list
print(f"{first}, {second}, {rest}") # 1, 2, []



アンパックは実体のあるシーケンスに対してだけではなく、イテレーターに対しても利用できます。


it = iter(range(1, 3))
first, second = it
print(f"{first} and {second}.") # 1 and 2.



何の役にもたたなそうですが、* 書式と一緒に利用すると...
例えば、ユーザー情報を保存した CSV ファイルがあり、それを1行ずつ読み込んでいく generator があるとします。


def generate_user_info_from_csv():
yield("名前", "住所", "電話番号", "年齢")
...



もちろんインデックス番号とスライスを利用しても処理可能です。


all_csv_rows = list(generate_user_info_from_csv())
header = all_csv_rows[0]
data_rows = all_csv_rows[1:]
print(f"項目: {header}") # 項目: ('名前', '住所', '電話番号', '年齢')
print(f"データは {len(data_rows)} 行あります。") # データは 2000 行あります。



ですが、アンパックと * 書式を利用することでこんなにスッキリします。


it = generate_user_info_from_csv()
header, *data_rows = it
print(f"項目: {header}") # 項目: ('名前', '住所', '電話番号', '年齢')
print(f"データは {len(data_rows)} 行あります。") # データは 2000 行あります。



ただし注意点もあります。


* 書式は常にリストを返します。ということは、元のデータが非常に大きい場合、メモリ消費が非常に大きい、もしくは使い切ってしまう可能性さえある、ということです。
ですから、イテレータを対象として * 書式を含んだアンパックを利用する場合は、最終的に取得するデータの大きさを常に意識しておかなければいけません。


まとめ

1: アンパックする際の特定要素以外の残り全ての要素は * 書式を利用してリストとして取得することができる。

2: * 書式はアンパック構文中の何処にでも記述可能で、0 個以上の要素を含むリストを返す。

3: あるリストを重複のないサブセットに分解する場合、* 書式を含むアンパック構文を利用することで、インデックス番号とスライスを利用した場合と比較して簡潔に記述することが可能で、かつ、変更に伴うエラー発生の抑止にもなる。

この記事に興味のある方は次の記事にも関心を持っているようです...
- People who read this article may also be interested in following articles ... -
【 Effective Python, 2nd Edition 】アンパックのちょっとした使い方 - 1: 一時変数を使わずにスワップする、2: インデックス番号を用いないでネストしている iterable の要素を取り出す -
【Python 雑談・雑学】 関数への引数の渡し方 - 複数の個別の引数を取るのなら、ちゃんとアンパックして渡しましょう -
【 Effective Python, 2nd Edition 】assignment expression (walrus operator, :=) を利用して内包表記におけるサブ書式の重複を回避しよう!
【 Effective Python, 2nd Edition 】@classmethod ポリモーフィズム ( polymorphism ) を利用して、複数の派生クラスをよりジェネリック ( generic ) に活用しよう!
【 Effective Python, 2nd Edition 】ブロッキング I/O ( blocking I/O ) とスレッド ( thread ) を利用しているプログラムを、asyncio 組み込みモジュールを利用してコルーチン ( coroutine ) と非同期 I/O ( asyncronous I/O ) を利用したプログラムにリファクタリング ( refactoring ) しよう!
【 Effective Python, 2nd Edition 】組み込みタイプ ( built-in types ) を利用していてネストが深くなってきたらクラス ( class ) を作成する頃合いです、の巻
【 Effective Python, 2nd Edition 】Queue クラスを利用した producer-consumer パイプライン ( pipelines ) を構築して、マルチスレッドシーケンス処理をエレガントに管理しよう! 並行実行 ( parallelism ) と並列処理 ( concurrency ) もついでにちゃんとイメージしよう!