【入門】C#技術者の為のPython独学!クラス編

Python入門
この記事は約14分で読めます。

今回はPython のクラスについて解説します。

クラスの考え方は同じですが、仕様についてはC#とはだいぶ違いますので、ちょっとしたカルチャーショックを受けるかもしれません。

クラスのインスタンス化とメソッドの呼び方

クラスのインスタンス化は、変数名 = クラス名() と記述するだけです。

C#の様に new は必要ありません。

例えば、 MyClass というクラス定義がされていると仮定すると、次のように記述します。

メソッドの呼び出し方法についてもC#と同じで、()の中に引数をカンマ区切りで列記します。

   インスタンス名.メソッド名(引数1,引数2,・・・)

MyClassにmy_methodという名前で引数に数値を渡すメソッドがあると仮定すると、次のようになります。

指定した引数に対する値の設定

特定の引数に対して狙い撃ちで値を代入したい場合、C#の場合はコロン(:)を使いましたが、Pythonの場合はイコール(=)を使って 変数名=値 と記述をします。

例えば calcというメソッドに対して、position という名前の引数が定義されていて、そのpositionに直接50という値を指定したい場合、次のようになります。

命名規則

C#の場合は先頭を大文字にすることが慣習になっていますが、Pythonの命名規則においては クラス名は大文字メソッド名は小文字から始まることになっています。

しかし、完全に徹底されている訳ではなく、ライブラリによって必ずしもそうなっていない場合もあります。

対象ルール
クラス単語の先頭を大文字MyFavoriteClass
型変数単語の先頭を大文字MyFavoriteType
メソッド全て小文字でアンダースコア区切りmy_favorite_method
関数全て小文字でアンダースコア区切りmy_favorite_funcion
変数全て小文字でンダースコア区切りmy_favorite_instance
定数全て大文字でアンダースコア区切りMY_FAVORITE_CONST

では、次にクラスの定義の仕方について解説していきます。

クラスの定義

Pythonのクラス定義は class クラス名: で始まります。

Pythonの場合、for も if もコロン( : ) を末尾に付加しますが、class も同様です。

では、最も単純な(中身が無い)クラスをC#とPythonで記述してみましょう。

Pythonの場合、class myclass : の後に何らかのステートメント(行)が必要なので、pass という何もしないステートメントが用意されています。

また、C#の場合は class の前にスコープを示す public や private を記述することが多いですが、python の場合は何も記述しません。

では、クラスの中に目を向けてみましょう。

クラスの構造

Pythonのクラス構造は次のようになっています。

クラス定義の直後にはクラス変数を記述し、次にコンストラクタを記述し、以降はメソッドを記述していきます。

コンストラクタとメソッド

コンストラクタもデストラクタもメソッドの一種であるため、メソッドとして説明します。

メソッドは def メソッド名 : から始まります。

ちなみに、コンストラクタは __ init __ という名前が、デストラクタは __del __ という名前が固定で用意されています。

メソッドの第1引数には自分自身のインスタンスが、第2引数以降にはメソッド呼び出し時の引数の値が渡される仕様になっており、第1引数の名前は self と書くのが慣例になっています。

種類記述例
コンストラクタdef __init__(self,引数1,引数2,・・・):
デストラクタdef __del__(self,引数1,引数2,・・・):
メソッドdef クラス名(self,引数1,引数2,・・・):

パブリックメソッド、プライベートメソッド、ローカル関数

C#では、外部からアクセス可能なプライベートメソッドと、クラス内でのみ利用可能なプライベートメソッド、メソッド内だけで利用可能なローカル関数がありますが、Pythonでも用意されています。

Pythonの場合はメソッド名の先頭にアンダースコアを2個(__)記述することでプライベートメソッドを定義したことになり、アンダースコアが無いか1個の場合はパブリックメソッドとなります。

メソッド及びローカル関数から戻り値を返したい場合は C# と同様に return を使います。

以上の内容を踏まえたサンプルソースは次のようになります。

メソッドやローカル関数を使う場合は、呼び出す前に定義が必要になります。

これは、Pythonが入力されたステートメントを逐次実行するスクリプト言語であるためです。

C#の場合は、どの位置にクラスやメソッドの定義を書いても問題はありませんが、Pythonの場合は記述する位置に注意しましょう。

引数省略時の既定値

メソッドも関数と同じものですから、省略可能な引数を定義することが可能で、省略時の既定値も定義できます。

  def メソッド名(self,第1引数=既定値,第2引数=既定値,・・・)

例えば calc という名前で、第1引数は省略不可の数値、第2引数は省略可能で、省略した場合は “ABC” という文字列を受取るようなメソッドを、C#とPythonで記述すると次のようになります。

可変長引数

C# では 関数名(param object[] args) という記述によりメソッドの引数を可変長にできますが、Pythonの場合は変数の先頭にアスタリスクを1個又は2個付けることで実現できます。

