🥤

PythonにおけるProtocol(ダックタイピング)とABC(抽象化)の違い

2022/12/16に公開
1

Qiita 「Python Advent Calendar 2022」 18日目の記事です。
https://qiita.com/advent-calendar/2022/python

Pythonにおけるポリモーフィズムの実装で、「ABC(Abstract Base Class/抽象基底クラス)とProtocol(プロトコルクラス)の違いってなんだ?」と疑問に思ったので、実際に試して確認してみました。

ABCとProtocolとは何か?

Pythonの公式ドキュメント

https://docs.python.org/ja/3/library/typing.html

使いどころ

ABCとProtocolのつかいどころを、ざっくり書くと下記のようになります。

  • ABC
    • CやJavaで言うところの、抽象クラスを作る時に使う
  • Protocol
    • ダックタイピングをするときの型定義に使う

何が違うか?

ABCもProtocolもポリモーフィズムを実現する為に使いますが、微妙に考え方が違います。
例えば、人間絵を描くという関係を例に違いを見てみます。

  • ABCを使う場合は、次のように考えます
    • 人間であるならば、絵を描くことができる
  • Protocolを使う場合は、次のように考えます
    • 絵を描くことができるならば、人間である

細かい話を書くと混乱してくるので、実際にコードを書いてみましょう。

コードを書いてみた

実際に書いたもの

単純なコードですが、一応GitHubにも置いておきます。
https://github.com/k-ibaraki/duck-typing-python-sample/

前提

poetryを使っています。
エディタはVSCodeを使い、型チェックのためにmypyをインストールしておきます。

poetry add mypy

状況設定

前述の人間絵を描くの関係に対して、下記の4人を設定します。

  • 田中(Tanaka)
    • 絵が描ける人間
  • 山田(Yamada)
    • 絵が描ける人間
  • 鈴木(Suzuki)
    • 絵が描けない人間
  • AI
    • 絵が描けるが、実は人間ではない

ABCを試す

  • 必要なライブラリをimportします。
from abc import ABC, abstractmethod
  • 人間を表す抽象クラスHumanを作ります。
    • 抽象基底クラスABCを継承して定義します。
    • 絵を描くdrawメソッドを定義します。@abstractmethodをつけたメソッドは抽象メソッドになります。中身は定義しないのでpassします。
class Human(ABC):
    @abstractmethod
    def draw(self) -> None:
        pass
  • 絵が描ける人間の田中と山田を表すクラスを作ります。
    • さきほど定義したHumanを継承して、drawメソッドの中身を書きます。
class Tanaka(Human):
    def draw(self) -> None:
        print("😁")


class Yamada(Human):
    def draw(self) -> None:
        print("👶")
  • 絵が描けない人間の鈴木を表すクラスを作ります。
    • 絵が描けないので、drawではなく適当に別なメソッドを定義しておきます。
class Suzuki(Human):
    def write(self) -> None:
        print("こんにちは")
  • 絵が描けるAIを表すクラスを作ります。
    • 人間ではないので、Humanは継承しません。
    • 絵が描けるので、drawメソッドを定義します。
class AI:
    def draw(self) -> None:
        print("👽")
  • 人間が絵を描く関数を定義します。
def run_draw(human: Human) -> None:
    human.draw()
  • 田中、山田、鈴木、AIのインスタンスを作ります。
tanaka: Tanaka = Tanaka()
yamada: Yamada = Yamada()
suzuki: Suzuki = Suzuki() 
ai: AI = AI()

ここで、VSCodeに怒られます。

鈴木に対して怒っていますね。
人間であるならば絵を描くことができる」という前提がありますので、
人間なのに絵を描くことができない」鈴木は存在できません。
残念ですが消えてもらいましょう。

tanaka: Tanaka = Tanaka()
yamada: Yamada = Yamada()
# suzuki: Suzuki = Suzuki() 
ai: AI = AI()
  • 生き残った田中と山田とAIに絵を描かせましょう。
run_draw(tanaka)
run_draw(yamada)
run_draw(ai) 

ここで、VSCodeに怒られます。

AIに対して怒っていますね。
run_draw関数は「人間が絵を描く関数」と定義しました。
AIは絵は描けますが人間ではありませんので、お断りされてしまいます。
AIにも消えてもらいましょう。

run_draw(yamada)
run_draw(tanaka)
# run_draw(ai) 
  • 実行します。
% poetry run python src/abc_sample.py
😁
👶

ABCまとめ

Protocolを試す

続いて、Protocolの場合はどうなるかを試します。

  • 必要なライブラリをimportします。
from typing import Protocol
  • 人間を表すプロトコルクラスHumanを作ります。
    • Protocolを継承して定義します。
    • 絵を描くdrawメソッドを定義します。中身は定義しないので...とします。
class Human(Protocol):
    def draw(self) -> None:
        ...
  • 絵が描ける人間の田中と山田を表すクラスを作ります。
    • ABCと違って継承は不要です。drawメソッドの中身を書きます。
class Tanaka:
    def draw(self) -> None:
        print("😁")


class Yamada:
    def draw(self) -> None:
        print("👶")
  • 絵が描けない人間の鈴木を表すクラスを作ります。
    • 絵が描けないので、drawではなく適当に別なメソッドを定義しておきます。
class Suzuki:
    def write(self) -> None:
        print("こんにちは")
  • 絵が描けるAIを表すクラスを作ります。
    • 絵が描けるので、drawメソッドを定義します。
class AI:
    def draw(self) -> None:
        print("👽")
  • 人間が絵を描く関数を定義します。
def run_draw(human: Human) -> None:
    human.draw()
  • 田中、山田、鈴木、AIのインスタンスを作ります
tanaka: Tanaka = Tanaka()
yamada: Yamada = Yamada()
suzuki: Suzuki = Suzuki() 
ai: AI = AI()

先ほどと違い、ここでは怒られません。

  • 田中と山田と鈴木とAIに絵を描かせましょう。
run_draw(yamada)
run_draw(tanaka)
run_draw(suzuki) 
run_draw(ai) 

ここで、VSCodeに怒られます。

鈴木に対して怒っていますね。
絵が描けない鈴木には、絵を描く為の関数であるrun_drawを実行することは許されません。
残念ですが今回も消えてもらいましょう。

run_draw(yamada)
run_draw(tanaka)
# run_draw(suzuki) 
run_draw(ai) 

ここで注目はAIです。ABCでは脱落したのにProtocolでは生き残っています。
絵が描けるなら人間である(→本当に人間であるかはどうでもよい)」というのがProtocolです。
このような考え方を世の中ではダックタイピングといいます。

  • 実行します。
% poetry run python src/protocol_sample.py 
👶
😁
👽

Protocolまとめ

ググると「ABCを使ってダックタイピングをする」系の記事も出てきますが、それは抽象化でありダックタイピングとは呼べないです。
かといって型を定義しない世界でダックタイピングをすることは、個人開発や小規模開発ならいいかもしれませんが一般的なプロダクト開発ではツライことになります。
Pythonでダックタイピングをしたいときは、Protocolを使うべきだと思います。

全体まとめ

ABCはAI絵師は許容できない派、ProtocolはAI絵師でも問題ない派

最後に

全世界の鈴木さん、ごめんなさい。ただの例示であり他意はないです。
また、絵が描けるかどうかで人を区別する意図もありません。
私は絵は描けません。

Discussion