【オブジェクト指向】Pythonのクラス定義と基本について解説!

2023-07-17

Pocket

こんにちは、爽です。皆さん、いかがお過ごしでしょうか?

今回から2回に渡ってPythonのクラス定義について確認します。
クラス定義はプログラミングの花形でもある、オブジェクト指向型のプログラミングをする際に必要な構文となり、アプリケーションを開発する上では必ず知っておく必要があると思えるほど重要です。
なお、オブジェクト指向の考え方には巷に考え方が出回っているので、詳しくはそちらに譲りますが、私はプログラミングという機械的な作業を現実世界に置き換えて物事を捉える考え方と捉えています。
クラス定義はそんなオブジェクト指向型のプログラミングをする上での最初のステップとなるので必ず覚えておきましょう!

なお、継承、抽象クラス、そしてカプセル化についての記事は下記です。

■この記事の対象読者
・Pythonに興味がある方
・Pythonを学び始めてみたい方

・Pythonのクラス定義について詳細を知りたい方

なお、私はPythonをAnacondaをインストールしてJupyterで実行しています。MacのAnacondaのインストール方法とJupyterの使い方は下記記事にまとめているので良かったらご参考ください。

それではどうぞ!

クラス定義

クラスはclassキーワードを使って、class クラス名: の形式で定義できます。
なお、Python3以降は上記のclass クラス名:の形式でOKですが、他の定義の方法もあります。具体的には下記に記載の3パターンの定義方法があります。特にclass クラス名(object):の形式で定義するとそのクラスはベースクラスとして扱うという意味となり、クラスの継承をしない場合はこちらの書き方が推奨されてるとのことなので覚えておきましょう。

また、クラスの中にはプロパティと関数を定義できます。
プロパティはそのクラスが持つ属性のことです。人間で言うと年齢、性別、国籍などがそれにあたります。
関数については基本的には通常の関数と同じ要領で定義できますが、クラスの中に定義した関数の第一引数には基本的にはselfキーワードが必要です。なお、クラスの中に定義した関数は特にメソッドと呼ばれることが多いと思います。

そして、クラスを定義したら実際に使えないと意味がないので、変数 = クラス名()でクラスをインスタンス化(要はクラスを使える状態にすること)し、変数名.クラスのメソッド名()でクラスで定義したメソッドを実行します。

#クラス定義1
class Horse:
    #プロパティ
    age = 3
    #関数定義時にはselfキーワードが必要
    def print_age(self):
        print(self.age)
    def run(self):
        print('run fast!!')

#クラス定義2
class Horse():
    #プロパティ
    age = 3
    def print_age(self):
        print(self.age)
    def run(self):
        print('run fast!!')

#クラス定義3
class Horse(object):
    #プロパティ
    age = 3
    def print_age(self):
        print(self.age)
    def run(self):
        print('run fast!!')

#インスタンス化
horse = Horse()
#メソッド実行
horse.print_age()
horse.run()

# 3
# run fast!!

コンストラクタとデストラクタ

コンストラクタ

コンストラクタはクラスがインスタンス化されたときに必ず実行される処理のことでdef __init__(self):の形式で定義できます。インスタンス化した際に何か初期設定をしたい時に使います。
また、コンストラクタの時に設定したプロパティは他のメソッド呼び出し時にアクセスすることが可能です。なお、プロパティと同様にメソッドもself.メソッド名()で呼び出すことができるので覚えておきましょう!

class Horse(object):
    #コンストラクタ
    def __init__(self, age):
        #selfは自分自身と言う意味。自分自身のageという変数に引数のageを代入する
        self.age = age
        print(self.age)
    
    def print_age(self):
        print('print_age:', self.age)  
    
    def run(self):
        print('run fast!!')

#インスタンス化
horse = Horse(3)
horse.print_age()

#インスタンス化された時にコンストラクタの処理が実行される
# 3
#コンストラクタで設定されたプロパティおよびメソッドにアクセス可能
# print_age: 3
# run fast!!

デストラクタ

デストラクタはコンストラクタとは逆にインスタンス化されたオブジェクトが廃棄される時に実行される処理のことです。コンストラクタとは逆にオブジェクトが廃棄される時に何か必ず実行したい処理がある場合に使います。

class Horse(object):
    def __init__(self, age):
        self.age = age
        print(self.age)
    
    def print_age(self):
        print('print_age:', self.age)  
        self.run()
    
    def run(self):
        print('run fast!!')
    #デストラクタ
    def __del__(self):
        print('sleep...')

#インスタンス化
horse = Horse(3)
horse.run()

# 3
#オブジェクトが廃棄される時にデストラクタの処理が実行された
# sleep...

クラス変数とインスタンス変数

