BackEnd

Pythonのポリモーフィズム、ABCにするか? プロトコルにするか?

投稿日:

はじめに

Pythonでポリモーフィズムを実現する方法として、ABC(Abstract Base Classes) または Protocolがあります。個人的には、最近はProtocolを選ぶことの方が多いのですが、ちゃんと比較してみたことがなかったので、調べて比較してみました。

ズバリ、ABCとProtocolは何が違うか?

ざっくり、このような目的・使いどころです。

  • ABC (Abstract Base Classes)
    • その名の通り、抽象基底クラスを定義するためのもの (メタクラスとして実装されている)
    • 抽象メソッドを示すデコレータをメソッドに付け加えることで、継承先にメソッド実装を強制させることができる
    • Javaのabstractクラスと似たような概念
  • Protocol (Python 3.8以降)
    • 静的タイプチェッカーがダックタイピングを認識するためのクラスを定義できる
    • typingに実装されている型ヒント機能の一部
    • Golangのinterfaceと似たような概念 (異なるところもあります)

それぞれの使い勝手をおさらい

ABC

abc.ABC を継承することで抽象クラスを作ることができます。(ちなみに、ABCはメタクラスとして ABCMeta を指定してくれるヘルパークラスです。)

@abstractmethod がついたメソッドは抽象メソッドとなり、継承先クラスでの実装が強制されます。そうでないメソッドは非抽象メソッドとなり、継承先クラスでの実装は強制されませんし、実装しない場合はこの抽象メソッドの実装が実行されます。

ここからは、実装クラスと抽象型を引数のとる関数、そしてその関数の呼び出し時に実装クラスのインスタンスを渡す部分をおさらいします。

ちなみに、実装クラスが抽象メソッドの実装を満たしていない場合は、インスタンス生成時にランタイムエラーが発生します。試しに、findメソッドの実装を削除してみます。

Protocol

typing.Protocol を継承することで継承することでプロトコルを作ることができます。

プロトコルを使った場合でも、ABCの非抽象メソッドのようにデフォルト実装を持たせることができます。ただし、このデフォルト実装が実行されるのはこのプロトコルを継承した場合のみであり、そうでなければ実装クラス自身で実装する必要があります。

以下は、継承せずにプロトコルを実装した例です。

先ほどと同じように、findメソッドの実装をやめてみると、mypyやpylanceといった静的タイプチェッカーがエラーを出力します。

もっとも、Protocolはタイプヒント機能の一種であるため、実際に実行してみるとインスタンス生成自体はできますが、その後のfindメソッドの呼び出し時に AttributeError: 'DBUserRepository' object has no attribute 'find' というランタイムエラーが発生します。

このように、継承をすることなくプロトコルを実装することができ、タックタイピングの嬉しい点である、継承による複雑化を避けることができます。

実装クラスからの継承

プロトコルといっても、結局はクラスなので継承することもできます。以下が継承してみた実装クラスです。

継承すると、以下のようなできるようになります。

  • プロトコルのデフォルト実装を使用することができるようになる (ABCのような使い方)
  • IDEのジャンプ機能が使えるようになる (プロトコルの各メソッド↔︎実装クラスの各メソッド)

個人的には2つ目が嬉しい点で、継承しなければ、IDEのジャンプ機能でプロトコルの実装クラスを追うことは不可能です。そのため、プロトコルを使う場合でも、継承した方が実際のところは作業しやすいのではないかと思います。

どっちを使うか?

基本的には、冒頭に書いたように以下のような使い分けになると思います。

  • クラスの抽象化をしたいのであればABC
  • ダックタイピングをしたいのであればProtocol

その上で、基本的にはProtocolを使った方が良いと思いました。その理由は、以下のとおりです。

  • 継承が不要であるため、クラスの階層構造が生まれない
  • その上で、必要であれば、継承することでABCの非抽象メソッドのようなことをすることもできる

こういった実装がPythonになされている背景には、クラスを持たないプログラミング言語の台頭があると思います。継承を好まない人にもPythonを気持ちよく書けるように、ダックタイピングが取り入れられたのではないかと考えます。
実際、ProtocolのPEPでは、TypeScriptやGolangが引き合いに出されています。

さいごに

Pythonは歴史が長いため、目的を達成するための手段が複数用意されていることがあります。その中でどれが最適なのかを調べると奥が深くて面白いため、今後も記事にしていきたいと思います。

おすすめ書籍

エキスパートPythonプログラミング 改訂4版 (アスキードワンゴ) Effective Python 第2版 ―Pythonプログラムを改良する90項目 動かして学ぶ!Python FastAPI開発入門

blog-page_footer_336




blog-page_footer_336




-BackEnd

執筆者:

免責事項

このブログは、記事上部に記載のある投稿日時点の一般的な情報を提供するものであり、投資等の勧誘・法的・税務上の助言を提供するものではありません。仮想通貨の投資・損益計算は複雑であり、個々の取引状況や法律の変更によって異なる可能性があります。ブログに記載された情報は参考程度のものであり、特定の状況に基づいた行動の決定には専門家の助言を求めることをお勧めします。当ブログの情報に基づいた行動に関連して生じた損失やリスクについて、筆者は責任を負いかねます。最新の法律や税務情報を確認し、必要に応じて専門家に相談することをお勧めします。


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

laravel logo

DeployerでLaravelをデプロイ! 初期設定〜レシピのカスタマイズまで

1 はじめに2 Deployerの導入2.1 前提条件2.2 インストール3 デプロイの設定3.1 デプロイスクリプト3.2 サーバサイドの設定3.3 デプロイコマンド3.4 Deployerのディレ ...

Go言語

Go言語の基礎〜基本構文その1〜

1 はじめに2 変数2.1 変数の定義2.2 暗黙的な定義2.3 varと暗黙的な定義2.4 ローカル変数とパッケージ変数3 定数3.1 const3.2 iota4 関数4.1 関数定義の基本4.2 ...

Go言語

goroutineとchannelとContext

1 はじめに2 並行処理と並列処理3 goroutine4 channel4.1 channelからデータ受信4.2 for-range でのデータ受信4.3 複数のchannelを受信4.4 buf ...

Go言語

Go言語、ゴルーチン(goroutine)で並列処理を

1 はじめに2 ゴルーチン2.1 go文2.2 ゴルーチンの終了条件2.3 WaitGroup3 チャネル3.1 チャネルの型3.2 チャネルの生成3.3 チャネルの送受信3.4 チャネルとゴルーチン ...

rails

Railsのバリデーション

1 はじめに2 基本的なバリデーション3 EachValidatorクラス4 Validatorクラス5 autoload_pathsの編集6 さいごに はじめに 今回はRailsのActiveRec ...

フォロー

blog-page_side_responsive

2024年7月
 123456
78910111213
14151617181920
21222324252627
28293031  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。