PYTHON勉強会
第6回Python勉強会
公開日:
2020/06/21
第6回Python勉強会

初めに

長かったPython勉強会も今回で終わりです。皆さん、今までお疲れ様でした。今回はclassの紹介をし、オブジェクト指向プログラミングの概念の説明をします。

複素数の計算の掛け算をする関数を書いて下さい。(Pythonは標準で複素数型の数値をサポートしていますが、それは使わない事にします)

>>> a = 1
>>> b = 2
>>> c = 3
>>> d = 4
>>> # 複素数をa + bi、c + diとする
>>> def conplex_mul(x1, y1, x2, y2): # x1 + y1i * x2 + y2i
>>>   return x1 * x2 - y1 * y2, x1 * y2 + x2 * y1
>>> 
>>> e, f = complex_mul(a, b, c, d)
>>> e
-5
>>> f
10

ここで初出な事実として関数の返り値って,で複数返す事が出来るんですね。これはタプルと呼ばれるやつで本当はreturn (ほげ, ほげ)って形なんですが、書きやすさの観点から()は省略可能です。

まあそれはいいんですが、皆さん思うことはありますか?a+biで1セットなのにaとbがバラバラなの、嫌じゃ無いですか?

オブジェクト指向の基本: classを用いて複数の変数とそれらを扱う関数をまとめて1つの塊として扱う

>>> class MyComplex:
>>>   real = 1
>>>   img = 2
>>>   
>>> c1 = MyComplex()
>>> c1.real
1
>>> c1.img
2
>>> c1.real = 3
>>> c1.real
3
>>> c2 = MyComplex()
>>> c2.real
1

MyComplex型のc1を作るとc1.real(1)とc1.img(2)が作られます。最後にc2を作っていますが、c1とc2は別物です。コードを見ればなんとなく理解できると思います。

しかし、これだと1+2iで初期化されていて微妙ですね。

>>> class MyComplex:
>>>   def __init__(self, real, img):
>>>     self.real = real
>>>     self.img = img
>>> 
>>> c1 = MyComplex(3, 4)
>>> c1.real
3

init()関数はコンストラクタと呼ばれる特殊な関数でインスタンス化する時に呼び出される初期化の関数です。つまり、classを実際に作る時に呼ばれる関数です。第一引数(self)は自分自身を表す変数で、それに初期値を代入します。

さて、複数の変数をまとめる事が出来ましたが、このままだと不便ですね。複素数の足し算をする関数を定義してみましょう。

>>> class MyComplex:
>>>   def __init__(self, real, img):
>>>     self.real = real
>>>     self.img = img
>>>   def plus(self, cmplx):
>>>     self.real += cmplx.real
>>>     self.img += cmplx.img
>>>   def print(self):
>>>     print(self.real, "+", self.img, "i")
>>> 
>>> c1 = MyComplex(3, 4)
>>> c2 = MyComplex(1, 2)
>>> c1.print()
3 + 4 i
>>> c1.plus(c2)
>>> c1.print()
4 + 6 i

classの中の関数をメソッドと呼びます。例えばListのappend()とかもメソッドです。pythonではメソッドの第一引数はselfとします。基本的にデータの書き換え等はselfを使って書き換えましょう。

一番最初に導入したclassに直接変数を書き込むやり方は定数を定義するならば良いですが、特にmutableなもの(Listとか)が置かれると、全てのclassで共有されてしまうので、__init__()でself.d = []のように初期化しましょう。ちなみにイミュータブルなもの(数値)なら上手く動作します。

ちなみにPythonでは複素数は2+1jみたいな形で書けます(1jの1は必ずいるっぽい)

オブジェクト指向の考え方1: classによってデータをまとめ、再利用性を高めている

演習問題?) RPGを作る時どのようにclassでまとめるのがいいか考えてみましょう。

次にclassの継承について

オブジェクト指向の考え方2: classの継承によって似ているclassをコピーする

