C# と Python

これは何の話?

普段C#を書いている自分が、Pythonコードを書くためのメモ

クラス

class Hoge
{
}
class Hoge:
    pass

書くものがないときは、passを書く。

new する

var h = new Hoge();
h = Hoge()

インスタンスメソッド

public class Hoge{
    public void Fuga(){
    }
}
class Hoge:
    def Fuga() -> None :
        pass

引数なしコンストラク

public class Hoge{
    public Hoge(){
    }
}
class Hoge:
    def __init__(self):
        pass

コンストラクタに self が必要なところで驚く。 var h = Hoge() とかけば init が呼ばれてHogeインスタンスができる。 なので、実際のところ、呼び出し時に self を渡すコードを書くことはない。

引数ありコンストラク

public class Hoge{
    public Hoge(string s){
    }
}
class Hoge:
    def __init__(self, s: str):
        pass

selfのあとに、引数を書く。 この例では引数は受け取っているけど、使っていない。

Pythonは型を明示しなくても動作するけど、C#に慣れた身としては明示しておいた方がしっくりくる。 mypyのようなコード検査ツールでは、型指定されていればそれに応じたチェックがなされる。 この例では、変数sstr型 なので、 Hoge(123) は、mypyではエラーが検出される。 実行時にはチェックされない。

インスタンスフィールド ( Python では 属性 と呼ぶ )

public class Hoge{
    private string s;
}
class Hoge:
    def __init__(self):
        self.s: str = ""

Pythonでは、この s のことを属性という。

単に宣言だけすることはできないので、何らかの初期値を与えておく。

staticフィールド ( Python では クラス変数 と呼ぶ )

public class Hoge{
    public static string s;
}
from typing import ClassVar    #ClassVar型構築子を使う場合に必要
class Hoge:
    s1: str = ""
    s2: ClassVar[str]

h = Hoge()
Hoge.s1 = "123"  #OK クラス名から辿れる
h.s1 = "123"     #OK インスタンスからも辿れる
h.s1 = 123       #NG  型があっていない
Hoge.s2 = "123"  #OK  クラス名から辿っているのでOK
h.s2 = "123"     #NG  インスタンスから辿っているのでNG(これがClassVar型構築子の効果)
Hoge.s2 = 123    #NG  型があっていない

C#er的には、インスタンスフィールドを書いたような気分になってしまうが これで、staticフィールドになる。インスタンス変数からアクセスできてしまったりするので、C#のstatic フィールドとは ちょっと違う。 ClassVar[型] のように型を指定すれば、クラス名でのアクセスを強制できる。

複数のコンストラク

public class Hoge{
    private string s;
    private int i;
    public Hoge(string s){
        this.s = s;
    }
    
    public Hoge(int i){
        this.int = i;
    }
}
class Hoge:
    def __init__(self):
        self.s :str= ""
        self.i :int= 0
    
    @classmethod
    def CreateHoge1(cls, s : str):
      c = cls()
      c.s = s
      return c
      
    @classmethod
    def CreateHoge2(cls , i : int):
      c = cls()
      c.i = i
      return c

h0 = Hoge()
h1 = Hoge.CreateHoge1("abc")
h2 = Hoge.CreateHoge2(123)

端的にいうと、複数のコンストラクタを作ることはできない。 init を複数個書くと実行時にエラーになる。 selfの属性として、インスタンスフィールドを表現する都合上、確かに複数の init の記述を認めると 読み手もどう解釈すればいいのかわからないのでこうなっている、と自分は受け止めている。

複数のコンストラクタを作りたい場合は、インスタンスを作るメソッドを作ることで代用する。 これには、 @classmethod デコレータをつけたメソッドを使うことが一般的なようだ。 これに相当するものは C# にはない。

第一引数を cls とするのは慣例なので従うこと。

次の@staticmethodでも代用できそうにも思えるけど @classmethodにしかできないこともあり、後述する。

staticメソッド

public class Hoge{
    public static void Piyo(string s){
        Console.System.WriteLine(s);
    }
}
class Hoge:
    @staticmethod
    def Piyo(s: str) -> None:
        print(s)


h1 = Hoge()
Hoge.Piyo("static")
h1.Piyo("instance")    # インスタンスから呼ぶこともできる

classmethod

classmethod は、C.f() とも呼び出せるし、 C().f() のように呼ぶこともできる。 このようなメソッドは、C#にはない。

複数のコンストラクタの代用品を作るときに classmethod を使用した。 staticmethodでも似たようなことができるが、cls() のように、init の呼び出しを 抽象的に書くことはできない。 clsのように書くことで、継承のときに、cls()がサブクラスの __init__() に読み替えられる。

staticmethod で、コンストラクタ代わりのメソッドを書くと、継承時に困る。

先ほど、classmethodでコンストラクタ代わりのメソッドを書いたが 戻り値の型指定を省略していた。継承を考慮すると、戻り値の型指定を書くことはできない。

class Hoge:
    def __init__(self):
        self.s :str= ""
        self.i :int= 0
    
    @classmethod
    def CreateHoge1(cls, s : str) -> cls :  # このように書くことはできない
      c = cls()
      c.s = s
      return c

    @classmethod
    def CreateHoge2(cls , i : int) -> Hoge : # これもダメだった。意外。この時点ではクラスの定義が完了していない、ということのようだ。
      c = cls()
      c.i = i
      return c

using

+-- Main.py
|   
\---Fuga
    |   Hoge.py
    |   Piyo.py
    \   __init__.py

Hogeクラスが、Hoge.py に定義されている。 Piyoクラスが、Piyo.pyに定義されている。

from Fuga import Hoge,Piyo
h = Hoge.Hoge()
p = Piyo.Piyo()
print(h)
print(p)
from Fuga.Hoge import *
from Fuga.Piyo import *
h = Hoge()
p = Piyo()
print(h)
print(p)