見出し画像

Python基礎4:クラス(初級編)

概要

Pythonのクラスの説明となります。初級編なのは私が未熟であり、まだまだクラスは奥が深いためとりあえず初級としております。

【ご留意事項】
●私が作成したクラス(機械学習用データセット、自動売買システム)をベースに作成しており、web開発レベルの内容までは達しておりません。
●コードの動作は問題ないですが、解釈が間違っているかもしれません。
●多重継承のような複雑な内容は避けています。

1.用語の説明

用語が多く混乱しやすいため、参考までに一部の用語を記載しました。

●インスタンス:クラスから作成されたモノであり、クラスで定義した変数やメソッドを保持しています。
●インスタンス化:クラスを用いてインスタンスを作成する処理
●オブジェクト:データとメソッドがセットになっているもの
●基底クラス(親クラス):クラス継承時に継承元のクラス
●クラス変数:メソッド内に入っていない変数(def()の上のやつ)
●組み込み関数:Pythonで最初から使える事前にpythonに組み込まれている関数です。
●属性:インスタンス変数を呼び出す時の.ドットの後ろにある名前
●デコレーター:クラスや関数に特別な機能を追加します。
●特殊メソッド:__アンダーライン2つで囲まれているメソッドであり、通常のメソッドとは異なる特別な挙動をします。
●メソッド:クラスの中で使用される関数をメソッドと呼びます。

2.概念図(自己流)

一般的に「クラスとは設計図であり、その設計図をもとに作成された物がインスタンスである。」と言われます。最初は”なるほど”と思いますが、いざ自分で作ってみるとよくわからん!となりました。

自分なりで解釈すると「変数と関数を集めてpythonの組み込み関数も使えるもの」という理解です。頭のイメージは下記の通り。

classの説明.drawio

関数とクラスの違いですが、(個人的な意見として)クラスは変数を保持できたり専用の処理を入れたりできるため高度な処理ができるのが便利です。

3.メインコード(Class)

説明用クラスです。説明用のためクラスの中身に意味はありません。

[In]
import string #文字作成用

class Cobject:
   cattr1 = 11 #クラス変数
   cattr2 = 22 #クラス変数
   
   @classmethod
   def classfunc(cls): #クラスメソッド
       print(f'クラスメソッドを実行!クラスオブジェクト利用可能->{cls.cattr1}, {cls.cattr2}')

   def __init__(self, iattr1): #初期化
       print('__init__が実行!')
       self.iattr1 = iattr1 #インスタンス.属性で値を呼び出せる
       self.iattr2 = 100
       self.iattr3 = string.ascii_lowercase #output->'abcdefghijklmnopqrstuvwxyz'
       self.count = 26 #後でイテレータで使用する->self.iattr3も文字数

   def __call__(self, callattr): #特殊メソッド:実行時にメソッド名が不要
       print(f'callメソッド名の実行!{callattr}')

   def function1(self, f1attr):
       self.f1attr = f1attr
       return self.cattr1 + f1attr #クラス変数も頭にselfがないとエラー発生

   def function2(self, f2attr):
       return self.f1attr + f2attr #クラス変数も頭にselfがないとエラー発生

   @property
   def function3(self): #オーバーライド試験用->戻り値(return)必須
       return self.iattr3 #output->'abcdefghijklmnopqrstuvwxyz'
   
   def makeval(self):
       for idx, word in enumerate(self.iattr3): #'abcdefghijklmnopqrstuvwxyz'の26文字+index番号をfor文で回す
           setattr(self, word, idx) #これでidxを値に持つwordを変数名としたインスタンス変数ができる。

   def __len__(self): #インスタンスをlen()に入れた時に所定の数値を返
       return len(self.iattr3) #ここでは上の文字列の長さを返すため数値は26(文字)となる。        

   def __getitem__(self, index): #インスタンスをlen()に入れた時に所定の数値を返す
       self.iattr3_list = list(self.iattr3) #文字列をリスト化
       return self.iattr3_list[index]

   def __iter__(self): #イテレーター自身を返す。
       return self

   def __next__(self): #コンテナの次の要素を返す
       if self.count <=0:
           raise StopIteration
       self.count -= 1
       return self.iattr3_list[25-self.count]

4.クラスの詳細説明

詳細説明より、個々に処理をしてどのような動作になるかを示します。

4-1.エラーになる処理(インスタンス化前)

下記はすべてエラーになります。そのことから下記が分かります。

●インスタンス化しないとメソッドは使用できない
 ー>クラスオブジェクトからメソッドを使用できない
●インスタンス化しないとcallメソッドは使用できない
 ー>インスタンス化と同じ形のためPCが認識できない
