Pythonとアスタリスク 🐍🌟💫🐍🌟💫

#pyconjp #pyconjp_4

Event:

PyCon JP 2022

Presented:

2022/10/14 nikkie

ういっす✌️

㊗️有明開催🌈(スタッフの方々、ありがとうございます!!👏❤️)

聞きに来てくださり、誠にありがとうございます

ヒトツダケナンテエラベナイヨー なラインナップですよね!(全45本)

お前、誰よ

  • Python 大好き、にっきー

  • Twitter @ftnext / GitHub @ftnextはてなブログ

  • アニメ も好きで、積極的に アニメネタを仕込む アウトプットスタイル

XPするデータサイエンティスト

  • 株式会社ユーザベース

  • えぬえるぴーや (自然言語処理、Python)にして えくすぴーや (テスト駆動開発、ペアプログラミング)

  • We're hiring!! (Engineers, Data scientists, Researchers)

https://drive.google.com/uc?id=19PMMnkqDiFMCJBPwoA1B51ltQBG0y4kL

0️⃣導入:本トークの位置づけ

「Pythonとアスタリスク 🐍🌟💫🐍🌟💫」へようこそ

出逢いと発見(Meet and Discover)

アスタリスクってそんなに使えるの❓

  • うるせェ!!! 行こう!!!!

  • もちろん * は頻出ではありません

  • * との出逢いを頭の片隅に留めておくと、 気持ちよく書ける瞬間 が訪れますよ🤗

動作環境

  • Python 3.10 (doctestで動作確認)

  • TODO(nikkie) 10/24にPython 3.11がリリースされたら動作確認して更新すること

お品書き:Pythonとアスタリスク 🐍🌟💫🐍🌟💫

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数

  5. 関数と * その2: 可変長 引数

LT⚡️が5本 ある感じです!

  • オススメの聞き方: 興味 のあるトピックに 集中

  • 早くて分からなくなってしまったら 次のトピックから合流 で大丈夫です🙆‍♂️

この色のスライドは 休憩タイム (本題ではありません)

  • LTの間には閑話🍵コンテンツ

  • さらに知りたい方向けコンテンツ(さら知り)は本編では駆け抜けます🏃‍♂️(スライド、作りすぎちった)

1️⃣二項算術演算子としての *, **

二項算術演算子

  • (A)乗算

  • (B)べき乗

使ったことある方〜?🙋‍♂️🙋‍♀️

1️⃣-🅰️ *乗算 (掛け算)

>>> 2 * 6
12
>>> 6 * 2
12

6.7. 二項算術演算 (binary arithmetic operation)

乗算を整理

  • 数値どうし(前頁で見た)

  • 整数と シーケンス

6.7. 二項算術演算 (binary arithmetic operation)

シーケンスとは(用語集 より)

  • リスト

  • 文字列

  • タプル

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

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

整数とシーケンスの乗算

>>> "🐍🌟💫" * 2
'🐍🌟💫🐍🌟💫'
>>> 3 * (1, 3, 5)
(1, 3, 5, 1, 3, 5, 1, 3, 5)
>>> [9, 8] * -2  # 負の繰り返し数では空
[]

繰り返し 操作

1️⃣-🅱️ **べき乗 (パワー)

>>> 5 ** 3  # 5 * 5 * 5
125

6.5. べき乗演算 (power operator)

べき乗演算子の優先順位

>>> -1 ** 2  # -(1 ** 2) 左側にある単項演算子 - より ** が先
-1
>>> (-1) ** 2
1

6.5. べき乗演算 (power operator)

🔍二項算術演算子って どう実装 されているんでしょう?

わたし、気になります!

