FabeeeFabeee

お問い合わせ
2021.01.29FabeeePython

【Python】あんぱん食って、アンパック!


日ごと寒さがつのる今日この頃、みなさまいかがお過ごしでしょうか。
あんぱん&牛乳という刑事ドラマの定番張り込み中スタイルでこれを書いております、りんです。ごきげんよう。

さて
Pythonをやっているそこのアナタ。こんなものを見かけたことはありませんか?

def __init__(self, *args, **kwargs):
    # なにやら処理いろいろ

フレームワークなどを使っていると目にすることも多いかと思います。

なんか引数に*が異様についてるな?
*って、何?
**は何でふたつもついてるの??

今までスルーしてきたこの素朴な疑問を今回は解き明かしていきたいと思います。不思議、発見!

①とりあえず実験してみよう


とりあえず正体を探るべくとりあえずprintしてみましょう。
*args, **kwargsのある関数を作って見てみます。

def print_func(*args, **kwargs):
    print(f'args -> {args}')
    print(f'kwargs -> {kwargs}')

エラーが起きても失うものは何もない。テキトーに呼び出してみましょう。

print_func('あんぱん')
# args -> ('あんぱん',)
# kwargs -> {}
print_func(123)
# args -> (123,)
# kwargs -> {}
print_func('クロワッサン', 'トースト', 'あんぱん')
# args -> ('クロワッサン', 'トースト', 'あんぱん')
# kwargs -> {}
print_func({'bread': 'あんぱん'})
# args -> ({'bread': 'あんぱん'},)
# kwargs -> {}

何をやっても引数が全てargsにタプル型で入りました。
そしてkwargsは辞書ですが何をやっても空です。むむ。
細かいことはさておき、とりあえず「引数が全部まとめてargsになったなぁ」ということだけがわかりました。

②アンパック〜展開〜


なぜこんなことが起こるのか?**kwargsとは何だったのか?
謎を解く鍵が今回のテーマ「アンパック」です。
段階を追ってアンパックを見ていきましょう。

Pythonにはタプルやリスト、辞書などの要素を”展開する”という機能があります。

bread1, bread2, bread3 = ('クロワッサン', 'トースト', 'あんぱん')
print(bread1)
# クロワッサン
print(bread2)
# トースト
print(bread3)
# あんぱん

タプルの中身が分解されてひとつひとつ変数に入っていることがわかりますね。
一つ目の要素はひとつめの変数に、二つ目の要素はふたつめの変数に入っています。
これが「アンパック」と呼ばれるPythonの機能です。

これは=の右と左とで数が合わないと怒られます。

# 変数が2つ、要素が3つ: あんぱんの居場所がない!
bread1, bread2 = ('クロワッサン', 'トースト', 'あんぱん')
# Traceback (most recent call last):
#   File "", line 1, in 
# ValueError: too many values to unpack (expected 2)

# 変数が3つ、要素が2つ: bread3が手持ち無沙汰!
bread1, bread2, bread3 = ('クロワッサン', 'トースト')
# Traceback (most recent call last):
#   File "", line 1, in 
# ValueError: not enough values to unpack (expected 3, got 2)

ちなみにタプルでなくリストでも同じことができます。

bread1, bread2, bread3 = ['クロワッサン', 'トースト', 'あんぱん']
print(bread1)
# クロワッサン
print(bread2)
# トースト
print(bread3)
# あんぱん

③アンパック〜*〜


あんぱんにゴマがついているように、アンパックにも相方がいます。それが「*」です。
見た方が早いのでみてみましょう。

bread1, *bread2 = ('クロワッサン', 'トースト', 'あんぱん')
print(bread1)
# クロワッサン
print(bread2)
# ['トースト', 'あんぱん']

一つ目の変数bread1にクロワッサンを、*をつけた変数bread2に残り二つのパンがリストで代入されていますね。
この*はこのように、残りをよしなにやってくれます。

# パンが増えても大丈夫
bread1, *bread2 = ('クロワッサン', 'トースト', 'あんぱん', 'カレーパン')
print(bread1)
# クロワッサン
print(bread2)
# ['トースト', 'あんぱん', 'カレーパン']

先ほどは右辺の要素数(パンの数)と左辺の数(変数の数)が合わず怒られましたが
この例ではbread2に残り全てを詰め込むことにより事なきをgetしています。

ただひとつ注意!
*をつけられる変数はひとつだけです。

*bread1, bread2 = ('クロワッサン', 'トースト', 'あんぱん')

これは文法的に可能ですが

*bread1, *bread2 = ('クロワッサン', 'トースト', 'あんぱん')

これはNGです。
3つの要素を2つの変数に振り分けるのに、どのように振り分けたらよいかわからないからですね。

ちなみにこの*は変数名ではありませんので
bread2を使う時は上記のように*なしで使います。

④アンパック〜tuple〜


さて、この*を使うとこんなこともできます。

breads = ('クロワッサン', 'トースト', 'あんぱん')
print(*breads)
# クロワッサン トースト あんぱん

??ちょっとわかりにくいですね。
普通のprintと比べてみましょう。

print(breads)
# (‘クロワッサン', 'トースト', 'あんぱん')

