プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Python 3.11 の typing.Self を使ってみた

最近 python で遊ぶときは、型ヒントをつけながらいろいろと試しています。

もともとは、クラス定義の時、自分自身を指定することができないようです(下記の前方参照)を参照

[翻訳] PEP 0484 -- 型ヒント (Type Hints) - Qiita

で、Python 3.11 から、自分自身のクラスを表すことができる typing.Self が使えるようになるとのことだったので、早速試してみました。

こういう Self は、あるクラスが同じクラスおよびそれの派生クラスを保持するようなデータ構造を扱うような場合に、よくでてくるかと思います。

from typing import Self, TypeVar, Union

# 子クラス側で警告がでる
class A:
    def __init__(self, p: Self | None):
        self.p: Self | None = p

class B(A):

    def __init__(self, pb: A | None):
        print(f"type: {super()}")
        super().__init__(pb)

class C(A):

    def __init__(self, pc: A | None):
        print(f"type: {super()}")
        super().__init__(pc)


if __name__ == "__main__":
    a = A(None)
    b = B(a)
    c = C(b)

これをすると、なぜか B や C の super().init の部分で Pylance がエラーと判定してしまいます。

どうも、型が不一致との判定のようです。なんでだ?

とりあえず、 Self を使わなくてもいいので、型を一致させてみます。

from typing import Self, TypeVar, Union

class A:
    def __init__(self, p: Union["A", None]):
        self.p: Self | None = p
(後略)

これだとうまくいきますね。どうも、 Self を指定しても、そのクラスおよび派生クラスを示すものではないということのようです。

ちなみに、

# "A" | None でエラーになる
class A:
    def __init__(self, p: "A" | None ):
        self.p: Self | None = p

とすると、 Pylance ではエラーになりませんが、実行時に

mor@DESKTOP-DE7IL4F:~/work/tmp$ python3 test_class_inherit.py 
Traceback (most recent call last):
  File "/home/mor/work/tmp/test_class_inherit.py", line 11, in <module>
    class A:
  File "/home/mor/work/tmp/test_class_inherit.py", line 12, in A
    def __init__(self, p: "A" | None ):
                          ~~~~^~~~~~
TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'
mor@DESKTOP-DE7IL4F:~/work/tmp$ 

のようなエラーがになります。なので、 | を使った省略表記が使えないので、上記の書き方になりました。

ちなみに、

Python 3.11の型ヒントに導入される"PEP 673 – Self Type"を訳してみた

に紹介されているようなケースだと、 Self がうまく活きるようです。でも、今回の場合はうまくいきませんでしたね。

残念。

参考

ちなみに、上記のエラーが出る理由を chat-GPT に聞いてみると、

Selfは型ヒントの特殊な表現であり、継承関係にあるクラス同士の関係を表現するものではありません。

と回答が返ってきました。まあ、エラーになっているので、その通りなんだけど、 Self を指定しているのに、そのように判断される理由を知りたかったんだけど、微妙な回答ですね。

なかなか Python もややこしいですね。