特殊メソッド(用語集

ある型に特定の操作、例えば加算をするために Python から暗黙に呼び出されるメソッド。

乗算・べき乗の特殊メソッド があるんです!

特殊メソッド(用語集

この種類のメソッドは、メソッド名の最初と最後にアンダースコア 2 つがついています。
特殊メソッドについては 特殊メソッド名 で解説されています。

補足🏃‍♂️:これらの用語は同じ意味

  • 特殊メソッド(special method)

  • マジックメソッド くだけた同義語

  • ダンダーメソッド(ダンダー= d ouble under score 参照

乗算の特殊メソッド __mul__

* (乗算)は __mul__ を呼んでいます

>>> 2 * 6
12
>>> (2).__mul__(6)  # カッコつけずに 2.__mul__(6) は小数とパースされてSyntaxError
12

べき乗の特殊メソッド __pow__

** (べき乗)は __pow__ を呼んでいます

>>> 5 ** 3
125
>>> (5).__pow__(3)
125

組み込み関数 pow

二引数の形式 pow(base, exp) は、冪乗演算子を使った base**exp と等価です。

>>> pow(5, 3)
125
>>> (5).__pow__(3)
125

あなただけの乗算

  • クラスを定義するときに、特殊メソッドを オーバーライド

  • __mul__ をオーバーライドすれば、乗算の動きをカスタマイズできます

オリジナルの乗算:文字列どうし

掛け算」ができる文字列 CouplableStr

>>> "ゆう" * "ぽむ"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'

>>> CouplableStr("ぽむ")  
'ぽむ'
>>> CouplableStr("ゆう") * CouplableStr("ぽむ")  
'ゆうぽむ'

オリジナルの乗算:文字列どうし 実装例🏃‍♂️

class CouplableStr(UserString):
    def __mul__(self, other: CouplableStr) -> str:
        if not isinstance(other, self.__class__):
            return NotImplemented
        return f"{self.data}{other.data}"

ソースコード

まとめ🥟:二項算術演算子としての *, **

演算

演算子

特殊メソッド

乗算

*

__mul__

べき乗

**

__pow__

さらに知りたい方へ🏃‍♂️:『Python実践入門』がオススメ

  • 8.2 特殊メソッド ── Pythonが暗黙的に呼び出す特別なメソッド

  • 特殊メソッドを学ぶのにオススメのリソース知っている方はぜひ教えてください!🙏

さら知り🏃‍♂️:過去のPyCon JPからのオススメトーク

さら知り🏃‍♂️: __rmul__

  • x * yx.__mul__(y)NotImplemented を返すとき、 y.__rmul__(x) が呼ばれる

  • x__mul__ メソッドを持っていないときも同様

__rpow__ もある

さら知り🏃‍♂️: __imul__ もある

  • 累算算術代入 (乗算の場合は *= )の実装

    特定のメソッドが定義されていない場合、その累算算術演算は通常のメソッドにフォールバックされます。

お品書き:Pythonとアスタリスク 🐍🌟💫🐍🌟💫

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数

  5. 関数と * その2: 可変長 引数

閑話🍵: import 文と *

from module_or_package import * はオススメしません🙅‍♂️

from module_or_package import *

オススメしません🙅‍♂️

  • PEP 8避けるべき

    Wildcard imports should be avoided

  • コードを理解しにくくなる(『Effective Python 第2版』項目85)

  • 使うとしても対話モードonly

📺提供:にっきー(お前、誰よ 2/N)

  • PyCon JP 2019からスタッフ、2021は 座長 🇨🇭

    • 小声:実は2021もオンサイト開催でした(ハイブリッドなので)

  • そして、燃え尽きました!!

    • 現在の関心:コーディングでOSSにコントリビュート

📺提供:にっきー(お前、誰よ 2/N)

2️⃣アンパック代入と *

使ったことある方〜?🙋‍♂️🙋‍♀️

代入

>>> t = ("🖤", "🎀")

私はおそらく、親の顔を見るより代入してます

シーケンスのアンパック

>>> first, second = t  # 前頁より t はタプル ("🖤", "🎀") を指してます
>>> first  # t[0]
'🖤'
>>> second  # t[1]
'🎀'

Pythonチュートリアル 5.3. タプルとシーケンス

シーケンスのアンパックの書き方

>>> first, second = ("🖤", "🎀")
  • 等号の左辺の 変数の数 と、右辺のシーケンスの 長さ を同じにする

タプルに限らずシーケンスはアンパックできます

>>> first, second = ["🖤", "🎀"]  # リストはシーケンス
>>> first, second = "🖤🎀"  # 文字列はシーケンス

シーケンスのアンパックのすゝめ

アンパックを使えば、シーケンスにインデックスを使わずに済むので、コードが明確になり、見た目がすっきりする。

Effective Python 第2版 項目6

シーケンスのアンパック、いいですよね

>>> first, second, third, fourth = ("🍞", "📶", "👑", "🐑")

変数をシーケンスの 長さ分並べる 必要がある🤨

catch-allアンパック

1つ だけ * 付きの変数!

>>> first, second, *others = ("🍞", "📶", "👑", "🐑")

変数はシーケンスの長さよりも少なくて済む!

catch-allアンパック

>>> first, second, *others = ("🍞", "📶", "👑", "🐑")
>>> first
'🍞'
>>> second
'📶'
>>> others  # シーケンスの残りの要素を持ったリスト
['👑', '🐑']

* 付きの変数は リスト を指す

左辺のどの変数にも * を付けられます!

>>> first, *others, last = ("🍞", "📶", "👑", "🐑")
>>> others
['📶', '👑']
>>> *others, second_to_last, last = ("🍞", "📶", "👑", "🐑")
>>> others
['🍞', '📶']

* 付きの変数に該当する要素がないときは 空リスト

>>> first, *others, last = ("🖤", "🎀")
>>> first
'🖤'
>>> last
'🎀'
>>> others
[]

利用シーン:シーケンスを重複のないように分割

リストを重複のないように分割する場合、catch-allアンパックは、スライスやインデックスを使うよりもエラーを起こす危険が少ない。(Effective Python 第2版 項目13)

インデックスやスライスの 添字誤りは発生しない書き方 ですよね

小さなCSVファイルをヘッダと残りの行に分けることも🏃‍♂️

# https://github.com/bslatkin/effectivepython/blob/4ae6f3141291ea137eb29a245bf889dbc8091713/example_code/item_13.py#L144-L147
it = generate_csv()
header, *rows = it  # 注: rowsが指すリストを作るのにすべてのデータがメモリに展開されている
print('CSV Header:', header)
print('Row count: ', len(rows))

私はあんまり使いません(CSVに限らず大きいファイルをジェネレータで扱う)

まとめ🥟:アンパック代入と *

  • シーケンスの長さと同じだけ変数を並べる シーケンスのアンパック

  • * を付けた変数1つに 残りをまとめ られる:catch-allアンパック

お品書き:Pythonとアスタリスク 🐍🌟💫🐍🌟💫

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数

  5. 関数と * その2: 可変長 引数

閑話🍵: パック/アンパック

パック/アンパック

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

  • シーケンスのアンパック:中の物を 取り出す (本編で紹介済み)

Pythonチュートリアル 5.3.

タプルのパック (tuple packing)

>>> t = 12345, 54321, "hello!"
>>> type(t)
<class 'tuple'>
>>> t
(12345, 54321, 'hello!')

逆の演算がシーケンスのアンパック

タプルの肝は カンマ

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

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

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

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

カッコつけずに書いたタプルで パック

Pythonでは複数同時の代入ができる

>>> taki = "瀧"
>>> mitsuha = "三葉"
>>> taki, mitsuha = mitsuha, taki
>>> taki
'三葉'
>>> mitsuha
'瀧'

「「入れ替わってるーー!!」」

さら知り🏃‍♂️:複数同時代入の裏にパック/アンパック

taki, mitsuha = mitsuha, taki
  1. 右辺でタプルのパック ("三葉", "瀧")

  2. シーケンスのアンパック

詳しくは『Effective Python 第2版』項目6 をどうぞ!

3️⃣アンパック演算子としての *, **

使ったことある方〜?🙋‍♂️🙋‍♀️

2つのアンパック演算子

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

  • (B)辞書アンパック演算子

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

3️⃣-🅰️ イテラブルアンパック演算子 *

イテラブルとは(用語集 より)

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

「反復可能オブジェクト」

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

  • シーケンス(リスト、文字列、タプルなど)

  • 辞書

  • ファイルオブジェクト

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

イテラブルアンパック演算子を使ってみる

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

タプルと同じ要素を持つリストを作る方法の1つ

イテラブルアンパック演算子で新しいリストを作る

>>> [*(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)

カッコつけずに書けます(タプルの肝はカンマ)

>>> *(1, 2), 3  # (*(1, 2), 3) をカッコつけずに
(1, 2, 3)
>>> *(1, 2),  # (*(1, 2),) をカッコつけずに
(1, 2)
>>> *(1, 2)  
  File "<stdin>", line 1
SyntaxError: can't use starred expression here

辞書にイテラブルアンパック演算子を使うと

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

キー が取り出されます

3️⃣-🅱️ 辞書アンパック演算子 **

辞書アンパック演算子を使ってみる

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

キーと値の組が同じ新しい辞書を作った

キーと値の組を 追加 した辞書を作れます

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

アンパック演算子は 一度に複数 使える!

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

>>> [*(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で回しているマージしているんですけど、もっとシュッと書けないですかね?」

  • 💡それ、 辞書アンパック演算子 でできます!!

2つの辞書をマージして新しい辞書を作りたい

>>> d1, d2 = {"x": 11, "y": 22}, {"v": 101, "y": -22}
>>> {**d1, **d2}  # キーが重複したら後勝ち
{'x': 11, 'y': -22, 'v': 101}

プロポーザルの「私は先日2つの辞書のマージが * でスッキリ書けました」回収✌️

まとめ🥟:アンパック演算子としての *, **

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

*

新しい リストやタプル を作れる

辞書 アンパック演算子

**

新しい 辞書 を作れる

一度に複数使える!(PEP 448)

さら知り🏃‍♂️:再びの『Python実践入門』がオススメ

  • 4.9 そのほかの型を表す概念

  • シーケンスやイテラブルなど説明

  • Pythonの型を学ぶのにオススメのリソース知っている方はぜひ教えてください!🙏

さら知り🏃‍♂️:過去のPyCon JPからのオススメトーク

お品書き:Pythonとアスタリスク 🐍🌟💫🐍🌟💫

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数

  5. 関数と * その2: 可変長 引数

閑話🍵: 辞書のマージ

「2つの辞書をマージして新しい辞書を作りたい」

Python 3.9からは | 演算子でできます

>>> d1, d2 = {"x": 11, "y": 22}, {"v": 101, "y": -22}
>>> d1 | d2  # キーが重複したら右の辞書の値が残る
{'x': 11, 'y': -22, 'v': 101}

https://www.python.jp/pages/python3.9.html

Guido氏も忘れてた {**d1, **d2}

I know I myself had forgotten about it ({**d1, **d2}) when this thread started!

PEP 584 – Add Union Operators To dict より

There should be one-- and preferably only one --obvious way to do it.

(IMO)辞書のマージは

  • Python 3.9以降は | を使う

  • Python 3.7, 3.8でも {**d1, **d2} でできます(3.8は2024年10月にリタイア)

さかなー🐟

折り返してます! ストレッチ しましょう🙌

4️⃣関数と * その1: 専用 引数

使ったことある方〜?🙋‍♂️🙋‍♀️

関数定義における 専用 引数

  • (A)キーワード専用引数(*

  • (B)位置専用引数(/

関数の呼び出しの話から 始めましょう

関数には2つの「引数」

仮引数

関数の 定義 で使う引数

実引数

関数 呼び出し で渡す値

関数に渡す値(実引数)は2通り

  • 位置 引数

  • キーワード引数(= 名前付き 引数)

仮引数は 位置またはキーワード引数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.1.

仮引数にデフォルト値を指定

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

平均体重(日本の男性 国民健康・栄養調査 2018年)を指定

必須の仮引数だけを指定して呼び出し

>>> calculate_bmi(1.58)  # 位置引数で指定
24.75564813331197
>>> calculate_bmi(height_m=1.58)  # 名前付きで指定
24.75564813331197

オプションの仮引数も指定した呼び出し例

>>> calculate_bmi(1.58, 46)  # オプションの引数に位置引数で値を渡す
18.426534209261334
>>> calculate_bmi(1.58, weight_kg=46)  # オプションの引数に名前付きで値を渡す
18.426534209261334

オプションの仮引数が複数あるとき

>>> def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1):
...     ...

>>> flow_rate(weight_diff, time_diff, 3600, 2.2)
  • 位置引数で値を渡すと、値の意味が分かりづらい

Effective Python 第2版 項目23のコード より

オプションの仮引数が複数あるとき

>>> flow_rate(weight_diff, time_diff, period=3600, units_per_kg=2.2)
  • 名前付き で渡すと 分かりやすい

  • デフォルト値を使わない引数だけを指定できる(units_per_kg=1.5 のみ指定)

関数に 追加 する引数は オプション引数

Effective Python 第2版』項目23「キーワード引数にオプションの振る舞いを与える」

  • 必須引数として追加すると既存の呼び出しを壊してしまう

  • (すでに見たように)オプション引数は 名前付き で渡す

    • 分かりやすい・ 必要なものだけ 指定できる

💡仮引数に位置引数で渡すか、名前付きで渡すかを指定できる!

仮引数デフォルトの「位置またはキーワード引数」に代えて

  • (A)キーワード専用引数(*

  • (B)位置専用引数(/

4️⃣-🅰️ キーワード専用引数

  • 名前付き 引数で渡すのを 強制 する

  • 名前付きで渡すので、渡す順番は自由

関数定義でキーワード専用にする

>>> # 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: オプション(=デフォルト値あり)の引数 は、キーワード専用が望ましいのではないか

    • オプション引数に位置引数で渡すのは分かりにくさを生むので、それをさせない

組み込み関数におけるキーワード専用引数🏃‍♂️

maxminkey 引数

>>> fruits_prices = {"apple": 100, "banana": 50, "melon": 777}
>>> min(fruits_prices)  # keyが最小(アルファベット順で先頭)
'apple'
>>> min(fruits_prices.items())  # keyが最小(アルファベット順で先頭)
('apple', 100)
>>> min(fruits_prices.items(), key=lambda pair: pair[1])
('banana', 50)

4️⃣-🅱️ 位置専用引数

  • 位置 引数で渡すのを 強制 する

  • 名前付きでは渡せなくなる(名前を知らずに使って ほしい)

  • Python 3.8 から(PEP 570

関数定義で位置専用にする

>>> # 2つの引数をどちらも位置専用とした
>>> 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)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: calculate_bmi() got some positional-only arguments passed as keyword arguments: 'height_m, weight_kg'

位置専用引数の使い所

  • 将来、 引数名を変更可能 にするために使う

  • 引数名が有用ではない場合にキーワード引数としての利用を排除する(What's New In Python 3.8

組み込み関数における位置専用引数🏃‍♂️

sum の第1引数 iterable

>>> sum(range(1, 11))
55

IMO: iterable= と指定しないほうがスッキリ

まとめ🥟:関数と * その1: 専用引数

仮引数

制限

利点

*

キーワード専用

位置引数で渡せない

呼び出し意図が明確。オプション引数と相性よい

/

位置専用

名前付き引数で渡せない

引数名が将来変えられる

お品書き:Pythonとアスタリスク 🐍🌟💫🐍🌟💫

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数 ✅

  5. 関数と * その2: 可変長 引数

閑話🍵:標準ライブラリの *, **

6つ紹介🏃‍♂️

glob (1/6)

>>> glob.glob("**/*.txt", recursive=True)  
['2.txt', 'sub/3.txt']
.
├── 1.gif
├── 2.txt
└── sub/
    └── 3.txt

glob.glob

  • "*" は「shell-style wildcards」

    recursive が真の場合、パターン "**" はあらゆるファイルや0個以上のディレクトリ、サブディレクトリおよびディレクトリへのシンボリックリンクにマッチします。

pathlib (2/6)

>>> sorted(Path('.').glob('**/*.py'))  

Path.glob

fnmatch (3/6)

Unix のシェル形式のワイルドカードに対応しています

re(正規表現の *) (4/6)

直前の正規表現を 0 回以上、できるだけ多く繰り返したものにマッチ

sqlite3 (5/6)

>>> for row in cur.execute("SELECT * FROM stocks ORDER BY price"):  
...     print(row)

https://docs.python.org/ja/3/library/sqlite3.html#tutorial

dataclasses (6/6)

  • Python 3.10から KW_ONLYキーワード専用 フィールドができるように!(* 相当)

>>> @dataclass
... class Point:
...     x: float
...     _: KW_ONLY
...     y: float
...     z: float
...
>>> p = Point(0, y=1.5, z=2.0)

目撃報告お待ちしています!

アスタリスクはまだまだ隠れていると思います

📺提供:にっきー(お前、誰よ 3/3)

  • ソフトウェアを作ることに関心があります

    • ソフト= 変更容易

    • 私の書いてきたコード、変更難度高すぎるハードウェアなんですよ😢

📺提供:にっきー(お前、誰よ 3/3)

5️⃣関数と * その2: 可変長 引数

使ったことある方〜?🙋‍♂️🙋‍♀️

関数定義における 可変長 引数

  • (A)可変長位置引数(引数に * を付ける)

  • (B)可変長キーワード引数(引数に ** を付ける)

ふだんよく使う あの関数から 始めましょう

組み込み関数 print

>>> print("We (Uzabase) are hiring!")  # 位置引数1個
We (Uzabase) are hiring!
>>> print()  # 位置引数0個

>>> print("Hello", "PyCon", "JP", "2022")  # 位置引数4個
Hello PyCon JP 2022

引数を 何個でも 渡そう

print のドキュメントによると

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
  • 引数 *objects

  • これが 可変長位置引数

5️⃣-🅰️ * を付けた引数

  • 「任意の個数の位置引数が与えられる」引数(用語集「仮引数」

  • *args には 0個以上 の位置引数が与えられる(オプションになっている!)

  • * を付けた引数の次の引数からは キーワード専用 になる

* を付けた引数を持つ関数

>>> def f(*args):
...     print(type(args))

>>> f(1, 2, 3)  # * を付けた引数はタプルを指す!
<class 'tuple'>

何個でも位置引数を受け取れる関数!

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

f-stringの = 指定子(What's New In Python 3.8

呼び出し例

>>> f(1, 2, 3)
args=(1, 2, 3)
>>> f()
args=()

では、何個でもキーワード引数を与えられる関数は?

  • 組み込みの dict

>>> dict(apple=100, banana=50)
{'apple': 100, 'banana': 50}
>>> dict()
{}

dict のドキュメントによると

dict(**kwarg)
  • **kwarg これが 可変長キーワード引数

  • なお、(組み込み関数のドキュメントに載ってますが)dictはクラスです

5️⃣-🅱️ ** を付けた引数

  • 「任意の個数のキーワード引数が与えられる」引数(用語集「仮引数」

  • **kwargs には 0個以上 のキーワード引数が与えられる(オプションになっている!)

** を付けた引数を持つ関数

>>> def f(**kwargs):
...     print(type(kwargs))

>>> f(apple=100, banana=50)  # ** を付けた引数は辞書を指す!
<class 'dict'>

何個でもキーワード引数を受け取れる関数!

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

呼び出し例

>>> f(apple=100, banana=50)
kwargs={'apple': 100, 'banana': 50}
>>> f()
kwargs={}

可変長引数を持った関数

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

可変長位置引数も可変長キーワード引数も

復習: args はタプル、 kwargs は辞書

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

5️⃣-🅰️ 手元にはイテラブル

>>> numbers = [1, 2, 3]  # リストはシーケンスなので、イテラブル
>>> f(numbers)
args=([1, 2, 3],)
kwargs={}
  • args は要素1のタプル

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

リストの要素全部を位置引数として渡したい

>>> f(???)  
args=(1, 2, 3)
kwargs={}
  • args が要素3のタプルとなってほしい

インデックスを指定してできなくもないけれど😫

>>> f(numbers[0], numbers[1], numbers[2])
args=(1, 2, 3)
kwargs={}

numbers の要素数が変わったらこのままだと動かない

イテラブルアンパック演算子 でできます!

>>> f(*numbers)  # イテラブルアンパック演算子!
args=(1, 2, 3)
kwargs={}

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

他のイテラブルでも

>>> f(*(4, 5))  # タプルはシーケンスなので、イテラブル
args=(4, 5)
kwargs={}
>>> f(*{"apple": 100, "banana": 50})  # 辞書はイテラブル
args=('apple', 'banana')
kwargs={}

5️⃣-🅱️ 手元には辞書

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

辞書のキーと値を キー=値 と名前付きで全部可変長キーワード引数 kwargs に渡したい

辞書アンパック演算子 でできます!

>>> f(**fruits_prices)  # 辞書アンパック演算子!
args=()
kwargs={'apple': 100, 'banana': 50}

2種のアンパック演算子は一緒に使えます!

>>> f(*numbers, **fruits_prices)  # ただし、この順に限る
args=(1, 2, 3)
kwargs={'apple': 100, 'banana': 50}

(再びの)アンパック演算子は 一度に複数 使える!

  • 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}

まとめ🥟:関数と * その2: 可変長引数

仮引数

種類

実引数

利点

* 付き

可変長 位置

*イテラブル

要素を全部渡せる

** 付き

可変長 キーワード

**辞書

キー=値 で全部渡せる

さら知り🏃‍♂️:海外のPyConより

  • nikkieが勝手にPythonのYodaと思っている、Reuven氏のトーク

  • PyCon APAC 2022 「Function Dissection Lab」

  • 関数 オブジェクト で関数の仕組み(デフォルト値などなど)が明らかにされ、実に面白い🙌

Pythonとアスタリスク 🐍🌟💫🐍🌟💫 駆け抜けましたね⚡️

  1. 二項算術演算子としての *, **

  2. アンパック代入と *

  3. アンパック演算子としての *, **

  4. 関数と * その1: 専用 引数 ✅

  5. 関数と * その2: 可変長 引数 ✅

閑話コンテンツたち🍵

  • from module_or_package import * オススメしません

  • タプルのパック↔️シーケンスのアンパック

  • 辞書のマージはPython 3.9からは |

  • 標準ライブラリの *, **

学んだもの(ぜひ血肉にしてください)

  • シーケンス、イテラブル、特殊メソッド

  • catch-allアンパック

  • 関数の仮引数の *, /, *args, **kwargs

  • アンパック演算子を使って、新しいシーケンスを作る & 関数の可変長引数に渡す

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

May the Stars🌟 be with you
星🌟は君の頭上に輝くよ

Appendix

  • References

  • Overrun contents(さらなる *

References

スライドになるべくリンクを載せていますが、特に参照したものをまとめます

Pythonドキュメント

翻訳ありがとうございます❤️

書籍

元となったアウトプット(LT会に感謝❤️)

nikkieによる関連アウトプット

さらなる *

本編(プロポーザル)に盛り込みきれなかった * を紹介

Exception Group

Python文法(Grammar)の *

  • 拡張BNF

  • PEG(Python 3.9〜)

拡張BNFやPEGでの *

CPythonに潜ったらもっと * と出会えるのでは🤩

例えば TokensSTAR

To Be Continued...

EOF