0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

float(x) できるなら SupportsFloat

Python には SupportsFloat というプロトコル1がある。

これは、Python 組み込みの float 型ではないが、float として扱うことができる型であることを要求する。実際の動作としては、self.__float__() をメンバーに持つ、つまり組み込み関数 float(x) で変換できることを意味する。float ではないが SupportsFloat であるような型には NumPy の np.float64 などがある。

from typing import SupportsFloat

import numpy as np


def func1(x: float) -> float:
    return 2.0 * float(x)


def func2(x: SupportsFloat) -> float:
    return 2.0 * float(x)


x = np.empty((8, 8), dtype=float)
y: np.float64 = x[0, 0]
print(func1(y))  # NG
print(func2(y))  # OK

complex(x) できるなら SupportsComplex、ではない

同様に self.__complex__() を実装していることを意味する SupportsComplex がある。float 型は complex(x) によって complex に変換できるため、以下のコードは型チェッカーを通過できるはずだ。

from typing import SupportsComplex


def distance(x: SupportsComplex, y: SupportsComplex) -> float:
    return abs(complex(x) - complex(y))


print(distance(5.0, 3.0))  # NG

しかし、このコードは型チェッカーが許さない。なぜだろう?

実は、floatself.__complex__() を持っていないのだ。つまり floatSupportsComplex ではない。

print(hasattr(5.0, "__complex__"))  # False

さらにいえば、complex さえ Python 3.11 まで SupportsComplex ではなかった。SupportsComplex ってなんだよ。

ではなぜ complex(x) によって変換できるのか? complex 関数(コンストラクタ)の型シグネチャは Pyright によるとcomplex(real: complex | SupportsComplex | SupportsFloat | SupportsIndex = ..., imag: complex | SupportsFloat | SupportsIndex = ...) となっている。SupportsComplex だけでなく SupportsFloatSupportsIndex が指定されていることがわかるだろう。SupportsIndexoperator.index(x) によって正確な int に変換できる、つまり整数とみなせる型であることを意味する。

もしや、floatint に対して特殊化し、float(x)operator.index(x) によって変換できる型はそのようにしてから処理をおこなっているのではないか? 確かめてみよう。

class Foobar:
    def __float__(self) -> float:
        print("called __float__")
        return 0.0


complex(Foobar())  # called __float__

間違いない。つまり、complex(x) によって変換できることを要求する場合は、例えば以下のようにしなければならない。

from typing import SupportsComplex, SupportsFloat, SupportsIndex, TypeAlias

ConvertibleToComplex: TypeAlias = (
    complex | SupportsComplex | SupportsFloat | SupportsIndex
)


def distance(x: ConvertibleToComplex, y: ConvertibleToComplex) -> float:
    return abs(complex(x) - complex(y))


print(distance(5.0, 3.0))  # OK

確かに float に変換できるなら complex にも変換できるべきなので、self.__float__() だけ実装すればいい complex(x) の型は理解できる。しかし、SupportsComplex というプロトコルの動作が非直感的になっていることは念頭に置かなければいけない。

おまけ

では、self.__index__() self.__float__() self.__complex__() すべてを実装している場合 complex(x) の動作はどうなるのだろう。試してみよう。

class Foobar:
    def __index__(self) -> int:
        print("called __index__")
        return 0

    def __float__(self) -> float:
        print("called __float__")
        return 0.0

    def __complex__(self) -> complex:
        print("called __complex__")
        return complex()


complex(Foobar())  # called __complex__

self.__complex__()self.__float__()self.__index__() の順で優先されて呼び出されるようだ。

  1. ABC ということになっているがこれはプロトコル言語機能の実装前の言い分であり、実際には継承せずとも動作するためプロトコルとみなすべきだろう。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?