kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

PEP 483を読む(続き6)

covariant・contravariantを使って型変数の共変・反変を宣言できます。デフォルトでは型は不変です。

不変というのは変だろうと思ってたけど
PEP 484の翻訳記事を読んでみたら

それは List[Employee] 型のようにアノテートされた引数に対する引数は、型アノテーションに厳密に一致しなければならないということを意味します。型パラメーター (この例では Employee) のサブクラスやスーパークラスは許されません。

と書いてあった
そういうことなのか
それなら不変っちゃあ不変

  • 不変(Invariant) ― 上記のどちらでもないとき

この定義からそうなるのかっていうのはよくわからないけど
上記のどちらでもなくてどういう型として扱っていいかわからないから
安全のために厳密な一致を求めることにします、ということか
デフォルトが安全側に倒れているのは妥当

  • List[T]は不変。List[int]の値の集合はList[float]の値の集合の部分集合だが、List[int]にはintしかappendできない。

これはList[T]の性質を言っているのかと思ったけど
不変な型コンストラクタだからintしかappendできないように制限している、
って話だったんだな

そしてこれも

  • 共変 ― すべてのt1、t2においてGenType[t2]がGenType[t1]の部分型であるとき
  • 反変 ― すべてのt1、t2においてGenType[t1]がGenType[t2]の部分型であるとき

性質(たとえばCallableは引数について反変である、みたいな)というより
この型変数は共変的に扱っても安全ですよ、反変的に扱っていいですよ、と
型チェッカーに教えてあげるもの、と思ってたほうがいいのかな
この引数は変化しませんよ、とconstをつけてあげる的な

共変・反変は型コンストラクタの性質であるかのように最初書いてあったけど
ここまでの話からして型変数の性質であるように思われる

反変の方の例

T_contra = TypeVar('T_contra', contravariant=True)

class Sink(Generic[T_contra]):
    def send_to_nowhere(self, data: T_contra) -> None:
        with open(os.devnull, 'w') as devnull:
            print(data, file=devnull)

ここではT_contraは反変的ですよ、だからT_contraのところは
指定された型の上位型も入れて大丈夫ですよ、と言っている

IntSink = Sink[int], FloatSink = Sink[float]とすると
intはfloatの部分型で、T_contraは反変だからFloatSinkはIntSinkの
部分型で、IntSink型の変数にFloatSink型の値を入れたりしても大丈夫だよと

注意:共変・反変は型変数を通じて定義されるけれども、型変数の性質ではなく総称型の性質です。

えーやっぱりそうなの
型変数によって変わる型コンストラクタの性質というのがより正確になるんだろうか

派生した総称型の複雑な定義では、共変・反変は使用される型変数で決まります。

型パラメータがふたつあって、片方は共変、片方は反変だったら?
不変になるのかなあ
いややっぱりこっちは共変、こっちは反変ってだけじゃないのかなあ
Callableだって返り値については共変、引数については反変だったし

でもなにか誤解している雰囲気を感じないでもない

共変・反変の最後の例

T_co = TypeVar('T_co', Employee, Manager, covariant=True)
T_contra = TypeVar('T_contra', Employee, Manager, contravariant=True)

class Base(Generic[T_contra]):
    ...

class Derived(Base[T_co]):
    ...

ええと
T_coは共変な型変数でEmployee型になったりManager型になったりする
T_contraは反変な型変数でEmployee型になったりManager型になったりする

ManagerはEmployeeの部分型で、
BaseはT_contraに関して反変だから
Base[Employee]はBase[Manager]の部分型になる

同様に
DerivedはT_coに関して共変だから
Derived[Manager]はDerived[Employee]の部分型になる

けどその前にまずDerivedはBase型を継承しているから、Baseの部分型になる
Derived[Employee]はBase[Employee]の部分型で
Derived[Manager]はBase[Manager]の部分型ってことかな

でいいみたい

総称型としてのDerivedは総称型としてのBase型の部分型になる、とも言っていい?

共変・反変はここで終わり

ややこしいけど常に不変でやってると不自由で使いづらいケースが出てきそうだし
覚えておかないといけなさそう

たぶん、ここでこの変数に派生クラスを入れていいのか
親クラスを入れていいのか、それともどっちも危ないのか、って考えれば
大丈夫な気がする