SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

C#erがPythonを勉強したので、違いについて比較しながら述べてみる その2 名前空間、クラス

前回、Pythonについて、構文レベルの話を書きました。

hiroronn.hatenablog.jp

今回は、名前空間や、クラスについて述べていきます。

名前空間

名前空間とは

名前空間とは、いろいろ説明がありますが、クラスをわかりやすく整理するためのもの、と考えてよいと思います。
フォルダとファイルの関係、みたいなものです。*1

名前空間 - Wikipedia

IDEを使う場合、フォルダの物理構成と合わせた形で、初期の名前空間が振られます。
別のフォルダにある同名クラスは、別物として扱われます。

フォルダに同じ名前ファイルが作れないように、同じ名前空間に同じクラスは作れません。

C#名前空間

  • 公式の説明

名前空間 (C# プログラミング ガイド) | Microsoft Docs

公式の説明では、「名前空間はネストできる」とか、いろいろ書いてあります。
なお、経験上、「一つのファイル内で名前空間を分ける」ことは、ほとんどありません。

理由は、ネストが深くて見づらいから、と思っています。

C#では、ソースコードの中に、明示的に名前空間を指定します。
名前空間の中にいるクラスが、その名前空間に属することが、ソースを見たらわかります。

namespace My.Practice {
    public class ClassPractice{
    }
}

上のように書くと、My.Practice名前空間ClassPracticeクラスを表します。

C#の難しいところは、名前空間とは別に、アセンブリという概念(Visual Studioのプロジェクト単位)があり、どう分けたらよいのかわかりづらい点にあると思います。*2

Python名前空間

9. クラス — Python 3.6.1 ドキュメント

この説明だけでは、何のことやらわかりませんでした。
結局、具体的にどうやって名前空間を識別しているのかについては、触れられていません。

というのも、前提として「モジュール」が存在するためです。

6. モジュール (module) — Python 3.6.1 ドキュメント

Python では定義をファイルに書いておき、スクリプトの中やインタプリタの対話インスタンス上で使う方法があります。このファイルを モジュール (module) と呼びます。

とあるように、.pyファイルを、モジュールと呼んでいます。*3
ここは、Javaのモジュールに近いと思います。

ただ、決定的に違う点がありまして…
Pythonでは、名前空間やモジュール名を、ファイル内に宣言しません

f:id:hiroronn:20170721211314p:plain

上記のようなソリューション構成(フォルダ構成も同じ)だとしたら、

ファイル名 種類
PythonPractice.py モジュール(名前空間なし)
My 名前空間
Practice.py モジュール(My名前空間配下)

となります。

なお、単にフォルダを作るだけではダメで、__init__.pyというファイルがあるフォルダを、名前空間と認識するそうです。*4

名前空間は、別名パッケージ…なのでしょうか?細かい部分が、あまり理解できていません。

上記のMyはパッケージで、階層構造を名前空間と呼ぶのか…?

クラスについて

名前空間については、正直謎が多いです。
クラスも、C#Javaに慣れている人にとっては、とっつきづらいように思います。

C#のクラス

C#のクラスですが、他の要素との関りで言うと、以下のことが言えます。

  • クラスは、名前空間もしくは他のクラスに属する
  • メソッドやプロパティ、フィールド等は、必ずクラスに属する

前者は、名前空間の下にクラスが作られること、クラスはネストできることを言っています。
クラスのネストは、名前空間のネストに比べたら、よく使います。

後者は、グローバルな参照は無い、ということを示しています。*5

Pythonのクラス

Pythonでは、変数等は必ずしもクラスに属しません。
ただ、必ずいずれかのパッケージには属します。

両者の違い

クラスそのものの違いは置いておいて、C#Pythonで同じように書いても、意味が違うものについて書いてみます。

フィールド

public class Practice{

    int _value = 10;

    public int Value{
        get{return _value;}
    }

    public Practice(int val){
        _value = val;
    }
}
class Practice:
    val = 10

    def __init__(self, value):
        val = value

という、見た目が似たクラスがあります。

def __init__(self, value):は、コンストラクタです。

これを、

var p1 = new Practice(5);
var p2 = new Practice(8);

Console.WriteLine(p1.Value.ToString()); 
Console.WriteLine(p2.Value.ToString()); 

とした場合、

5
8

と出力されます。

p1 = Practice.Practice(5)
p2 = Practice.Practice(8)

print(p1.val)
print(p2.val)

とした場合、

10
10

と出力されます。

実は、C#のフィールドと同じ書き方をすると、Pythonでは「クラス変数」として扱われます。

C#でいうと、

public class Practice{

    public static int _value = 10;

    public Practice(int val){
        _value = val;
    }
}

という定義に近いです。*6

C#と同じく、インスタンスの変数として扱うなら、

class Practice:
    val = 10

    def __init__(self, value):
        self.val = value

のように、変数selfの値を変更します。

なお、上のように書くと、「クラス変数のval」と、「インスタンス変数のval」は、別の値として扱われます。

よって、

p1 = Practice.Practice(5)
p2 = Practice.Practice(8)

print(p1.val)
print(p2.val)
print(Practice.Practice.val)

と呼び出すと、

5
8
10

と出力されます。

上二つは、「インスタンス変数のval」にアクセスし、print(Practice.Practice.val)は、「クラス変数のval」にアクセスしているためです。

つまり、Pythonでは、クラス変数とインスタンス変数を厳密に区別し、完全に別のものとして扱っているようです。

インスタンスに属する値は、C#のフィールドのように定義せず、self.valのように書くだけで、定義できます。
コンストラクタの中で、初期化してあげると、分かりやすいと思われます。

関数

private int _val;

public int GetValue(){
    return _val;
}

C#では、上記のように書くと、自身のインスタンスの_valを返します。

同じ意味を示す処理を書くと、

def GetValue(self):
    return self.val

となります。

先頭のselfですが、インスタンスメソッドの場合に暗黙的に渡される、「メソッドを呼び出したインスタンス」を表します。*7

インスタンスの状態を変えるなら、このselfに対して処理しましょう。

補足

Python

class Practice:

    def __init__(self, value):
        self.val = value

というクラスに対し、

p1 = Practice.Practice(5)
print(p1.val)

# 定義されていない変数を外から準備
p1.valval = 3
print(p1.valval)

とすると、どうなるでしょう?

なんと、そのまま動作します。

結果はこうなります。

5
3

C#とかJavaではありえない挙動です…
なんで、クラスで定義していない変数が追加できるんだ…と思いました。
Pythonはそういうもの、と考えましょう。*8

もっとも、こうやって変数を追加しても、クラス自体の処理には何ら影響はないです。

感想

二回にわたって、C#Pythonについて書きました。

チュートリアル程度ですが、Pythonについて勉強して、あまりにもC#と違いすぎて、最初は戸惑いました。

でも、正直、Python好きになりました。

何より、文が短くてわかりやすいです。
大規模になると、型指定不要という部分が弱点になりそうですが、3.5から「型アノテーション」が追加されたので、うまく使えばいいのでしょうきっと。

いろいろ、作ってみたい今日この頃です。

*1:フォルダ構成が名前空間、ファイルがクラス

*2:アセンブリが異なっていても、同じ名前の名前空間を定義できる

*3:C#では、ファイルはただのファイルであり、特に意味はない

*4:Visual Studio では、Pythonパッケージを選択して作成すると、自動で作成してくれる

*5:staticである程度は代用できる

*6:上記Pythonの例示では、クラス変数の値は変わらないが、C#では変更できる。違いがよく分からない。自分で自分の変数を識別できないと思われる

*7:先頭のselfという名前は、規約でそうするようになっている。名前が違っても動くが、selfにすること

*8:可読性を損なうので、やるべき処理ではないと、個人的には思う