実現したいこと
mypy で、メソッドの戻り値を、(引数によっては)狭めるような override をするスタブファイル test.pyi
を作りたい。
発生している問題・分からないこと
test.pyi
にて、以下のエラーメッセージが発生しました。
エラーメッセージ
error
1test.pyi:14: error: Signature of "__add__" incompatible with supertype "数式" [override] 2test.pyi:14: note: Superclass: 3test.pyi:14: note: def __add__(self, int | float | 数式, /) -> 数式 4test.pyi:14: note: Subclass: 5test.pyi:14: note: @overload 6test.pyi:14: note: def __add__(self, int | float | 実数, /) -> 実数 7test.pyi:14: note: @overload 8test.pyi:14: note: def __add__(self, 数式, /) -> 数式 9Found 1 error in 1 file (checked 1 source file)
該当のソースコード
python:test.pyi
1from typing import overload 2 3class 数式: 4 def __add__(self, other:int|float|数式)->数式: 5 # 例0: 数式("x") + 数式("y") = 数式("x+y") 6 # 例1: 数式("x") + 実数("1") = 数式("x+1") 7 # 例2: 数式("x") + 1 = 数式("x+1") 8 # 例3: 実数("1") + 数式("x") = 数式("x+1") 9 # 例4: 実数("1") + 実数("2") = 実数("3") 10 # 例5: 実数("1") + 2 = 実数("3") 11 pass 12 13class 実数(数式): 14 @overload 15 def __add__(self, other:int|float|実数)->実数: 16 # 例4: 実数("1") + 実数("2") = 実数("3") 17 # 例5: 実数("1") + 2 = 実数("3") 18 pass 19 @overload 20 def __add__(self, other:数式)->数式: 21 # 例3: 実数("1") + 数式("x") = 数式("x+1") 22 # 例4: 実数("1") + 実数("2") = 実数("3") 23 pass
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
「teratailやGoogle等で検索した」 について
Google で Signature of method incompatible with supertype
を検索したところ、
こちら の Qiita の記事を見つけました。
そこで、 リスコフの置換原則 (LSP) という言葉が出てきたので、Google でLSPについて調べました。
LSPについて分かったことを自分なりの言葉で表現すると、「親に出来ることは、子にも出来ろ」ということです。
- 子クラスではメソッドの引数を親より狭めてはならない
(例えば 親メソッドがint|str
を入力できるのに、子メソッドがint
しか入力できないよう引数を狭めてしまうと、「親に出来たstr
の入力が、子には出来ない」という事態が発生し、LSPに違反します) - 子クラスではメソッドの戻り値を親より広げてはならない
(例えば 親メソッドがint
を出力するのに、子メソッドがint|str
を出力するよう戻り値を広げてしまうと、「親に出来た "出力を2で割る演算" ((int)/2
) が、子には出来ない場合がある ((str)/2
はエラーになる)」という事態が発生し、LSPに違反します)
一方、私のコードにおける 子クラスのメソッド ( 実数.__add__
) では、
- 引数は
int|float|実数
, 戻り値は実数
- 引数は
数式
, 戻り値は数式
が overload になっており、
- 可能な引数をまとめると、
int|float|数式
- 可能な戻り値をまとめると
数式
になっています。
これは、親クラスのメソッド ( 数式.__add__
)のシグネチャ
- 引数は
int|float|数式
, 戻り値は数式
と一致しているため、どこが Signature of "__add__" incompatible with supertype "数式"
なのかわからないのです。
また、入出力を表にまとめると、次の通りです。
入力\出力 | 親メソッド | 子メソッド | 子の出力は親より |
---|---|---|---|
int|float|実数 | 数式 | 実数 | 狭い |
実数 以外の数式 | 数式 | 数式 | 同じ |
この表から、特定の型の入力があった場合に出力が親より狭まることはあっても広げることはない。(LSPに違反する入力パターンはない)ということがないことがわかります。
「ソースコードを自分なりに変更した」 について
test.pyi
の20行目を def __add__(self, other:
int|float|
数式)->数式:
に変更したところ、 mypy がエラーを返すことは無くなりましたが、可読性の観点からこのような変更は極力避けたいです。
補足
環境は python 3.13.1, mypy 1.14.0 を利用しています。
また、本当のことをいうと、本件の問題は、sympy 1.13.3 の スタブファイルを(自分にとって必要な箇所だけ)自作しているときに直面しました。具体的には、
python:sympy.pyi
1from typing import overload 2 3class Expr: # sympyの式 4 def evalf(self, n:int)->Float: pass 5 def __truediv__(self, other:int|float|Expr)->Expr: pass 6 7class Float(Expr): # sympyの実数。sympyの式 の一種 8 @overload 9 def __truediv__(self, other:int|float|Float)->Float: pass 10 @overload 11 def __truediv__(self, other:Expr)->Expr: pass
のようなスタブファイルを作っていた時に、
error
1sympy.pyi:8: error: Signature of "__truediv__" incompatible with supertype "Expr" [override] 2sympy.pyi:8: note: Superclass: 3sympy.pyi:8: note: def __truediv__(self, int | float | Expr, /) -> Expr 4sympy.pyi:8: note: Subclass: 5sympy.pyi:8: note: @overload 6sympy.pyi:8: note: def __truediv__(self, int | float | Float, /) -> Float 7sympy.pyi:8: note: @overload 8sympy.pyi:8: note: def __truediv__(self, Expr, /) -> Expr 9Found 1 error in 1 file (checked 1 source file)
というエラーに直面したのですが、sympy
に詳しくない方が回答する気を失ったりしたらもったいないので、敢えて sympy
が関係ないコードを別に用意し、質問させていただきました。
なので、もしも python 3.13.1, mypy 1.14.0 sympy 1.13.3 で正常に動作する sympyのスタブファイルをお持ちか、 sympy に型ヒントを追加する方法をご存じの方がいらっしゃいましたら、そちらも回答いただけますと大変ありがたいです。
以上、何卒宜しくお願い致します。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。