12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonAdvent Calendar 2020

Day 14

PythonビルトインのNotImplementedとは何なのか。

Last updated at Posted at 2020-12-13

本記事はPython Advent Calendar 202014日目の記事です。

Pythonの本を読んでいたらNotImplementedという記述が出てきました。
初めて見るビルトインのものだったのでこのNotImplementedについて色々調べてみます。

公式ドキュメントによると・・・

Python公式ドキュメントの「組み込み定数」のページに以下の記述がありました。

二項演算の (あるいはインプレースの) メソッドが NotImplemented を返した場合、インタープリタはもう一方の型で定義された対の演算で代用を試みます (あるいは演算によっては他の代替手段も試みます)。試行された演算全てが NotImplemented を返した場合、インタープリタは適切な例外を送出します。
組み込み定数

どうやらTrueやFalse、None、Ellipsisなどと同じようなビルトインの組み込み定数なようです。TrueやNoneなどは有名ですが、今まで存在を知りませんでした。

ドキュメントには「メソッドが NotImplemented を返した場合、インタープリタはもう一方の型で定義された対の演算で代用を試みます」と書かれています。これはどういった挙動をするものでしょう?

コードを書いて試してみる

等価の演算子(==)を使う形でコードを試してみます。クラスで等価演算子の内容を定義するには__eq__のメソッドを記述することで対応ができます。

AとBというクラスを作成し、A側の__eq__メソッドでは、(実際にはこういった内容が必要になることはほぼ無い変なコードですが検証ようとして)もし比較として指定された引数の値(obj)がAクラスのインスタンスもしくはBクラスのインスタンスであればTrueを返すようにしています。

一方でB側の__eq__メソッドではダイレクトにNotImplementedを返すようにしてあります。

※実行されたことが分かるように各

class A:

    def __eq__(self, obj: object) -> bool:
        print('A側の__eq__メソッドが実行されました。')
        if isinstance(obj, A):
            return True
        if isinstance(obj, B):
            return True
        return False


class B:

    def __eq__(self, obj: object) -> bool:
        print('B側の__eq__メソッドが実行されました。')
        return NotImplemented


a = A()
b = B()

Aクラスのインスタンスをa、Bクラスのインスタンスをbとしました。

まずはaとa同士、及びaとbを比較してみます。これは当たり前ですが普通にTrueになります。また、左辺側のメソッドが先に評価されるため、a側のメソッドのみが実行されていることが出力内容から分かります。

print('a == a:', a == a)
print('a == b:', a == b)
A側の__eq__メソッドが実行されました。
a == a: True
A側の__eq__メソッドが実行されました。
a == b: True

ではB側を前にする形でb == aとするとどうでしょう?

b側を左辺側に置いているので、NotImplementedが返却されるb側の__eq__メソッドが実行されます。

print('b == a:', b == a)
B側の__eq__メソッドが実行されました。
A側の__eq__メソッドが実行されました。

実行してみると、b側だけでなくa側のメソッドも実行されていることが分かります。このようにNotImplementedを使うことで「先に評価した側でNotImplementedが返却された場合には右辺側のメソッドも使って評価される」という挙動を実装することができます。

どんなケースで便利なのか

今まで知らなかったというのもあり、正直ビルトインや各種ライブラリの内部実装で不足していない感じではありますが、もし自分でなにか作る際には既存のもの(ビルトインのもの)などとの比較で、別のクラスのインスタンス同士を比較するケースなどで便利かなと思いました。

たとえば自作のクラスなどで、属性(_list_val)にリストを持つようなクラスが必要になったとします。以下のような感じです。

class OriginalArray:

    def __init__(self, list_val: list) -> None:
        self._list_val = list_val

このとき、このインスタンス自体の比較で内部の属性のリストを使いたいとします。

そのままリストと比較するとリストの値が一致していてもFalseになってしまいます。

list_val = [100, 200, 300]
original_arr = OriginalArray([100, 200, 300])
print(list_val == original_arr)
False

そこで、追加したクラスに__eq__メソッドを追加してリストと比較ができるようにするとします。

class OriginalArray:

    def __init__(self, list_val: list) -> None:
        self._list_val = list_val

    def __eq__(self, obj: object) -> bool:
        print('OriginalArrayの__eq__メソッドが実行されました。')
        if isinstance(obj, list):
            return obj == self._list_val
        if isinstance(obj, OriginalArray):
            return obj._list_val == self._list_val
        return False

ビルトインのリスト側ではOriginalArrayクラスとの比較はNotImplementedになるので、以下のように比較してもOriginalArray側の__eq__メソッドが実行され、等価条件の比較を行ってくれます。

list_val = [100, 200, 300]
original_arr = OriginalArray([100, 200, 300])
print(list_val == original_arr)
OriginalArrayの__eq__メソッドが実行されました。
True

これがもしビルトイン側でNotImplementedが指定されていない場合(ビルトイン側のクラスの__eq__メソッドだけでしか判定去れない場合)、list_val == original_arroriginal_arr == list_valで、比較順の違いだけで結果が変わってしまいます。

サードパーティのライブラリなどを作る際にもそれだと不便なので、拡張しやすいように良く考えられているなぁという印象です。

使った環境

  • Python 3.8.5

参考サイト

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?