***

🌟💫🌟💫🌟💫🌟💫🌟💫🌟💫

Event:

Python Tips LT会 - vol.3

Presented:

2022/05/25 nikkie

質問です! どうなるでしょうか?

>>> *(1, 2)
  • Python 3.10 で動作確認

  • 現在サポートされている Python 3.7 以降で再現します(はず)

お前、誰よ

  • 「お前、誰よ」は自己紹介のエイリアス(Python界隈の慣習)

  • Python大好き にっきー

  • 株式会社ユーザベースのデータサイエンティスト(NLPer)

Python Tips LT、お世話になっております

Python Tips LT、お世話になっております

最近のPythonニュース

NHKのドラマ『17才の帝国』に㊗️ 出演

もう1つ:読書会やるんで、みんな来て

再掲:どうなるでしょうか?

>>> *(1, 2)

考えながらお聞きください

Pythonにおける ***

  • 二項演算子( * は乗算、 ** はべき乗)

  • 可変長引数( *args, **kwargs

  • アンパック演算子

他にも浮かんだらぜひ教えてください🙏

Pythonにおける *

  • catch-all アンパック代入

  • キーワード専用引数

他にも浮かんだらぜひ教えてください🙏🙏

このLTでは、2つの切り口 🌟💫🌟💫

  1. アンパック

  2. 関数の引数

紹介できないものはAppendixへ

お品書き *** 🌟💫🌟💫

  1. アンパック演算子

  2. 関数と ***

    1. キーワード専用引数

    2. 可変長引数

Part I. アンパック演算子 🌟💫🌟💫

アンパックする演算子としての *** を紹介します

パック/アンパック

  • タプルのパック: 詰める (パッキング)

  • シーケンスのアンパック:中の物を 取り出す

パック・アンパックは Pythonチュートリアル 5.3. で紹介されます

アンパック演算子

  • イテラブルアンパック演算子

  • 辞書アンパック演算子

用語は「What's New In Python 3.5」より

イテラブルアンパック演算子

「イテラブル」を用語集で引く

要素を一度に 1 つずつ返せるオブジェクト

これらがイテラブル(用語集より)

  • シーケンス

  • 辞書

  • ファイルオブジェクト

  • など(※イテラブルを自作もできます)

これらがシーケンス(用語集 より)

  • リスト

  • 文字列

  • タプル

  • など(※自作もできます)

整数インデクスでアクセスでき、長さを返す

例で紹介:イテラブルアンパック演算子

>>> # タプル (1, 2) は(シーケンスであり)イテラブル
>>> [*(1, 2)]
[1, 2]

この書き方でも新しいリストが作れます🌟💫

>>> [*(1, 2), 3]
[1, 2, 3]

脱線🍚:他のイテラブルでも

>>> [0, *[1, 2]]
[0, 1, 2]
>>> [*range(2), 2]
[0, 1, 2]
>>> [*"12", 3]
['1', '2', 3]

イテラブルアンパック演算子で新しいタプルも作れます

>>> (*(1, 2), 3)
(1, 2, 3)

ご存知ですか? タプルの肝は カンマ

タプルを書くときは必ずしも丸括弧で囲まなくてもいい (Pythonチュートリアル 5.3.

>>> 1, 2
(1, 2)
>>> 1,
(1,)

カッコつけずに書けます

>>> *(1, 2), 3
(1, 2, 3)

先の質問:どうなるでしょうか?

>>> *(1, 2)  

たたーん! 正解は SyntaxError

>>> *(1, 2)  
  File "<stdin>", line 1
SyntaxError: can't use starred expression here

ですが、カンマつけると動きます

>>> *(1, 2),
(1, 2)

カッコつけないタプルの書き方になっている!

脱線🍚:辞書の場合、キー からなるタプルになります

>>> # 辞書はイテラブル
>>> fruits_prices = {"apple": 100, "banana": 50}
>>> *fruits_prices,
('apple', 'banana')

脱線🍚:複数の値を返す関数は タプル を返している!

>>> def f():
...   return 42, "spam"
...
>>> type(f())
<class 'tuple'>

カッコつけずに書いたタプル!

イテラブルアンパック演算子 小まとめ

  • *iterable, v1, v2 で新しい タプル が返る

  • [*iterable, v1, v2] とすれば新しいリスト

    • [*iterable] だけでもリストにできる

アンパック演算子

  • イテラブルアンパック演算子

  • 辞書アンパック演算子

辞書アンパック演算子

>>> fruits_prices = {"apple": 100, "banana": 50}
>>> {**fruits_prices}
{'apple': 100, 'banana': 50}

新しい辞書を作る例

>>> {**fruits_prices, "melon": 777}
{'apple': 100, 'banana': 50, 'melon': 777}

キーと値の組を 追加 した辞書

アンパック演算子は 複数 使えます

アンパック演算子を複数使う例

>>> [*(1, 2), 3, *range(2)]
[1, 2, 3, 0, 1]
>>> d1, d2 = {"x": 11, "y": 22}, {"v": 101, "w": 201}
>>> {**d1, "z": -33, **d2}
{'x': 11, 'y': 22, 'z': -33, 'v': 101, 'w': 201}

脱線🍚:2つの辞書から新しい辞書を作る場合

  • 知っていたので、一方の辞書をforで回さず、簡潔に書けました

>>> fruits_prices = {"apple": 100, "banana": 50}
>>> {**fruits_prices, **{"melon": 777}}
{'apple': 100, 'banana': 50, 'melon': 777}

脱線🍚:2つの辞書から新しい辞書を作る場合

  • なお、Python 3.9 以降は | 演算子で もっと簡単 に書けます

>>> fruits_prices | {"melon": 777}
{'apple': 100, 'banana': 50, 'melon': 777}

Guidoさん曰く「私も {**d1, **d2} って書けるの忘れてた(意訳)」(PEP 584

まとめ🌯 Part I. アンパック演算子🌟💫🌟💫

  • * はイテラブルを、 ** は辞書をアンパックする演算子

  • *** を使って、新しいタプル・リスト・辞書を作るやり方を紹介

    • PEP 448により ***複数回 使えます!

閑話休題:PyCon JP 2022はプロポーザルを募集中!

Part II. 関数と *** 🌟💫🌟💫

関数の 引数 と絡めて *** を紹介します

Pythonの関数の引数

  • 位置またはキーワード引数Pythonチュートリアル 4.8.3.1.

  • 位置引数としても渡せるし、キーワード引数(=名前付き引数)としても渡せる

関数の例

>>> def calculate_bmi(height_m, weight_kg):
...     return weight_kg / height_m / height_m

呼び出し例

>>> calculate_bmi(1.58, 46)
18.426534209261334
>>> # すべてキーワード引数で渡せば、順番を変えられます
>>> calculate_bmi(weight_kg=46, height_m=1.58)
18.426534209261334
>>> # 位置引数はキーワード引数より先に来ます
>>> calculate_bmi(1.58, weight_kg=46)
18.426534209261334

キーワード専用引数と示す *

  • 引数の並びの中で、 *, のあとの引数は、キーワード専用引数となる

  • 必ず名前付き で渡さなければならず、位置引数としては渡せなくなる

Pythonチュートリアル 4.8.3.3.

キーワード専用引数をお試し導入

>>> # 2つの引数をどちらもキーワード専用とした
>>> def calculate_bmi(*, height_m, weight_kg):
...     return weight_kg / height_m / height_m

キーワード専用引数の動作確認

>>> calculate_bmi(weight_kg=46, height_m=1.58)
18.426534209261334
>>> calculate_bmi(1.58, 46)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: calculate_bmi() takes 0 positional arguments but 2 were given

キーワード専用引数の使い所

  • 関数呼び出しの意図を明確に するために使う(『Effective Python 第2版』項目25)

    • 例えば複数の論理型フラグを使うような、紛らわしい関数呼び出し

    • 脱線 IMO:フラグ引数は悪魔が潜みがちですよね(ref: #ミノ駆動本)

脱線🍚:位置専用引数もできます

別の話題:可変長引数と ***

>>> def f(*args, **kwargs):
...     print(f"{args=}")
...     print(f"{kwargs=}")

* が付いた args** が付いた kwargs について見ていきましょう

脱線🍚: f"{args=}" というf-string

可変長位置引数 *args

>>> f(1, 2, 3)
args=(1, 2, 3)
kwargs={}

位置引数たちは argsタプル)に詰められる

リストの要素の数字全部を渡したい

>>> numbers = [1, 2, 3]
>>> f(numbers)
args=([1, 2, 3],)
kwargs={}
  • args は要素1のタプル

  • args[0][1, 2, 3]

イテラブルアンパック演算子を使えば渡せる!🙌

>>> f(*numbers)
args=(1, 2, 3)
kwargs={}

イテラブルの要素が位置引数として渡された

可変長キーワード引数 **kwargs

>>> f(a=1, b=2, c=3)
args=()
kwargs={'a': 1, 'b': 2, 'c': 3}

キーワード引数たちは kwargs辞書)に詰められる

辞書アンパック演算子を使って辞書を渡すと・・・

>>> values = {"a": 1, "b": 2, "c": 3}
>>> f(**values)
args=()
kwargs={'a': 1, 'b': 2, 'c': 3}

辞書のキーと値の組をキーワード引数として渡せました🙌

2種のアンパック演算子は合わせて使えます!

>>> f(*numbers, **values)  # ただし、この順に限る
args=(1, 2, 3)
kwargs={'a': 1, 'b': 2, 'c': 3}

One more thing: アンパック演算子は 複数回 使えます!

  • PEP 448「複数回使える」は関数の引数にも該当します

アンパック演算子を複数回使う例

>>> f(*range(2), 2, *[3, 4])
args=(0, 1, 2, 3, 4)
kwargs={}
>>> f(**{"z": 1}, y=2, **{"x": 3})
args=()
kwargs={'z': 1, 'y': 2, 'x': 3}

まとめ🌯 Part II. 関数と *** 🌟💫🌟💫

  • 引数リスト中に * を置くことで、それ以降を キーワード専用引数 にできる

  • * を先頭に付けた引数は 可変長位置引数** を先頭に付けると 可変長キーワード引数

  • 可変長{位置,キーワード}引数に、アンパック演算子を使って渡すこともできる

まとめ🌯 *** 🌟💫🌟💫

  • {イテラブル,辞書}アンパック演算子

  • 可変長{位置,キーワード}引数 + アンパック演算子との組合せ

  • これらは頻出ではないですが、 知っていたからこそ簡単に書けた瞬間 が訪れると思います

ご清聴ありがとうございました

Enjoy development with stars! 🌟💫

Appendix (WIP)

追加をお楽しみに!

その他の *

  • *インポートfrom module import * と書けるけれど非推奨)

  • glob.glob***)(Mikumoさん、ありがとうございます)

  • re*

EOF