*args 又は **kwargs

アスタリスクが1つの場合は、タプルとして受け取り、アスタリスクが2個の場合は辞書として受け取ります。

言語仕様上、引数名はなんでも良いのですが、慣習としてタプルは *args 、辞書は **kwargs を使うことが推奨されています。

以下はタプルとして受け取るサンプルです。

辞書として受け取る場合は、 キー=値,キー=値、・・・ という具合にキーと値をイコールで結んだものを1つの要素とし、カンマ区切りで羅列していきます。

本来の辞書だとキーに数値が指定可能ですが、可変長引数の場合はキーは必ず文字列である必要があります。

また、キーとなる文字列はシングルクォートで囲みません(囲むとエラーになる)。

以下は、辞書として受け取るサンプルです。

クラス変数、インスタンス変数、ローカル変数

C#と同様、変数は大きく分けてクラス変数、インスタンス変数、ローカル変数の3種類が用意されていて、定義場所とself.を変数名の前に付けるか否かによってによって区別されます。

また、クラス変数、インスタンス変数、ローカル変数は、それぞれアクセス可能な範囲(スコープ)が異なる。

種類Pythonの記述ルール意味と記述例
クラス変数(パブリック)変数名クラス定義の直後に記述する。
全てのインスタンスで共通(共有)となる。
クラスの外部からアクセスが可能。
例:name 、data
クラス変数(プライベート)__ + 変数名クラス定義の直後に記述する。
全てのインスタンスで共通(共有)となる。
クラス内でのみアクセスが可能。
例:__name、__data
インスタンス変数変数名コンストラクタ内で self. を付けて定義する。
インスタンス内でのみ有効となる。
例:self.name,self.data
ローカル変数変数名それぞれのメソッド内で定義する。
定義したメソッド内でのみ有効となる。
例: name、data
クラス変数

クラス変数については後で詳しく説明しますが、全てのインスタンスで共有される変数で、C#で例えるなら public static 変数に相当します。

外部からアクセス可能なパブリック変数とクラス内でのみアクセス可能なプライベート変数の2種類が用意されていますが用意されており、プライベート変数は必ずアンダースコア2個から始めるというルールになっています。

インスタンス変数

インスタンス変数はインスタンスを生成する度に領域が確保される変数で、C#における public 変数になりますが、外部から参照することは出来ない点が異なります。

ローカル変数

ローカル変数はC#と同じくメソッド内でのみ有効な変数です。

メソッド内におけるクラス変数とインスタンス変数の参照方法

メソッド内での変数の参照方法については、C#と少し異なります。

C#の場合、クラスの先頭に記述した public また private 変数をメソッド内で使用する場合、そのまま変数名が記述できます。

一方、Pythonの場合はメソッドの第一引数で渡された自分自身のインスタンスを経由して利用する必要があります。

第一引数では self という引数名を書くことが慣例になっていますので、self.変数名 という記述をしなければなりません。

下記の例では、クラス変数、インスタンス変数のどちらもメソッド内で参照する場合、self.を付けなければなりません。

インスタンス変数の落とし穴

クラス変数とインスタンス変数は定義する場所によって決まりますが、メソッド内からは同じ方法(先頭に self.を付ける)でアクセスできます。

これは一見便利なようで、ミスを生み出す可能性があります。

多くのケースではクラス変数のようにインスタンスで共通となる変数を定義することは稀です。

C#の場合、自分で明示的に static という記述をしないかぎりクラス変数にはなりません。

自分ではインスタンス変数を定義したつもりで、C#の感覚でクラス定義の直後に変数を書いてしまうと、それはクラス変数になってしまうため、予期せぬ動作になる危険性=見つけにくいバグを生み出す結果になります。

ただ、単純な値や文字列を保持する変数であれば、この勘違いが問題になることは少ないかもしれません。

しかし、リストや辞書のような append や add で値が追加できる変数については、インスタンスを生成するたび値が追加されてしまいます。

間違いを避けるためにも、クラス内で利用する変数は、極力コンストラクタの中に記述(コンストラクタの中で初期化)したものを使うことを心がけた方が無難です。

プライベート変数も強引に外部からアクセス可能

少し特殊な仕様として、クラス変数をプライベートとして定義しても、強引に外部からアクセスできてしまう点があります。

具体的には、クラス名の先頭にアンダースコアを1個だけ付けて、続けてプライベート変数名を記述するだけです。

クラス変数の値を変更すると、全てのインスタンスに連動(伝搬)する

クラスを定義した後で、そのクラス名でクラス変数の値を書き換えると、既に生成済みの全てのインスタンスに対しても、その内容が連動(伝搬)されてしまいます。

C#では有り得ないことですが、Pythonの場合はクラス定義の時点で定義情報がクラス変数ごとメモリ上に展開され、クラス変数のアドレスがインスタンスに渡されているのでしょう。

