自動車学校に通い始めた六角レンチです
車体左側の感覚がなんもわからん
今回はpylanceで謎めいたエラーに遭遇したので紹介したいと思います
なお、Pythonのバージョンは3.11を使用しています
Union[str, None]
をstr | None
に短縮しているので注意
問題のコード
ある日、適当にプログラミングしてたら奇妙な型チェックエラーに遭遇する
a: list[str | None] | None = [None]*10
これはstr
かNone
が含まれるリストかNone
を受け取る変数にNone
のみのリストを代入しようとしているわけだが...
Type "list[None]" is not assignable to declared type "list[str | None] | None"
Type "list[None]" is not assignable to type "list[str | None] | None"
"list[None]" is not assignable to "list[str | None]"
Type parameter "_T@list" is invariant, but "None" is not the same as "str | None"
Consider switching from "list" to "Sequence" which is covariant
"list[None]" is not assignable to "None"
なんかpylanceに怒られる
エラー文を見る感じ、どうやらlist[None]
がlist[str | None]
に似ていない。
さらに言えば、None
がstr | None
に似ていないと言っている。
でもどう考えてもNone
はstr | None
に対して内包関係であるはず
さらに深まる謎
さらに意味不明な点がある。
次のコードを見て欲しい
a: list[str | None] | None
b = [None, None]
a = [None, None]
a = [None]*10
a = [None for _ in range(10)]
a = b
ミューダブルだからa = b
はまずいのではっていうのは一旦置いておいて、この4通りのa
への代入でpylanceがどれに怒るのかというと...
なんとa = [None]*10
とa = b
にのみ怒る
他の代入方法もリスト的にはほぼ同じはずなのになぜかこれらにのみ怒る
さらにさらに深まる謎
もっと意味不明な点もある
a: list[str | None]
b = [None, None]
a = [None, None]
a = [None]*10
a = [None for _ in range(10)]
a = b
このコードはa
の型ヒントでNone
の可能性を排除しただけなのだが...
今度は最後のa = b
にのみ怒る
ちなみにリストの中身をNone
ではなくstr
のインスタンスにしても(例えば"a"
だけにしても)発生する
なんでa = [None]*10
に怒らなくなるのかは謎
一応この代入方法だと、リストの中身が全部同じものになるっていう仕様があるけどNone
はイミュータブルだから関係ないはず...
主に発生する場所
関数とかの引数で複数要素の値を許容する奴で起きる
こんな感じ
設計の意図的にはint
かfloat
が入ってたらいいですよって感じにしたかったのに、pylanceが許してくれない
解決策
エラー文をよく見ると...
Consider switching from "list" to "Sequence" which is covariant
と書いてある
list
を使うな!Sequence
を使え!!!と言っている気がする。多分。
つまりこうすればよい
これならpylanceも怒らないし、list
以外のイテラブル(tuple
とかset
とか)も許容するようになる
注意点はSequence
にしたらlist
特有の操作(copy
メソッドとか)が使えなくなることくらいかな?
おまけ
Copilotで新しく使えるようになったGPT-5が思ったより優秀なので、こいつにこの問題を聞こうと思います
原因
- aは「期待される型」がlist[str|None]。コンテキストからRHSのリストリテラルの要素型がOptional[str]に推論され、OK。
- bは「期待される型」がUnion[list[str|None], None]。Union文脈では多くの型チェッカーが要素型のコンテキスト推論を弱めるため、RHSはlist[None]と推論される。
- listは不変(invariant)。list[None]はlist[str|None]のサブタイプではないため、Unionのどの枝にも一致せずエラー。
対処例
- 右辺に明示的な型を与える(キャストや一時変数)。
````python
from typing import Optional, cast
a: list[Optional[str]] = [None] * 10
b: Optional[list[Optional[str]]]
b = cast(list[Optional[str]], [None] * 10) # 明示キャスト
````
````python
from typing import Optional
tmp: list[Optional[str]] = [None] * 10
b: Optional[list[Optional[str]]] = tmp # OK
````
````python
from typing import Optional
b: Optional[list[Optional[str]]] = None
b = [None for _ in range(10)] # ツール次第でOK/NG。NGなら上記方法を推奨
````
ポイント
- 「listは不変」+「Union文脈ではコンテキスト推論が弱い」がエラーの理由。明示的に型を与えると解決します。
なんかよくわからんこと言ってる
list[None]
はlist[str | None]
のサブタイプではないとか言ってる
でもそれはa: list[str | None] = [None, None]
でエラー出てないのに矛盾する
ということでGPT-5でもあんま有用な答えはもらえなかった
Copilot君はお布団にくるまっておねんねしてね
この添付コードと「お布団にくるまっておねんねしてね」は、何をしたいですか?
- その文字列をコードに組み込みたい?
- 型エラーの整理を続けたい?
- ほかの処理に使う定数化や関数化が必要?
う~ん、この。