なんかちょっと違いますね。

breadsはタプルを表示していますが、*breadsは中身を展開しひとつひとつ表示しています。
これもまたアンパックです。
そしてこれこそがまさに*args。

Pythonではタプルは

breads =  ('クロワッサン', 'トースト', 'あんぱん')

このように書きますが、このようにも書けます。

breads = 'クロワッサン', 'トースト', 'あんぱん'

Pythonでタプルをタプルたらしめているのは、実はカッコでなくカンマだったのです。
つまり先ほどの例はこのようにも書くことができます。

breads = 'クロワッサン', 'トースト', 'あんぱん'
print(*breads)
# クロワッサン トースト あんぱん

⑤で、**kwargsは??


さて残るはアスタリスクをふたつ持つ**kwargsの謎。
これもアンパックです。
今まで*ひとつでリストやタプルをアンパックしてきましたが、辞書をアンパックする際には*をふたつ使います。

なるほど!やってみよう!

breads = {'クロワッサン': '120円', 'トースト': '100円', 'あんぱん': '110円'}
print(**breads)
# Traceback (most recent call last):
#   File "", line 1, in 
# TypeError: 'クロワッサン' is an invalid keyword argument for print()

おっと、怒られましたね。
よく見ると「‘クロワッサン’はprint()のキーワード引数として適切じゃないよ!」と言われています。
ということはクロワッサンをprintのキーワード引数として渡そうとしている、ということですね。

ということは(二回目)

# croissant, toast, red_bean_bunという名前のキーワード引数をもつ関数
def print_breads(croissant, toast, red_bean_bun):
    print(f'croissant -> {croissant}')
    print(f'toast -> {toast}')
    print(f'red_bean_bun -> {red_bean_bun}')

# croissant, toast, red_bean_bunという名前のキーをもつ辞書
breads = {'croissant': '120円', 'toast': '100円', 'red_bean_bun': '110円'}

print_breads(**breads)
# croissant -> 120円
# toast -> 100円
# red_bean_bun -> 110円

今度は動きましたね。
この**breadsは
‘croissant’という名前で’120円’という値を、’toast’という名前で’100円’という値を、’red_bean_bun’という名前で’110円’という値を、引数として渡す
の動きをします。
これが**kwargsの正体です。

⑥不思議、発見!


さて、そんなこんなで*argsと**kwargsの正体がわかってきたところで、冒頭の例に立ち返ってみましょう。

def print_func(*args, **kwargs):
    print(f'args -> {args}')
    print(f'kwargs -> {kwargs}')


bread_list = ['クロワッサン', 'トースト', 'あんぱん']
bread_dict = {'croissant': '120円', 'toast': '100円', 'red_bean_bun': '110円'}

# bread_listがそのまま渡さる
print_func(bread_list)
# args -> (['クロワッサン', 'トースト', 'あんぱん'],)
# kwargs -> {}

# bread_listが展開されて渡される
print_func(*bread_list)
# args -> ('クロワッサン', 'トースト', 'あんぱん')
# kwargs -> {}

# bread_dictがそのまま渡される
print_func(bread_dict)
# args -> ({'croissant': '120円', 'toast': '100円', 'red_bean_bun': '110円'},)
# kwargs -> {}

# bread_dictが展開され、キーワード引数として渡される
print_func(**bread_dict)
# args -> ()
# kwargs -> {'croissant': '120円', 'toast': '100円', 'red_bean_bun': '110円'}

こんな感じに、*argsと**kwargsが全ての引数を受け止めてくれていることがわかりました。
こういうヤツだったんですね。

ちなみに、好奇心のおもむくまま更に実験してみた結果がこちら。

# dictに*をひとつだけつけると、キーのみが展開される
print_func(*bread_dict)
# args -> ('croissant', 'toast', 'red_bean_bun')
# kwargs -> {}

# 引数にふたつとも入れるとこうなる
print_func(*bread_list, **bread_dict)
# args -> ('クロワッサン', 'トースト', 'あんぱん')
# kwargs -> {'croissant': '120円', 'toast': '100円', 'red_bean_bun': '110円'}

# listを展開しつつdictのキーだけを展開するという合わせ技をやると、全てargsに集約される
print_func(*bread_list, *bread_dict)
# args -> ('クロワッサン', 'トースト', 'あんぱん', 'croissant', 'toast', 'red_bean_bun')
# kwargs -> {}

# 引数の順番を変えると、タプルの要素の順番も変わる
print_func(*bread_dict, *bread_list)
# args -> ('croissant', 'toast', 'red_bean_bun', 'クロワッサン', 'トースト', 'あんぱん')
# kwargs -> {}

# !dictをキーワード引数として**で展開する場合は、引数の順番を守らねばならない!
print_func(**bread_dict, *bread_list)
#   File "", line 1
# SyntaxError: iterable argument unpacking follows keyword argument unpacking

 


さて、今回は*argsと**kwargsの謎を解き明かしてきました。
このアンパックという機能は他の言語にはなかったりするので、独特で面白いなと思いました。
みなさまもこれから、この*や**を見たら今日のこのあんぱんを思い出してひっそりとほくそ笑んでいただければ幸いです。