Lesson 7

クラス

Lesson 7 Chapter 1
はじめに

このレッスンではPythonのクラスの概念やその使用用途について学んでいきます。これまでのプログラムと比較するとオブジェクト指向といった新しい考え方が必要となっており、最初は少し難しく感じるかもしれません。

クラスやメソッドの概念をマスターすると、冗長だったコードがより見やすくなり、複雑な処理をするプログラムでも内容を把握しやすくなります。 何度も繰り返し見直したり、実際にコードを書きながら進めていくことでより速く理解が深まりますので、頑張っていきましょう。

Lesson 7 Chapter 2
スコープと名前空間

このチャプターでは、変数の有効範囲を示すスコープと、変数データ格納場所の関係について学んでいきます。 これらをしっかりと把握することで、予期せぬ変数の参照を防いだり、予め想定された変数の影響範囲によって適切な変数定義をすることが出来るようになります。

スコープの概要

スコープは、変数や関数が有効な範囲を指します。 Pythonのスコープは、変数や関数が定義された場所によって決まります。 種類としては、グローバルスコープ、ローカルスコープ、関数スコープ、クラススコープの4つに分類されます。

スコープ名 説明
グローバルスコープ モジュール全体に適用される
ローカルスコープ 関数やメソッド内に適用される
関数スコープ 関数内に適用される
クラススコープ クラス内に適用される

名前空間の概要

名前空間は、変数や関数が格納される場所を指します。 Pythonの名前空間はスコープによって決まります。 スコープごとに異なる名前空間が存在し、各名前空間の中にグローバル変数やローカル変数が格納されます。つまり、複数のモジュールやプログラムで同じ名前の変数や関数を使用しても、それぞれが独立した空間に格納されているということになります。

実際にスコープと名前空間を確認してみよう

下記のコードでは、変数xはグローバル変数として定義されています。また、関数test内では変数xをローカル変数として再定義しています。 そのため、関数test内ではローカル変数xを参照しprintすることになりますが、関数の外ではグローバル変数xを参照しprintしています。

sample.py
# グローバル変数
x = "global"

def test():
    # ローカル変数
    x = "local"
    print(x)

test()
print(x)

出力結果

local
global

Pythonでのグローバル変数やローカル変数としての定義は、基本的には上下位置や、関数や制御構文内のインデントによって決まります。 このようにスコープによって名前空間も異なり、参照する変数も異なることが分かります。

グローバルキーワード

先程のプログラムで、関数内でグローバル変数を参照するためにはglobalキーワードを使用する必要があります。

sample.py
x = "global"

def test():
    global x
    x = "local"
    print(x)

test()
print(x)

出力結果

local
local

この例ではglobalキーワードを使用して、関数内でグローバル変数xを参照しています。その結果、関数内でグローバル変数xの値を変更し、関数外でもその変更が反映されることが分かります。

ここまでの説明を簡潔にまとめると、スコープと名前空間は以下のようになります。

  • スコープ: 変数や関数が有効な範囲を示す。
  • 名前空間: 変数や関数が格納される場所を示す。

スコープには、グローバルスコープ、ローカルスコープ、関数スコープ、クラススコープの4つがあり、それぞれに対応する名前空間が存在します。スコープによって、変数や関数が参照されることが決まります。

注意すべきことはグローバル変数とローカル変数の違いです。ローカル変数は、関数内で定義された変数で、関数外では参照できません。一方、グローバル変数は関数内外で参照することができます。もし関数内でグローバル変数を参照したい場合には、globalキーワードを使用します。

Lesson 7 Chapter 3
クラスの作成

クラスは、オブジェクト指向プログラミングにおいて、オブジェクトを定義するためのテンプレートのことを指します。クラスを使用することで、同じデータやメソッドを持った複数のオブジェクトを作成することができます。
クラスを定義するには、classキーワードを使用します。クラス名は自由に決めることができますが、一般的には各単語の頭を大文字で記述するパスカルケースという命名規則が使われます。

例えば、人間を表すクラスを作成する場合は、以下のようになります。

sample.py
class Person:
    pass

このように、クラスを定義するときには、クラス名の後にコロンをつけ、インデントを使って中身を記述します。上記の例では、まだ中身が空のためpassを使用しています。

クラスの属性

クラスには、データ属性(プロパティ)とメソッドを定義することができます。データ属性は、クラスのインスタンスが持つデータを表します。メソッドは、クラスのインスタンスが実行できる処理を表します。

例えば、Personクラスには、名前や年齢といったデータ属性を定義し、自己紹介をするintroduce()というメソッドを定義することができます。

sample.py
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def introduce(self):
        print("私の名前は" + self.name + "です。")
        print(str(self.age) + "歳です。")