クラス変数とは異なるインスタンス間で共有される変数のことで、クラス名の直下に宣言されたプロパティは全てクラス変数になります。
一方でインスタンス変数とはそれぞれのインスタンスでのみ使用できる変数のことで、メソッドの中に定義した変数は全てインスタンス変数になります。

クラス変数とインスタンス変数はそれぞれ下記の方法でアクセスすることができます。クラス変数には2通りのアクセス方法があります。

class Val(object):
    #クラス変数
    class_val = 'class_val'
    
    def instance_val(self, instance_val):
        #インスタンス変数
        self.instance_val = instance_val

val1 = Val()
#クラス変数へのアクセス1
print(Val.class_val)
#クラス変数へのアクセス2
print(val1.__class__.class_val)
#インスタンス変数へのアクセス
val1.instance_val('val1')
print(val1.instance_val)

# class_val
# class_val
# val1

また、冒頭に記載したようにクラス変数は異なる変数間でプロパティが共有されます。従って、いずれかのインスタンスでクラス変数を書き換えた場合は全てのインスタンスに影響が出るので注意しましょう。
逆にインスタンス変数はそのインスタンス内でのみの影響となるので、インスタンスごとに何か値を設定したとしても他のインスタンスに影響は及ぼしません。

#val2をインスタンス化
val2 = Val()
#val2でクラス変数を書き換え
val2.__class__.class_val = 'class_val2'
#val2でインスタンス変数を設定
val2.instance_val('val2')

#クラス変数の結果表示
print(val1.__class__.class_val)
#インスタンス変数の結果表示
print(val1.instance_val)
print(val2.instance_val)

#クラス変数はval2で書き換えた結果になっている
# class_val2
#インスタンス変数はそれぞれのインスタンスで独立しているため変更がない
# val1
# val2

クラスのメソッド色々

クラスでは3種類のメソッドを定義できます。

インスタンスメソッド

ここまでの記事でやってきたメソッドは全てインスタンスメソッドです。
従って、改めて説明の必要はしませんが、インスタンスメソッドの定義時の注意点としては引数に必ずselfキーワードを取ることです。

クラスメソッド

クラスメソッドとはクラスをインスタンス化せずに実行できるメソッドです。
具体的には、メソッド名の前に@classmethodキーワードをつけ、第一引数にclsキーワードを取ることで定義できます。
なお、クラスメソッドではcls.変数名とすることで、クラス変数にアクセスすることができますが、インスタンス化していないため、インスタンス変数にはアクセスできません。

class Horse(object):
    #クラス変数
    age = 3
    
    def __init__(self):
        self.name = 'Deep Impact'
    
    #クラスメソッド
    @classmethod
    def print_age(cls):
        print(cls.age)
        
    @classmethod
    def run(cls):
        print('run fast!!')

#インスタンス化せずにメソッド実行
Horse.print_age()
Horse.run()

# 3
# run fast!!

#インスタンス化していないため、インスタンス変数にアクセスしようとするとエラー
Horse.name

# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# Input In [43], in <cell line: 1>()
# ----> 1 Horse.name()

# AttributeError: type object 'Horse' has no attribute 'name'

いちいちインスタンス化するのも手間なので、クラス変数のみにアクセスできればよいメソッドがもしあればクラスメソッドで定義してもいいかもしれませんね。

スタティックメソッド

スタティックメソッドは、インスタンスメソッドとクラスメソッドのようにインスタンスとクラスが引数に渡されることはなく、引数なしで実行されるメソッドです。
具体的には、メソッド名の前に@staticmethodキーワードをつけることで定義できます。
なお、スタティックメソッドはインスタンス変数にもクラス変数にもアクセスができません。

class Horse(object):
    #クラス変数
    age = 3
    
    def __init__(self):
        self.name = 'Deep Impact'
    
    #スタティックメソッド
    @staticmethod
    def run():
        print('run fast!!')

#インスタンス化せずにメソッド実行
Horse.run()

# run fast!!

#インスタンス化していないため、インスタンス変数にアクセスしようとするとエラー
Horse.name()

# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# Input In [43], in <cell line: 1>()
# ----> 1 Horse.name()

# AttributeError: type object 'Horse' has no attribute 'name'

また、スタティックメソッドはインスタンス変数同様、クラス変数にもアクセスできません。

class Horse(object):
    #クラス変数
    age = 3
    
    def __init__(self):
        self.name = 'Deep Impact'

    #スタティックメソッド
    @staticmethod
    def print_age():
        print(cls.age)
        
    #スタティックメソッド
    @staticmethod
    def run():
        print('run fast!!')

#クラス変数にもアクセスできない
Horse.print_age()

# ---------------------------------------------------------------------------
# NameError                                 Traceback (most recent call last)
# Input In [8], in <cell line: 1>()
# ----> 1 Horse.print_age()