●インスタンス化しないとインスタンス変数は使用できない
 ー>初期化(__init__())されるまでは変数が存在しない

[In]
#Cobject.function1(5) #インスタンス化前にメソッドは使えない->TypeError: function1() missing 1 required positional argument: 'f1attr'
#Cobject() #インスタンス化しないと、callメソッドと初期化を理解できない->TypeError: __init__() missing 1 required positional argument: 'iattr1'
#print(Cobject.iattr1) #インスタンス変数はインスタンス化しないと存在しない->AttributeError: type object 'Cobject' has no attribute 'iattr1'

4-2.クラス変数とクラスメソッド

クラス変数とクラスメソッドはインスタンス化前に使用できます。

[In]
print('クラス変数:', Cobject.cattr1, Cobject.cattr2) #クラス変数の確認
Cobject.classfunc() #クラスメソッド:インスタンス化前にも実行可能

[Out]
クラス変数: 11 22
クラスメソッドを実行!クラスオブジェクト利用可能->11, 22

4-3.インスタンス化、変数、メソッド

インスタンス化するとインスタンス変数およびメソッドが使用できます。なお、インスタンス化時に__init__()メソッドは自動で実行されます。

[In]
ins1 = Cobject('初期化時の定数')
print('ins1.iattr1の値:', ins1.iattr1) #インスタンス変数の確認
ins1('call用変数を追加') #callメソッド

[Out]
__init__が実行!
ins1.iattr1の値: 初期化時の定数
callメソッド名の実行!call用変数を追加

あるメソッド戻り値を別メソッドで使用するなら事前にメソッド実行が必要

[In]
# ins1.function2(100) #先に持ってくるとエラー:AttributeError: 'Cobject' object has no attribute 'f1attr'
ins1.function1(2) #クラス変数+入力値
ins1.function2(100) #後はOK

[Out]
13
102

メソッドはメソッド名()で実行するが@propertyだと()が不要である。propertyをつけたメソッドの戻り値を更新したいなら@method.setteer

[In]
print(ins1.function3) #@propertyにより()を付けずに呼び出せる
# ins1.function3=100 #更新できるようにするためには@関数名.setterが必要:エラー:AttributeError: can't set attribute
[Out]
abcdefghijklmnopqrstuvwxyz

4-4.組み込み関数(setattr(), getattr())

クラスにイテレータリストなどを渡してインスタンス変数を作成するならsetattr()、値を取得するならgetattr()、削除ならdelattr()で処理可能。

[In]
ins1.makeval() #setatterを使用してインスタンス変数を作成->a~zの属性に対して0~25の整数が与えられている
print(ins1.a, ins1.c, ins1.t, ins1.z) #適当な属性を与えて値を取得
print(getattr(ins1,'a'), getattr(ins1,'c'), getattr(ins1,'t'), getattr(ins1,'z')) #同上※getattr(インスタンス, 属性)で取得
delattr(ins1, 'a') #インスタンス変数を削除
print(ins1.a) #上で削除したためエラー発生

[Out]
0 2 19 25
0 2 19 25
AttributeError: 'Cobject' object has no attribute 'a'

4-5.特殊メソッド(__init__, __call__以外)

インスタンスに下記のような処理したい場合に使用(一部未紹介)

【インスタンス(以下ins)への追加動作】
●__len__(self):len(ins)で返す値->長さを設定できる。
●__getitem__(self):ins[key]で値を取得できる->辞書のように扱える
●__iter__(self), __next__(self):イテレータとして扱える->for文で回せる
●__str(self)__:print(ins)の出力値を決められる->print文で中身を見れる
●__repr(self)__:オブジェクト名のみ入力した時の出力値を決められる
●__add__(self):インスタンスの足し算の出力を決める(和差商積もある)

一部の結果は下記の通り

[In]
print('文字数を数値に取っているため26を返す->', len(ins1))
print([ins1[i] for i in range(6)]) #__getitem__を使用することで辞書のような形でインスタンスから値を取得することができる。
print([i for i in ins1]) #__iter__と__next__を組み合わせることでインスタンスをイテレーターとして使用可能である。
print([i for i in ins1]) #イテレーターが消費されるため2回目では空の値が出力される。->再利用する場合はクラスを2つに分割する

[Out]
文字数を数値に取っているため26を返す-> 26
['a', 'b', 'c', 'd', 'e', 'f']
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
[]

なお、クラスオブジェクトは特殊メソッドを持てない??と思う。

[In]
print('文字数を数値に取っているため26を返す->', len(Cobject))
[Out]
TypeError: object of type 'type' has no len()

5.継承

