目次
はじめに
初投稿です。
タイトル通り、訳あってC#案件からPython案件へ軸足を移すことになったので、自分用に比較記事を書きました。
Pythonはひよっこ、C#もそこそこやっただけという駆け出しの記事なので、間違ってるところや補足がありましたらコメントしていただけると幸いです。
コーディングに関しない部分の違い
コンパイル
C#ではstatic void Main() メソッドがエントリポイントとなり、コンパイル時にはそこに記述された内容が実行されます。
Pythonの場合、エントリポイントというものはありません。python hoge.py
をコマンドライン経由で走らせると、ファイルの内容を一行目から順番に読み込んで実行します。
本当に書いてあることを頭から実行するので、変数の宣言や処理を直に書いておくと意図しないところで実行されて焦ったりします。そういった事態を避けるため、一般的にif __name__ == "__main__":
というブロックを宣言し、その中に行いたい処理を書きます。こうすると、このファイルを実行した時以外、例えばimportしたときなどにはこの部分の処理の読み込みは飛ばされます。
基礎的な文法
オフサイドルール
PythonとC#の大きな違いの一つは「インデント」に重要な意味があることです。C#では定義部分や条件分岐のネストなどをブランケットでくくりますが、Pythonではインデントで押し下げて視覚的に表現します。C#でも可読性を上げるためにインデントしますが、Pyrhonの場合はやらないとErrorが出ます。
型宣言の有無
C#は静的型付け言語なので変数やメソッドを宣言する際に変数の型や戻り値の型を書く必要があります。動的に型を決定する場合でもvar
キーワードをつける必要があります。
Pythonでは型名を一切つけません。メソッドの返り値も記述しません。
一応、3.5以降ではTypeHintという型を明示する記述方法が組み込まれたので型が分かってるときはなるべく使った方がいいと思います。
基本的な型
型 | C# | Python |
---|---|---|
整数型 | short, int, long etc... | int |
小数 | float, double, decimal | float/(decimal) |
複素数 | なし(Numeric から入れる) | complex |
bool型 | true, false | True, False |
文字列(文字) | char, string | str |
列挙型 | enum | モジュールで実装 |
シーケンス型 | Array, List | list tuple range |
マッピング型(辞書) | Dictionary | dict |
※Byteやbitについては普段使用してないので省きました。
整数型
C#の整数型はデータを格納するByte数や形式に応じて複数あり、型ごとに最大値や最小値が変わります。対して、pythonの整数型はPython3の場合は基本的にintだけです。さらに、最大値が規定されてなく、メモリの許す限り大きな値が取れるらしいです(下記参照)。動的型付けすごい。
[参考] [https://note.nkmk.me/python-int-max-value/]
小数
C#では組み込みでfloat(浮動小数点数)、double(倍精度浮動小数点数)、decimal(十進浮動小数点数)が用意されてました。
Pythonでは組み込み型は倍精度ひとつに統一され、名前がfloatになっています。公式のドキュメントによると、Cのdoubleをそのまま実装しているそうです。
[参考] [https://docs.python.org/ja/3/library/stdtypes.html#numeric-types-int-float-complex]
Pythonでdecimalを使いたい場合は、モジュールをimportする必要があります。
複素数
C# ではSystem.Numericを使わないと入れられない複素数ですが、Pythonでは組み込み型として定義されています。数値計算用なんでしょうか。
変数の宣言はc = complex(a,b)
のようにします。文字列を引数にしたりもできて結構便利そうです。
bool型
頭が大文字か小文字か以外に、特に大きな違いはありません。論理判定に関しては演算の方に回します。
文字列
C#では一つ文字分を格納するChar型と、Char型を配列のように組み合わせて文字列を表すstring型が別々に組み込まれていました。Pythonでは基本的に文字列を表すstrだけです。「文字列は文字の配列である」という考え方は共通しており、slicerを使って値を取り出したりできます。
C#ではシングルクオーテーション('
)でくくったらChar型、ダブルクォーテーション("
)でくくったらstring型と決められていましたが、Pythonではどちらでくくってもstrとなります。さらに"
でくくった場合は'
が、'
でくくった場合は"
がエスケープなしで普通の文字として認識されるので、省略形や口語の表記がちょっと楽になります。
a = " Don't stop beleivin' "
双方とも普通に宣言した場合immutableになります。
C#にはStringBuilderというmutableな文字列を生成するクラスがありましたが、Pythonには該当するものは組み込まれてません。
列挙子
C#では列挙型が組み込みで実装されていますが、Pythonではモジュールとしてしか用意されてません。
下に示した通り、実装の仕方も結構違います。
C#だと特別な宣言をしていますが、PythonだとEnumというクラスを継承する形になります。試してないですが、独自メソッドを組み込んだりもできるんでしょうか。
C#では宣言しなかった番号の割り当ては自動でやってくれますが、Pythonの場合は全て明示的に宣言する必要があります。Zenの考え方がよく出ている例だと感じました。
enum DayOfWeek { 月曜 = 0, 火曜, 水曜, 木曜, 金曜, 土曜, 日曜 }
from enum import Enum
class DayOfWeek(Enum):
月曜 = 0
火曜 = 1
水曜 = 2
木曜 = 3
金曜 = 4
土曜 = 5
日曜 = 6
シーケンス型
「配列」や「リスト」と呼ばれる、連続した値の集合を表す「シーケンス型」。これの扱いがC#とPythonで一番大きな違いを感じた部分です。具体的なことを紐解いていくと継承元やインタフェースの実装とかについてまで話が及びそうなので省きますが、C#にあったLINQのメソッドチェーンみたいなのはPythonのリストではできません。かわりにリスト内包表記とか使います。詳しくは下記の記事を参照してほしいですが、Pythonやって一番詰まった部分でした。
[参考] [https://ohke.hateblo.jp/entry/2017/06/09/235000]
LINQ以外にもう一つ大きい両者の違い。それは型に関する制限です。
C#の場合、ArrayでもListでも最初の宣言で指定した型以外の要素をそのシーケンスに加えることはできません。
Pythonのシーケンス型では、list、tuple共に異なる型の変数を組み合わせることができます。下記のように、Intだけのlistに文字列を加えることも可能ですが、やらない方がいいです。
var array = new int[]{0,1,2};
array[0] = 1;// OK
array[1] = "2";// Error
array = [0,1,2]
array[0] = 1
array[1] = "2"
print(array)
# >>> [1, '2', 2]
listとtupleの違いは可変かどうかです。listがmutable、tupleがimmutableとなってます。listは[]でくくるのに対し、tupleは()でくくります。
rangeは数字のシーケンスを返す特殊な型です。主にforループを使う際に使用されます。詳しくはフロー制御の方で説明します。
マッピング(辞書)型
C#ではSystem.Collections.Genericをusingすると使えるようになりますが、Pythonでは最初から組み込まれてます。
「KeyとValueをセットにして1項目とみなす」「Keyの重複は禁止」といった基本的な仕様は変わりませんが、シーケンス型と同様に、
C#のようなLINQはPythonではできません。
演算子
演算子 | C# | Python |
---|---|---|
代数演算子 | x+y,x-y,x*y,x/y,x%y,x**y | x+y,x-y,x*y,x/y,x%y,x**y |
インクリメント/デクリメント演算子 | x++, x--, ++x, --x | なし |
ビットごとの論理演算子 | &, |, ^ | &, |, ^ |
条件付き論理演算子 | &&, ||, ! | and, or, not |
比較演算子 | ==, !=, <, >, <=, >= | ==, !=,< , >, <=, >= (<>, is ) |
代数演算子
記号的に特に違いはありません。べき乗を表す「**」も一緒です。ただし、除算の結果については注意がいります。
C#では除算の演算子を異なった型同士に当てはめることはできず、結果の値も同一の型で返ってきます。そのため、Int型の除算結果は必ずInt型となり、余りや端数が出ます。
a = 5;
b = 2;
Console(a/b) // 2
Console(a%b) // 1
対して、Pythonの場合、自動的に値はfloatに変換されます。C#的な整数切り捨ての除算結果(割り算の「商」に相当する結果)を欲しい場合、//演算子を使います。
Pythonを初めて一番最初に実感する「動的型付けが効いてる例」ですね、多分。
a = 5
b = 2
print(a/b) # 2.5
print(a//b) # 2
print(a%b) # 1
インクリメント/デクリメント演算子
変数を1だけ増やしたり(++)減らしたり(--)する演算子。C#ではforループなどで必ず目にしますが、Pythonでは存在しません。
理由は、Pythonでは数値がimmutableな型として定義されてるからです。あまり意識したことがなかったですが、確かにこの演算子は「変数自体を書き換える」操作ですね。
これとよく混同される(自分も混同した)ものに「複合代入演算子」があります。x = x + y
をx += y
と短縮して書くようなやつです。これはPythonでもC#同様に存在します。一瞬とまどうのですが、これは名前にある通り「代入」なのでimmutableの制限に引っ掛からないのです。
ブール論理演算子
C#では&
や|
といった記号で示されていた論理演算子ですが、Pythonではand
やor
といった予約語となっています。より自然原語に近い記述を可能にするというZenの理念に則った例ですね。C#では&&
や||
で定義する短絡評価も最初から組み込まれてます。
ただし、and
やor
の部分を&
や|
で書いていても普通に通ります。理由はビット演算子としてこれらの記号が定義されているからです。論理型はビットに列挙子のように名前をつけただけと考えられるので通るわけです。ただし、ビットとして処理されるため、短絡評価は機能しません。そのため効率は下がりますしn >= len(list) | (list[n] の評価式)
みたいな短絡評価を利用したError回避のコードを書くとErrorで止まります。
XORを表す^
は論理演算子ではなくビットの演算子として定義されていますが、コード上は同じように使えます。
比較演算子
比較演算子は特に違いがありません。否定を表していた!
が名残のように残っているのでこれだけ見るとPythonでも使えそうに思えるのがちょっと紛らわしいですね。後、Pythonでは<>
でも非等価を表せるようです。
等価、等値についてはC#だとObjet.Equals()やis、Pythonでは is 演算子などもありますが、この辺のところはまだよく分かっていません。とりあえずnull評価はis null
とかis None
を使った方がいいらしいです。
フロー制御
制御 | C# | Python |
---|---|---|
if構文 | if(条件){処理}else / else if (条件){処理} | if 条件: 処理 else / elif 条件: 処理 |
三項演算子 | condition ? consequent : alternative | consequent if condition else alternative |
for ループ | for (int i = 0; i < length; i++) / foreach(var i in collection)) | for item in iterable: |
スイッチ構文 | switch (switch_on){ case hoge: case hoge: default: } | なし |
while ループ | while(条件){処理} | while 条件: 処理 |
goto
なんてものはC#にもPythonにも存在しないから、決して使ってはいけない。(戒め)
if構文
特に違うことはありません。else ifが elifというワンワードに置き換わるくらいです。
三項演算子
ワンセンテンス表記に便利な三項演算子ですが、C#とPythonでは書き方の順序が違います。C#では条件を先に書きますが、Pythonでは条件を値の後に書きます。条件を先に出す方が自然な感じがすると思うのですが、ZEN的にこれは正しいのでしょうか。正しいのでしょう。
C#ではnull評価しつつnullなら別の値を代入するa ?? b
という表記がありますが、これはPythonではa if a is not None else b
となります。やっぱりC#の方がなじみますね。not
使うのもなんだか回りくどいですし。
forループ
C#では二種類あるforループですが、Pythonには1種類しかありません。
Pythonのforはiterableなものから一つずつ値を取り出して処理するというもので、C#でいうforeachに相当するものになります。
C#のforに相当するものをPythonで実行するには、range関数を利用して以下のように実装します。
for i in range(0,length,1):
# 何らかの処理
スイッチ
PythonにはC#のスイッチに相当するものがありません。列挙子と組み合わせると便利なのですが、Pythonでは列挙子も組み込み型でないことを考えるといらないということなんですかね。どうしてもswitchがしたい場合はifとelseを組み合わせていくしかないようです。
whileループ
書き方はほぼ同じ。break
とcontinue
というキーワードも同様です。
オブジェクト指向的要素
クラスとメンバ
C#もPythonもオブジェクト指向言語と名乗っているのでクラスを定義できまが、その内実はかなり違います。
まず、C#のクラス内メンバーには「プロパティ」や「インデクサー」など何種類かありますが、Pythonでは変数とメソッドしかありえません。
Pythonのクラスで定義される変数には「インスタンス変数」と「クラス変数」があります。インスタンス変数はクラスから生成されたインスタンスごとに値が定義される変数、クラス変数はすべてのインスタンスで値が共通する変数です。C#でいうとインスタンス変数が普通のメンバー変数で、クラス変数がstaticなメンバー変数になるのでしょうか。
クラス内メソッドは通常のメソッドと同様に定義します。ただし、引数にself
というのを入れる必要があります。これはインスタンス変数を定義するために必要な処理のようです。実はインスタンス変数はメソッド内以外では定義できません。定義しようとすると読み込んだ時に「selfという語が未定義です」とエラーを吐かれてしまいます。呼び出す際はself以外の引数を指定する必要があり、selfのみの場合は引数無しで使えます。
また、Pythonではメソッドのオーバーロード(引数違いの同名メソッドの定義)というのができません。「違う性質のものは違う名前をつけろ」ということらしいです。
纏めると下図のようになります。
Pyrhon | 内容 | C# での対応 | 実装方法 |
---|---|---|---|
インスタンス変数 | インスタンスごとに定義される変数 | 通常のメンバー変数 |
self.hoge の形でselfをつけてメソッド内で定義 |
クラス変数 | 全てのインスタンスで共通変数 | static なメンバー変数 | クラス内で通常の変数と同様に定義 |
メソッド | クラス内で定義される関数 | 通常のメソッド/staticなメソッド | 引数にself をいれてやる。 |
Pythonではこのほかにクラスの外側で定義されるグローバル変数などもありますが、これはC#では対応するものがありません。強いて言えばstaticなクラスで定義されたstaticな変数でしょうか。グローバル変数は全部大文字で名前付けするというのがお約束らしいです。
以上をまとめて実際にコードにすると次のようになります。
VALUE_G = 'hoge' # グローバル変数
def class Hoge():
value_c = 'class' # クラス変数
def Method(self):
self.value_i = 'instance' # インスタンス変数
(追記(10/19))
@shiracamus さんからご指摘いただいて改めて見直しました。
クラス変数、およびインスタンス変数はクラスの外側からでも定義できます。
ただし、あるクラスのオブジェクトに対してインスタンス変数を定義した場合、同じクラスから作られた他のオブジェクトについてその変数にアクセスしようとするとエラーが出ます。
纏めると下記のようになります。
class Parson:
kind = "human" # これはできる(クラス変数の宣言)
# self.name = "hogehoge" # これはできない
def __init__(self):
self.name = 'Joe' # これはできる(インスタンス変数の宣言)
Parson.out = "hoge" # 外からクラス変数を宣言する。
A.job = "hogehoge" # 外側からのインスタンス変数の宣言
print(A.job)
# >>hogehoge (宣言した値が入っている)
B = Parson()
print(B.name)
# >> Joe (クラス内部で宣言した値が入っている)
print(B.out)
# >> hoge (上で定義した値が入っている)
print(B.job) # Bにはjobが定義されていないため、これはエラーが出る
なお、selfというのは予約語ではなく「慣例でそう書く」というだけで、thisでもよいらしいです。
やらない方がいいですが。
加えて、クラス変数について、staticと同じと書きましたが、immutableな型に対しては値の変更がオブジェクトを超えては反映されないということらしいです(下記参照)
[参照] [https://qiita.com/kxphotographer/items/60588b7c747094eba9f1]
アクセス制御
C#では以下の4種類のアクセス修飾子が存在し、必要に応じて使い分けることでカプセル化が達成されます。
- public : すべての場所からアクセスできる。
- internal : 現在のアセンブリ内でのみアクセスできる。
- protected : 継承先のみアクセスできる。
- private : 外からはアクセスできない。
Pythonではアクセスを修飾子で制御するという概念がありません。どこからでも好きな要素にアクセスできます。
ただし、慣習的にprivateなものには__(アンダーバー2つ、もしくは1つ)をつけることとされています。なのでlintの設定によってはアクセスしようとすると警告が出ます。
(追記)
@htsign さんからご指摘いただきました。
アンダーバーは1つが慣例で、2つをつけると内部的に変数名が変わり疑似的に保護されるそうです。
継承
C#、Pythonでの継承の定義は以下のようになります。Personクラスを継承してStudentクラスを作る形です。
class Student : Person
{
public stirng id {get; set;}
}
class Student(Person):
id = 'aaaa'
C#では多重継承が禁止されていますが、Pythonでは多重継承可能となっています。
メソッドのオーバーライドも原理的には可能です。正確には「継承先で継承元のメソッドと同じ名前のメソッドを定義して上書きする」という行為が可能というだけで、C#のようなvirtual
やoverride
と言ったキーワードはありません。なので「やらない方がいいし、やるなら気を付ける。」ということのようです。
継承元のメソッドや変数を参照したい場合はsuper().hoge
という形で呼び出します。
特殊メソッド
C#ではすべてのclassはObject型を継承した型として定義されており、Object型で規定されたToString()
とEquals()
というメソッドについてオーバーライドすることができます。こうした規定済みのメソッドを「特殊メソッド」と呼び、PythonではC#の比にならないくらい定義されています。詳しくは下記の公式を参考してください。
各種の演算子に対して自由に定義を行ったりもできます。例えばpathlib.Path
というファイルのパスを扱うモジュールでは「/」を使ってファイルパスを結合することが可能です。なかなか衝撃でした。
[参考] [https://docs.python.org/ja/3.6/reference/datamodel.html#special-method-names]
インターフェースと抽象クラス
PythonでC#のようなインターフェースや抽象クラスという概念を実装する場合、専用のモジュールをインポートする必要があります。詳しくは以下のサイトが参考になります。
[参考] [https://qiita.com/baikichiz/items/7c3fdb721bb72644f638]
名前空間とモジュール
C#ではnamespace
を使って名前空間を定義し、その中でClassなどを定義しますが、Pythonでは対応するキーワードがありません。代わりにモジュールという概念が規定されています。
モジュールはクラス定義その他をまとめて記述したファイルを意味します。C#でusing
キーワードを使って名前空間を取り込んでいたように、Pythonではimport
を使ってモジュールを取り込みます。更にモジュール同士はパッケージという形で構造化され、これはファイルが格納されているフォルダに該当します。C#では1ファイル1クラス定義が基本でしたが、Pythonで同じようにをやるとimportまみれになるので推奨されません。
C#では同じ名前空間を書けばファイルの位置に関係なくコンパイル時にひとまとめにしてくれましたが、Pythonではそういう便利なことはしてくれません。違うフォルダにあるファイルは違うパッケージのモジュールとして認識されます。なので、パッケージを作る場合はファイル単位でしっかり構成を意識する必要があります。まあ、それはC#も同じと言えば同じなんですが。