このように、クラスを定義することで、複数のインスタンスを作成し、それぞれに異なるデータ属性を持たせることができます。そして、これらのインスタンスは、クラスに定義されているメソッドを呼び出して、処理を実行することができます。
また、クラスには継承という機能があり、他のクラスを継承して新しいクラスを作成することができます。継承をすることで、既存のクラスの機能を継承し、新しい機能を追加することができます。 その他にも__init__といった特殊なメソッドなど、わからない箇所が多くあると思いますが、これらに関しては後のチャプターで詳しく説明していきます。

Lesson 7 Chapter 4
インスタンスの生成

インスタンスはクラスから生成される具体的なオブジェクトのことで、クラスはオブジェクトの持つデータ(インスタンス変数)や処理(メソッド)を定義するための設計図です。

例えばCarというクラスがあった場合、そのクラスからmy_carというインスタンスを生成することができます。my_carCarクラスが定義しているインスタンス変数やメソッドを持ち、実際に走ることができる車というオブジェクトになるイメージです。

sample.py
class Car:
    def __init__(self):
        self.color = "white"

    def start_engine(self, message):
        print(message + "エンジン始動!")

上記クラスのインスタンスを生成するには、以下のようにクラス名の後に()をつけて呼び出します。

sample.py
my_car = Car()

これにより、Carクラスからmy_carというインスタンスを生成し、使用することができます。 同じクラスから複数のインスタンスを生成して、各インスタンスの変数やメソッドを呼び出したり、値を設定することもできます。

sample.py
my_car = Car()
your_car = Car()

my_car.color = "Red"
my_car.start_engine("私の車の")

your_car.color = "Blue"
your_car.start_engine("あなたの車の")

print(my_car.color)
print(your_car.color)

出力結果

私の車のエンジン始動!
あなたの車のエンジン始動!
Red
Blue

このようにインスタンスはクラスという設計図から生成されるため、同じクラスから生成された複数のインスタンスは属性や処理を持ちますが、それぞれ独自のデータを持つことができます。 複数のオブジェクトに共通する部分をクラスとして定義することは、オブジェクト指向プログラミングを行う上で重要な役割を担います。

Lesson 7 Chapter 5
メソッドオブジェクト

Pythonのクラスは、特定の型のオブジェクトを作成するためのテンプレートです。クラスにはインスタンス変数とメソッドが含まれており、インスタンス変数は、クラスから生成されるオブジェクト固有の情報を保持します。 一方、メソッドはクラスから生成されるオブジェクトに対して実行できる処理を定義します。
メソッドはクラス内部で定義される関数で、インスタンスに対して特定の処理を実行するために使用されます。クラス外部からは見えませんが、クラス内部では、インスタンス変数にアクセスしたり、他のメソッドを呼び出すために使用することができます。

クラス内でメソッドオブジェクトを定義するには、defを使用します。

sample.py
class MyClass:
    def my_method(self):
        print("こんにちは")

メソッドはインスタンスを生成した後、インスタンスからメソッドを呼び出すことができます。インスタンス名.メソッド名のように記述をします。

sample.py
my_object = MyClass()
my_object.my_method()

出力結果

こんにちは

また、インスタンス変数へのアクセスや他のメソッドを呼び出すためにselfを使用します。

sample.py
class MyClass:
    def __init__(self):
        self.count = 0
    def my_method(self):
        self.count += 1
        print(self.count)

インスタンスを生成した後、インスタンスからメソッドを呼び出すことで、インスタンス変数にアクセスすることができます。

sample.py
my_object = MyClass()
my_object.my_method()
my_object.my_method()
my_object.my_method()

出力結果

1
2
3

このmy_methodメソッドでは、呼び出される度にインスタンス自身のインスタンス変数であるcount変数に値を+1して、その内容を出力するプログラムとなっています。

第一引数のselfについて

ここまで見てきたメソッドの引数に必ず記述しているselfについて説明してきます。 簡潔に言うとselfは「インスタンス自身を表すオブジェクト」を指します。
クラスメソッドの中で、インスタンス変数や別のメソッドを呼び出す場合に、selfを使用することで、インスタンス自身にアクセスすることができます。 インスタンスは、同じクラスから生成された複数のインスタンスが存在出来てしまうといった性質上、countと定義されたインスタンス変数が「一体誰のcountなのか?」といった事になります。
こういった事が起きないように、インスタンス変数が参照される際にインスタンスの情報をselfとして一緒に渡してあげることで、「〇〇さんのcountを参照したい」といった判断ができるようになります。 ここまでの説明で完璧に理解できなくても問題はありません。最初のうちは「メソッドを定義するときには第一引数にselfを書かなくてはならない」といった"おまじない"として覚えておきましょう。

__init__について