親クラスの変数やメソッドと同じものを引き継いで新しいクラスを作成することを継承オーバーライドと言います。ベースのクラス絶対に使う機能だけ集めただけ作成しておいて機能を追加する時に使用します(Pytorchのnn.Moduleなど)。

5-1.継承テスト1:関数追加、メソッドの継承

そのまま継承して2つメソッド追加してみました。新規クラス内で親クラスのメソッドを呼び出す親クラスメソッドの戻り値が欲しい場合はsuper()をつけます。結果として下記が確認できました。

●親クラスの変数やメソッドはそのまま使用可能
●super().methodで親クラスのメソッドの戻り値を取得できる
●親クラスと同じメソッド名を追加すると子クラスのメソッドが優先

[In]
class Overrideobj(Cobject): #クラス名(基底クラス名)
   def addfunc(self, x):
       print(f'オーバーライドしたクラスのメソッド->変数{x}')

   def function2(self):
       print('function2メソッドはオーバーライドしました。')
   
   def makeupperval(self):
       lowercases = super().function3 #基底クラスのメソッドを呼び出したい場合はsuper()をつける
       return lowercases.upper() #function3はpropertyにしたため()は不要

#インスタンス化前*************************
print('クラス変数:', Overrideobj.cattr1, Overrideobj.cattr2) #クラス変数の確認
Overrideobj.classfunc() #クラスメソッド:インスタンス化前にも実行可能
print('*'*100) #処理時に線を入れるだけ

#インスタンス化**************************
ins2 = Overrideobj('初期化時の定数')
print('ins2.iattr1の値:', ins2.iattr1)
ins2('Overrideでcallメソッド') #callメソッド

print(ins2.function1(2)) #元のメソッドも使用可能
ins2.function2()
# print(ins2.function2(100)) #TypeError: function2() takes 1 positional argument but 2 were given

print(ins2.makeupperval()) #super()で基底クラス(親クラス)から継承して修正した結果を出力

#特殊メソッドの確認
print('文字数を数値に取っているため26を返す->', len(ins2))
# print('文字数を数値に取っているため26を返す->', len(Overrideobj)) #クラスオブジェクトはエラー:TypeError: object of type 'type' has no len()
print([ins2[i] for i in range(6)]) #__getitem__を使用することで辞書のような形でインスタンスから値を取得することができる。
print([i for i in ins2]) #__iter__と__next__を組み合わせることでインスタンスをイテレーターとして使用可能である。
print([i for i in ins2]) #イテレーターが消費されるため2回目では空の値が出力される。->再利用する場合はクラスを2つに分割する
[Out]
クラス変数: 11 22
クラスメソッドを実行!クラスオブジェクト利用可能->11, 22
****************************************************************************************************
__init__が実行!
ins2.iattr1の値: 初期化時の定数
callメソッド名の実行!Overrideでcallメソッド
13
function2メソッドはオーバーライドしました。
ABCDEFGHIJKLMNOPQRSTUVWXYZ
文字数を数値に取っているため26を返す-> 26
['a', 'b', 'c', 'd', 'e', 'f']
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
[]

5ー2.継承テスト2:__init__()の扱い

結果のまとめとコードは下記の通り

●__init__(self)を継承せず初期化すると親クラスの__init__(self)が消える。
●__init__(self)を継承せず初期化すると親クラスの引数以外の変数も消える。
●__init__(self)を継承すると親クラスの引数・変数も使用できる。

[In]
class Overrideobj2(Cobject): #クラス名(基底クラス名)
   def __init__(self, newattr):
       self.newattr = newattr

ins3 = Overrideobj2(999)
print(ins3.newattr)
print(ins3.iattr1)
print(ins3.iattr2, ins3.iattr3, ins3.count)

[Out]
999
AttributeError: 'Overrideobj2' object has no attribute 'iattr1'    
AttributeError: 'Overrideobj2' object has no attribute 'iattr2'
[In]
class Overrideobj3(Cobject): #クラス名(基底クラス名)
   def __init__(self, iattr1, newattr):
       super().__init__(iattr1)
       self.newattr = newattr

ins3 = Overrideobj3('iaatr1用の引数', 777)
print(ins3.newattr)
print(ins3.iattr1, ins3.iattr2, ins3.iattr3, ins3.count)

[Out]
__init__が実行!
777
iaatr1用の引数 100 abcdefghijklmnopqrstuvwxyz 26

参考資料


あとがき

自分が作りたいものを勉強する時クラスをどこまで理解しないといけないかがわからないのが独学の辛さやね・・・
あと、プログラミングの勉強すると知らない単語を知らない単語で説明されるループが多いから本当に理解が難しい。


この記事が気に入ったらサポートをしてみませんか?