# Input In [6], in Horse.print_age()
#       9 @staticmethod
#      10 def print_age():
# ---> 11     print(cls.age)

# NameError: name 'cls' is not defined
# name

ない、正直オブジェクト指向でのプログラミングでは、クラス変数にもインスタンス変数にもアクセスしないような処理を必要とする頻度は少ないと思うので、スタティックメソッドをわざわざ使う機会はそれほどない気がします。

特殊メソッド

特殊メソッドとは、インスタンスをある条件下で実行すると呼び出されるメソッドのことです。文章で書くと分かりづらいですが、実際に下記に示す使い方の例を見て頂くとイメージが湧くと思います。なお、コンストラクタの時にも確認した__init__も特殊メソッドに分類されます。

class Word(object):
    #1
    def __init__(self, text):
        self.text = text
    #2
    def __str__(self):
        return self.text
    #3
    def __len__(self):
        return len(self.text)
    #4
    def __add__(self, word):
        return self.text.upper() + word.text.upper()
    #5
    def __eq__(self, word):
        return self.text.upper() == word.text.upper()
    #6
    def __hash__(self):
        return hash(self.text)
    #7
    def __bool__(self):
        return True if self.text == 'text1' else False

#1 __init__でインスタンス変数に値を設定
w1 = Word('text1')

#2 print、もしくはstrで__str__で呼び出される
print(w1)
str(w1)
# text1
# 'text1'

#3 len実行時に__len__が呼び出される
print(len(w1))
#5

#4 +演算子実行時に__add__が呼び出される
w2 = Word('text2')
print(w1 + w2)
#TEXT1TEXT2

#5 ==演算子実行時に__eq__が呼び出される
#※ __eq__を定義せずにprint(w1 == w2)を実行するとオブジェクトの比較になるため、同一のオブジェクトを比較しない限り、必ずFalseとなる
print(w1 == w2)
w2 = Word('text1')
print(w1 == w2)
# False
# True

#6 hash関数実行時に__hash__が呼び出される
hash(w1)
# -7240285852182422048

#7 if実行時に__bool__が呼び出される
w2 = Word('text2')
if w1:
    print('w1はok')
if w2:
    print('w2はok')
#w1はok

#5の__eq__のところを少し補足すると、__eq__を定義せずにオブジェクト同士を==演算子で比較した場合はオブジェクト同士の比較になるため、同一のオブジェクトを比較しない限り必ずFalseが返されます。

class Word(object):
    #1
    def __init__(self, text):
        self.text = text

w1 = Word('text1')
w2 = Word('text1')

print(w1 == w2)

print(w1)
print(w2)

#オブジェクト同士の比較になるため、Falseとなる
# False
# <__main__.Word object at 0x7fafd7cbb910>
# <__main__.Word object at 0x7fafd7cbbc40>

特殊メソッドは他にもありますが、他のものはPythonの公式ドキュメントに一覧と使い方が載っているのでそちらを参考にしてください。

参考資料

この記事はUdemyのPython+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~という講座と現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイルという2つの講座を参考にさせていただき、作成しました。

Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~

Udemyより抜粋

当講座は最低限のWeb開発の知識を知っていることが前提とはなりますが、とにかくPythonの説明とFlask開発の為の説明が充実しているのでおすすめです。
28時間に及ぶ長丁場の講座にはなりますが、絶対に聞く価値がある講座です。
これをマスターすればPythonでどんなアプリケーションでも作ることができると思います。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

とにかく説明が充実している

Webアプリ開発におけるフレームワークがなぜ有益なのか知ることができる

セキュリティ対策、Ajaxなどの技術も知れる

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

画像に alt 属性が指定されていません。ファイル名: a1ee3d048d6aa860662b6f58c4aa167f-1024x328.png
Udemyより抜粋

当講座はPythonの基礎から応用まで幅広く学べる講座なのでおすすめです。
この講座の講師はとにかくPythonについての知識が豊富ですし、話も適度な速さで聞き取りやすいです。さすがのシリコンバレーです。
また、最後の方に機械学習で使うライブラリについても解説があるので、データサイエンス・AIについても多少知ることができます。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

シリコンバレーで働いているということもあり、講師のPythonの知識が豊富

話も適度な速さで聞き取りやすい

コードの意味だけでなく、それをどう応用するかまで解説してくれる

なお、Udemyについては以下の記事でまとめていますのでご参考ください。

まとめ

ということで、今回はPythonのクラス定義について解説しました。
ここで書いてあることがオブジェクト指向のベースとなり、この後の抽象化、カプセル化、特殊メソッドといった考え方につながりますので、是非抑えていただければと思います。
では、今回はここまでとさせていただきます。