見出し画像

【Python】**二重アスタリスク〜初学者に向けて〜

言い訳ですが、網羅的系統的に学習したわけでなく、独学なので、基礎がおぼついていない私にような未熟者には、不意に出てくると「なんだっけこれ」ってなるので、メモがてら・・・

いわゆる関数定義における可変長のキーワード引数じゃないよ

まあ大体、学習の序盤に出てくるのが、いわゆる可変長引数(*args, **kwargs)だろう。
この辺りは、参考サイトとかにも、

「何だこれ」とつまずきやすいのが、関数の引数の*argsと**kwargs。

と書いてあったりして、その分、入門的な記事もたくさんあるので、それほど問題ではないだろう。

簡単に言うと関数定義の時などに、(仮)引数として、アスタリスク*や二重アスタリスク**をつけることで、可変な任意個の(実)引数を受け取ることができる、と言うもので、「args」「kwargs」とかは任意の変数名で定義すれば良いので、なんでも良い。
※ちなみにご存知かと思いますが、*hogeは、単純に可変長引数についての定義であり、**fugaは、キーワード引数、つまりdictなどに対して、key=valueの形で実引数を渡すことができる定義です。

アンパック(代入)って調べたら出てくるよ

上で書いたように、初学時によく出てくるいわゆる可変長引数についての話は、関数などの「定義」時に深く関わってくるものだ。
しかし、今回主題としているのは、どちらかというと代入、というか先ほどの話に絡めるとむしろ関数やクラスに実際にパラメータ(実引数)を渡す時の話である。

要は、Listやdict型のデータを展開して(unpack)取得できる、ということである。
参考サイトにもあるように、Listであれば、*listのような形で中身を展開した状態で取得できる。

個人的な実感としては、printなどで、コンソールなどに標準出力(デバック時など)する、というよりもそれこそ、引数として渡す(いわゆるアンパック代入)という場面での方が、業務や現場で出くわす印象が強い。

面倒なので参考サイトに載っているので、詳細は省くが、下記のようなdictに対してアンパックすると・・・

dict = {'key1': 'value1', 'key2': 'value2'}
# dictに対して一重アスタリスク*でアンパック
print(*dict)  
# >>> key1 key2
# dictの値に対して一重アスタリスク*でアンパック
print(*dict.value)
# >>> value1 value2

# dictに対して二重アスタリスク**でアンパック <- 今回の主題です
print(**dict)
# >>> key1: value1 key2: value2
# print(dict)であれば{key1: value1, key2: value2}となるので、展開されていることが分かるだろう

と言う感じになると思う。

何が嬉しいの?

dictを引数として渡す場面とかの記述が、簡略化できるっていうのは、当たり前なので(というかそのためのものなんじゃないの?)、置いておいて、なんというか個人的な実感に過ぎないけど、FastAPIの公式チュートリアルとかPydanticの公式ドキュメントにある使い方が、恩恵を割と感じれたので共有しておきます。

dictの更新と結合を簡単に記述できる:

Pydantic公式ドキュメント:

# バリデーション&自動型変換などに対応してくれるPydanticスキーマの定義
class User(BaseModel):
  id: int name = 'John Doe'
  signup_ts: Optional[datetime] = None
 friends: List[int] = []
# 引数で渡すためのdictを生成
external_data = { 'id': '123', 'signup_ts': '2019-06-01 12:22', 'friends': [1, 2, '3'], }

# 先ほど定義したスキーマ(クラスオブジェクト)にアンパック代入で、スキーマインスタンス(オブジェクト)を生成
user = User(**external_data)

# 実際にdictの値が対応する属性(フィールド)に渡されているか確認
print(user.id)
#> 123

これは結構、そのままというかわかりやすいですね。

FastAPI公式チュートリアル:

from typing import List, Optional
from pydantic import BaseModel

# この辺はPydanticの公式で見たのと同じようなスキーマ(オブジェクト)の定義
class ItemBase(BaseModel):
    title: str
    description: Optional[str] = None


class ItemCreate(ItemBase):
    pass


class Item(ItemBase):
    id: int
    owner_id: int

    class Config:
        orm_mode = True

# パスオペレーション関数に流用しやすいようにutility関数を用意
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
    # models.Item()はここには書いてないけど、SQLAlchemyのモデル定義をmodels.pyモジュールで定義している感じです。
    db_item = models.Item(**item.dict(), owner_id=user_id)  # <- ここに注目! 
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

先ほどの参考ページ(FastAPIの公式)には次のように書いてあります。

Tip
Instead of passing each of the keyword arguments to Item and reading each one of them from the Pydantic model, we are generating a dict with the Pydantic model's data with:

item.dict()

and then we are passing the dict's key-value pairs as the keyword arguments to the SQLAlchemy Item, with:

Item(**item.dict())

And then we pass the extra keyword argument owner_id that is not provided by the Pydantic model, with:

Item(**item.dict(), owner_id=user_id)

要は、SQLAlchemyのモデルのインスタンスオブジェクトを生成する際に、
Pydanticのスキーマオブジェクト(スキーマインスタンス)からdictを生成して、それをdictのまま引数として渡すことができる、と言うものだ。

そうすることで、Pydanticのスキーマインスタンスから属性を一個一個取り出して、代入していく必要がなく(スキーマオブジェクトをdictに変換しなくてはいけないが)、スキーマを丸投げしてモデルに渡すことができるよ、と言う感じだ(と思う多分)。

語学力、文章力、論理数理的素養に欠ける私の説明は頼りないと思いますので真面目な方は、参考サイトを覗いていただければと思う。

つ 大変いい加減かつナイーブ(デリケート?)な筆者が書いております故、誤った記述などがあった場合は、なるべくソフトに優しくふんわり丁寧にご指摘、ご教授のほどいただければと思います。

いいなと思ったら応援しよう!