Pythonでは特殊な意味を持つ名前が付けられたメソッドが存在します。これらのメソッドは、特定の処理を行うために、Pythonが自動的に呼び出すことができます。
例えば、__init__という名前のメソッドは、インスタンスの生成時に自動的に呼び出されます。これを「コンストラクタ」と呼び、インスタンスの初期化処理を記述するために使用します。

sample.py
class MyClass:
    def __init__(self):
        self.count = 0

上記のようにinitメソッドを定義した場合、インスタンスを生成する時に自動的に呼び出され、インスタンス変数countに0が代入されるようになります。 このようにPythonには他にも特殊な意味を持つ名前を持つメソッドが用意されているため、クラスを定義する際にはそれらを使うことでより簡単かつ効率的なプログラミングを行うことができます。

Lesson 7 Chapter 6
インスタンス変数

インスタンス変数は、インスタンスごとに保持される変数でクラスの中で定義されているメソッドからアクセスすることができます。インスタンス変数を定義するには、通常の変数と同じように定義します。 また、インスタンス変数はオブジェクトごとに独立して保持されるため、異なるオブジェクトでは、インスタンス変数の値が異なります。

以下のプログラムがインスタンス変数を使った例です。

sample.py
class MyClass:
    def __init__(self):
        self.count = 0
    def count_up(self):
        self.count += 1

# 2つの異なるインスタンスを生成
my_object = MyClass("わたし")
your_object = MyClass("あなた")

# インスタンス変数をカウントアップするメソッドを呼び出し、値を更新
my_object.count_up()
your_object.count_up()
your_object.count_up()

# インスタンス変数を参照して出力
print(my_object.count)
print(your_object.count)

出力結果

1
2

この例では、count_up()メソッドが呼び出される度にインスタンス自身のインスタンス変数であるcount変数に値を+1して、その内容を出力するプログラムとなっています。

__init__を使用したインスタンス変数の初期化例

これまでにあった例の中では主に、クラス定義の箇所でインスタンス変数の初期化も行っている方法でした。 ここでは少し応用的なインスタンス変数初期化の方法について説明します。具体的には、コンストラクタの引数を利用します。

sample.py
class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print("私は" + self.name + "です。")
        print(str(self.age) + "歳です。")

ここではコンストラクタにnameageという2つの引数をセットしています。これをインスタンス化する際は以下のようになります。

sample.py
# インスタンスを生成
my_object = MyClass("パイソン", 33)

my_object.introduce()

出力結果

私はパイソンです。
33歳です。

インスタンス化のタイミングで、"パイソン"という文字列がname変数に格納され、33という数値がage変数に格納されるといった流れになっています。 注意点として、コンストラクタに引数をセットした場合、インスタンス化の際にクラスに必要な引数を渡さないとエラーとなりますので忘れずに設定しましょう。

Lesson 7 Chapter 7
クラスの継承と多重継承

Pythonではクラスを継承することができます。継承することで、既存のクラス(親クラス)の機能を引き継ぐことができ、自身独自の機能を追加することもできます。

以下は、継承の例です。

sample.py
class Person:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print("こんにちは。私は" + self.name + "です。")

class Student(Person):
    def __init__(self, name, school):
        # 継承元のコンストラクタを呼び出す
        super().__init__(name)
        self.school = school

    def say_hello(self):
        # 継承元のsay_helloメソッドを呼び出す
        super().say_hello()
        print("私は" + self.school + "の生徒です。")

student = Student("太郎", "パイソン大学")
student.say_hello()

出力結果

こんにちは。私は太郎です。
私はパイソン大学の生徒です。

上記のコードでは、StudentクラスがPersonクラスを継承しています。Studentクラスは、Personクラスのsay_helloメソッドを引き継ぐだけでなく、自身独自の機能としてschool変数を持ちます。

次に、多重継承について説明します。多重継承とは、複数のクラスから継承を受けることを指します。以下は、多重継承の例です。

sample.py
class Person:
    def __init__(self, name):
        self.name = name

    def say_hello(self):
        print("こんにちは。私は" + self.name + "です。")

class Animal:
    def __init__(self, species):
        self.species = species

    def say_species(self):
        print("種族は" + self.species + "です。")

class Human(Person, Animal):
    def __init__(self, name, species):
        super().__init__(name)
        self.species = species

human = Human("太郎", "人間")
human.say_hello()
human.say_species()

出力結果

こんにちは。私は太郎です。
種族は人間です。

この辺りから少し複雑になってきますが、ゆっくり順を追って見ていきましょう。まず、クラスの定義は無視して、実行されるプログラムについて見てみます。

sample.py
human = Human("太郎", "人間")
human.say_hello()
human.say_species()

ここでは最初に、Humanクラスをインスタンス化しています。そしてコンストラクタに対して"太郎"と"人間"の文字列を渡していることが確認できます。 では、この引数がどこで処理されるのかを解明するため、次にHumanクラスに注目してみましょう。

