Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@spicy_laichi

PythonでProtocolを使って静的ダック・タイピング

この記事はニフティグループ Advent Calendar 2019の14日目の記事です。
昨日は@mito1296さんの「Terraformで立てたec2インスタンスをAnsibleで管理してみる」でした。
同様の問題に以前悩まされたことがあったのでタグ付けは非常に綺麗な解決方法だなと感心しました。
機会があれば、うちのチームでも導入したいですね!!

TD;LD

Pythonは型安全で無いので大きめのプロジェクトになると心理的な安全が担保できないので敬遠していました。
しかし、Python3.5以降typingが導入されmypyと組み合わせることで、少し大きめのプロジェクトでもコーティングする際に心理的な安全を得ることができるようになったので中規模の開発に採用できるようになりました。

今回は、python3.8からtyping.Protocolが正式に追加されたようなので紹介してみます。

typing.Protocolとは

詳しくは、以下を見てください。
https://docs.python.org/ja/3/library/typing.html#typing.Protocol

今までPythonでダック・タイピングをする際はabcを使っていたのですが。場合によってはtyping.Protocolほうが自然に実装できそうです。

https://qiita.com/kaneshin/items/269bc5f156d86f8a91c4
こちらの記事でも紹介されているダックタイピングの問題を解消しているのかな?と思います。

試してみる

Protocolの使い方は簡単で、抽象クラスとなるものに継承させればOKです。
以下の例ですとAnimalのクラスに該当します。
Animalの具象クラスにはstrを返すsoundメソッドが実装されることを期待しています。

Dogはキチンとダックタイピングされていますが、Booksoundが実装されておらず、Catsoundの戻り値が誤って実装されています。

test.py
from typing import Protocol


class Animal(Protocol):
    def sound(self) -> str:
        ...


class Dog():
    "soundが実装されている"

    def sound(self) -> str:
        return "Bow-wow"


class Book():
    "soundが実装されていない"

    def read(self) -> str:
        return "hogeeee"


class Cat():
    "soundは実装されているが、戻り値が違う"

    def sound(self) -> None:
        print("Meow")


def func(animal: Animal):
    animal.sound()


func(Dog())
func(Book())
func(Cat())

このコードをmypyで解析すると、きちんと解析されてErrorが出ていることが分かります。
Catに至っては、詳細なエラーが出ているので誤りが非常に見つけやすくなっています。

$ python -m mypy test.py
test.py:35: error: Argument 1 to "func" has incompatible type "Book"; expected "Animal"
test.py:36: error: Argument 1 to "func" has incompatible type "Cat"; expected "Animal"
test.py:36: note: Following member(s) of "Cat" have conflicts:
test.py:36: note:     Expected:
test.py:36: note:         def sound(self) -> None
test.py:36: note:     Got:
test.py:36: note:         def sound(self) -> str
Found 2 errors in 1 file (checked 1 source file)

最後に

typing.Protocolについて、簡単ですが紹介してみました。
abcでも似たようなことができるのですが、具象化クラスが継承する必要やアノテーションのつけ方が直感的ではないので場合によってはtyping.Protocolを使ったほうがいいかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
8
Help us understand the problem. What are the problem?