連動しているからと言って、インスタンスの count に値を代入してしまうと、その時点で別物となってしまいます。

これは、後述する「動的なクラス変数の追加」によるものです。

後から自由にパブリック変数を追加できる

一旦定義したクラスに対して適当な変数名で値を代入すると、そのクラスにパブリック変数が追加されます。

つまり、設計図を後から自由に書き換えられてしまうのです。

また、クラスからインスタンスを生成し、そのインスタンスに対して適当な変数名で値を代入すると、そのインスタンスに対してパブリック変数が追加されます。

前述の説明で、インスタンス側の count を変更すると別物になると言いましたが、これはインスタンス側に count という名前のクラス変数が追加されたため、元のcount が参照できなくなったという事です。

イメージでいうと、インスタンス生成時に引き継いできたクラス変数のアドレスが、新しく生成されたクラス変数のアドレスで書き換えられたと考える事ができます。

この様に、定義済みのクラスに対して変更を加えてしまうと、その時点で設計図が変わってしまい、以降はその設計図によってインスタンスが生成されるようになります。

言い換えると、定義済みのクラスに初期値が記述されていたとしても、ソースコードの書き方ひとつで、予期せぬ値がインスタンス生成時の初期値として代入される危険性をはらんでいます。

よほどの必要が無い限り、この仕様は使わないほうが良いと思います。

クラス外で定義された変数名が参照できる

これもC#では有り得ないことですが、クラスの外で定義した変数が、クラスの中で参照出来てしまいます。

例えば下記のソースコードにおいて、最初に bata という変数を定義しています。

一方、MyClass の calc というメソッドにおいて、bata * data の値を return で戻るように記述しています。

bata はクラス変数にもローカル変数にも存在しませんのでエラーになると思いきや、クラス外のbata の変数を参照し、5×1000= 1000 という結果が返されます。

もし、calc が引数の二乗を計算するメソッドだったとして、data * data とするところを、間違えて beta * data としてしまていたとしたら・・・・

上記の例は単純だから間違いに気づきますが、もっと複雑な処理だったら見つけ難いですよね。

この仕様の恩恵を受けるケースがどれだけあるのか思いつきませんが、バグを避けるためにも、クラスの定義より手前に変数は書かない方が無難です。

クラスの継承

Pythonもクラスの継承は当然可能で、クラス名に()を付けて、その中に継承元のクラス名を記述します。

class クラス名(継承元クラス名) :

ちなみに、Pythonでは多重継承が可能で、カンマ区切りでいくつも継承することが出来ます。

 class クラス名(継承元クラス名1,継承元クラス名2,・・・)

しかし、C#やJavaでは多重継承時の振る舞いが複雑になることから、言語仕様上使えないようになっています。

Pythonで多重継承が可能だからと言って、多様するとバグで収集が付かなくなることも有り得るので、使う場合は慎重に検討してください。

親クラスのメソッド上書き(オーバーライト)

親のメソッドを上書する場合、そのメソッド名を新たに定義するだけです。

親クラスのメソッド呼び出し

オーバーライトしたクラスの中から、親クラスのメソッドを呼び出したい場合、super() を使います。

super().親クラスのメソッド名

先ほどのサンプルで calc メソッドを上書きしましたが、その calc メソッドの中から親の calc メソッドを呼ぶには次のように記述します。

コンストラクタの中で親クラスのコンストラクタを呼び出したい場合は、次のように記述します。

super().__init_(引数1,引数2,・・・)

下記は引数の無いコンストラクの例ですが、MyClass のコンストラクタの中から親クラスのコンストラクタ super() を使って呼び出しています。

オーバーロード

オーバーロードとは、引数や戻り値が異なる同じ名前のメソッドを複数持てることなのですが、実は Python にはありません

多くの場合はメソッド定義時に必要な引数を全て定義しておき、省略できるよう初期値を設定しておくことで代替は可能です。

Pythonでは「挙動が違う=名前を変えるべき」という思想なのでしょう。

singledispatch というライブラリを使って実現することは可能ですが、C#のようにスッキリと書くことができないし、これが無くてもそれほど困ることもないので、ここでは割愛します。

まとめ

今回はクラスについて解説しました。

C#の様なコンパイル型言語と比べると、かなり柔軟な仕様ですね。

Pythonのクラスにおいて特に注意すべき点として下記が挙げられます。

  • クラス定義を変更すると、その時点で全てのインスタンスに変更した内容が連動(伝搬)する。
  • 存在しない変数名に値を代入することで、後からクラス定義やインスタンスに新たなクラス変数が追加できる。
  • クラス定義の外で宣言した変数を、クラス定義内で参照することができる

これらはPythonの柔軟さが故の仕様ですが、不用意に使うと予期せぬ挙動を招く恐れがあるので、乱用すべきではありません。

その点にさえ注意しておけば、あとはC#と同じような感覚でクラスが使えます。

タイトルとURLをコピーしました