sample.py
class Human(Person, Animal):
    def __init__(self, name, species):
        super().__init__(name)
        self.species = species

Humanクラスにもコンストラクタはありますが、最初の行で記述されているsuper().__init__(name)で親クラスのコンストラクタを呼び出しています。 しかし、親クラスはPersonAnimalの2つが存在するため、一見どちらのコンストラクタを呼び出すのか疑問に思います。 実はこの問題は単純で、左側に記述しているコンストラクタが優先されます。つまり、Personクラスのコンストラクタを実行しているということになります。

sample.py
class Person:
    def __init__(self, name):
        self.name = name

ここでまた1つ問題が生じます。それはAnimalのコンストラクタが呼び出されないことによってspeciesが初期化されないといったことです。 しかし、こちらも既に解決策が施されています。Humanのコンストラクタに戻って確認すると、しっかりとspeciesを初期化している事が確認できます。

sample.py
class Human(Person, Animal):
    def __init__(self, name, species):
        super().__init__(name)
        self.species = species # speciesの初期化

こうすることで、human.say_hello()human.say_species()を正常に呼び出し、処理する事ができます。 最後に1つ補足ですが、多重継承による親クラス間のメソッド名重複にも同じことが言えます。

sample.py
class Japanese:
    def say_hello(self):
        print("こんにちは")

class English:
    def say_hello(self):
        print("Hello")

class Human(Japanese, English):
    pass

human = Human()
human.say_hello()

出力結果

こんにちは

このように、継承の箇所で左側に記述した親クラスのメソッドが優先される事が確認できます。 念の為、継承の順番を逆にすることで結果も変わることを確認してみましょう。

sample.py
class Japanese:
    def say_hello(self):
        print("こんにちは")

class English:
    def say_hello(self):
        print("Hello")

# Englishを優先して継承
class Human(English, Japanese):
    pass

human = Human()
human.say_hello()

出力結果

Hello

このように継承を使うことで、既存のクラスの機能を引き継ぎながら新しいクラスを作成することができ、コードの繰り返しを避けることを意味するDRY(Don't Repeat Yourself)原則に従って、効率的なプログラミングをすることができるようになります。

Lesson 7 Chapter 8
プライベート変数

Pythonにおいてプライベート変数とは、クラスの外部からアクセスすることを想定していない変数のことを指します。プライベート変数は、クラス内部でのみ使用されることを想定しています。

以下は、プライベート変数の例です。

sample.py
class Person:
    def __init__(self, name):
        self.__name = name # プライベート変数

    def say_hello(self):
        print("私は" + self.__name + "です")


person = Person("パイソン")
print(person._Person__name)

出力結果

パイソン

プライベート変数の定義の仕方はシンプルで、インスタンス変数名の頭に__(アンダースコアを2つ)付けることでプライベート変数として定義できます。

外部からプライベート変数を参照する

一方で、外部からのプライベート変数の参照の仕方には大きく違いがあります。

sample.py
print(person._Person__name)

この部分に注目すると、インスタンス名._クラス名プライベート変数名という特殊な記述をしています。そしてもう1つ疑問な点として、クラスの外部からプライベート変数であるはずの__name変数を参照できてしまっているという点です。
実は、Pythonのプライベート変数は完全なプライベートではなく、あくまで外部から参照する必要のない変数というルールレベルの定義になります。プライベート変数は単純に_クラス名プライベート変数名といった形の変数名に置き換わるだけの物という風に解釈しても問題はありません。

では、ここまでの説明でプライベート変数の定義の仕方と外部からの参照の仕方について学びました。最後に、メソッド内でのプライベート変数の参照方法について説明していきます。

内部(メソッド)からプライベート変数を参照する

以下のコードは、同クラス内のプライベート変数を参照しているsay_helloメソッドを呼び出すプログラムです。

sample.py
class Person:
    def __init__(self, name):
        self.__name = name # プライベート変数

    def say_hello(self):
        print("私は" + self.__name + "です")


person = Person("パイソン")
person.say_hello()

出力結果

私はパイソンです

このように、self.__nameとするだけで呼び出すことが出来ます。また、以下の記述方法でも同じ結果となります。

sample.py
class Person:
    def __init__(self, name):
        self.__name = name # プライベート変数

    def say_hello(self):
        # self._Person__nameに変更
        print("私は" + self._Person__name + "です")


person = Person("パイソン")
person.say_hello()

出力結果

私はパイソンです

この例では、外部からプライベート変数を呼び出す際と似た形になっており、この記述方法でも同様に呼び出すことが出来ます。

以上がクラスに関する基礎的な記述方法になります。これらの内容を身につけることでプログラムがより簡潔かつ保守性が高くなりますので、オブジェクト指向をマスターしてみましょう。