これがオブジェクト指向のメイン部分です。例えばRPGの敵キャラのclassを作ったとします。他の敵キャラを作ろうとする時、それをコピーして、違う所だけをコードで書くと良さそうですね。

>>> class A:
...   def __init__(self):
...     self.a = 0
...   def print(self):
...     print(self.a)
... 
>>> class C(A):
...   def __init__(self):
...     self.a = 1
... 
>>> a = A()
>>> a.print()
0 
>>> c = C()
>>> c.print()
1

これの利点は特にC++やJavaなどの静的型付け言語で強みになります。base classの型に派生classを突っ込む事ができて、これにより表現の幅が増えます。

注意点として、継承を使うと結合が密になり、変更がしづらいコードになりがちです。

多重継承

複数のclassを継承します。これは一般にヤバイと言われますが動的型付け言語ならそこまで悪くは無い気がします(素人意見です)。

多重継承を用いるのがいいパターンは恐らくあります。しかし大抵の場合多重継承を使わずとも書けて、その場合は使わない方がいいでしょう。正直これは僕の経験不足で説明できないです。ごめんなさい。

基本は普通の継承と同じですが、メソッドが被った時は先に書いている方優先です。次の例だとclass C(A, B):なのでBよりAが優先されます。

>>> class A:
...   def __init__(self):
...     print("A")
... 
>>> class B:
...   def __init__(self):
...     print("B")
... 
>>> class C(A, B):
...   pass
... 
>>> c = C()
A

オブジェクト指向の考え方3: class内の変数は基本的に隠す

基本的に変数より関数(メソッド)の方が柔軟性があります。例えばListクラスを考えてみてください。内部実装はどのようになっていると思いますか?ちなみに僕は知りません。これは抽象化の観点で見るととても素晴らしいです。

例えば最初は効率の悪いやり方で書いて、後から良いやり方に変える事も可能です。その際、データの持ち方を変えなければならなくなる事もあると思います。メンバ変数を隠しておけば何も問題ありません。

実はPythonにはデータを隠す構文というのは存在しません。プライベート変数である事を明記するためにアンダーバー1個か2個を変数名の前に付ける事があります(アンダーバー2個で継承の際の衝突を避けれます)

隠す事によって、メンバ変数の書き換えられる範囲をclass内部に狭める事が出来ます(メンバ変数を書き換える関数は外から呼び出す事が出来ますが)。これは特に有用です。例えばclassや関数による抽象化されていない1000行コードがあったとします。そこで100個の変数が使われていたとして皆さんはそれを追えますか?僕には多分無理です。変数が操作できる範囲を狭めるという意味でも重要ですし、classに分割する事で、人間が理解しやすくなります。

まとめ

オブジェクト指向はとても直感的で理解するのは簡単です。しかし、オブジェクト指向で何も考えずに書くとすぐにコードが複雑になりがちです。

それを避けるためにはデザインパターンと呼ばれる書き方に沿ってうまく設計する事が重要になります。

個人的にはオブジェクト指向は設計手法として優れているとは思いません。これは継承を使う事によりclassの内部仕様について知らなければいけない範囲が増えて、変更に弱くなるのが原因かなと思っています。

しかし、状態数が非常に多いプログラミングではオブジェクト指向よりいいやり方は今の所無い気がするので、オブジェクト指向から逃げる事は出来ないと思います。

ついで

言いたかったけど言えなかったPythonの機能

  • モジュール: ファイル単位で分割して、なんかいい感じにグローバル変数の衝突を避けてくれます。
  • エラーと例外: ファイルを開く、ネットからデータを取ってくるなどの処理は必ずしも成功するとは限りません。パーミッション拒否されたりサーバーと繋がらなかったり...そんな時に使います。
  • ファイル入出力: ファイル操作やってなかったなー。そんなに難しくないから必要になれば調べて
  • 標準ライブラリ関数: Pythonを使うなら知っとくと便利です。
  • 数値計算ライブラリ: データ解析とかやるなら知っとくといいよね
loading...