Python クラス変数

Python学習【365日チャレンジ!】86日目のマスターU(@Udemy11)です。

ホームベーカリーで焼いたパンを切るための包丁を買うはずが、なぜかステーキ用のナイフとフォークのカトラリーを購入してしまいました。

global グローバル ナイフ フォーク カトラリー

ちょっと高級なglobalのナイフとフォークなので、食器棚の引き出しにガチャガチャと入れているフォークやスプーンと同じように入れるのが嫌だったので、きっちりと収まるオリジナルの収納箱を作ってしまいました。

製作に半日かかったので、若干疲れ気味ですが、出来具合に満足しているので、眺めながらニヤけてしまっています。

という変な趣味の話はここまでにして、今日もPython学習をはじめましょう!

昨日の復習

昨日は、使わなくても問題がないのなら、使わないほうがいい多重継承を学習しました。

最初のプログラム設計では予定していなかったクラスを既存のクラスを継承して作成する場合などに使われます。

チームで開発したり、コードが巨大になってきたときにどうしても必要になったときに使えますが、基本的に多重継承がなくてもいいような設計にするのがベストということでした。

class Human:
    def talk(self):
        print('Hello')

class Car:
    def run(self):
        print('run')

class Night2000(Human, Car):
    def auto(self):
        print('ai drive system')

night2000 = Night2000()
night2000.talk()
night2000.run()
night2000.auto()

出力結果

Hello
run
ai drive system

子供の頃に、【ナイトライダー】というアメリカの刑事モノドラマがあったのですが、そこで出てくる人工知能を持ったスーパーカーが【ナイト2000】といって、めっちゃかっこよかったのを覚えています。

ナイト2000

今回の例では、HumanCarを継承したNight2000を作成して、両方のメソッドを継承し、Night2000だけが持つメソッドを出力してみました。

多重継承についての詳しい内容は、昨日の記事をごらんください。

それでは本日の学習、クラス変数について学習しましょう!

クラス変数

クラス変数は、クラスの中で定義される変数のことで、これまでのレクチャーでは、__init__で定義される初期設定や関数の引数として記述していたものがほとんどでした。

しかし、クラス変数は第一インデントで独立して指定することも可能で、selfを使って呼び出すことができます。

class Animal:

    kind = 'bird'

    def __init__(self, name):
        self.name = name

    def what_is_it(self):
        print(self.name, self.kind)

a = Animal('A')
a.what_is_it()
b = Animal('B')
b.what_is_it()

出力結果

A bird
B bird

Animalクラスを作成し、最初にkindというクラス変数を定義しています。

定義されたkindは、Animalクラスから生成されたオブジェクト(インスタンス)が違っても、what_is_itメソッドでself.kindで呼び出して、出力することができます。

もちろん、今までのように、初期化際に次のように定義しても同じ出力結果が得られます。

    def __init__(self, name):
        self.name = name
        self.kind = 'bird'

リストは共有されてしまう

クラス変数を独立して記述した際は、同じクラスから生成された異なるオブジェクトでも共有して使うことが可能ですが、注意点があります。

それが、リストをクラス変数として定義するときです。

class Something:

    words = []

    def add_word(self, word):
        self.words.append(word)

r = Something()
r.add_word('apple')
r.add_word('banana')
print(r.words)

s = Something()
s.add_word('peach')
print(s.words)

出力結果

['apple', 'banana']
['apple', 'banana', 'peach']

Somethingクラスを作成し、クラス変数にwordsという空のリストを用意して、引数を加えていくadd_wordメソッドを用意したとしましょう。

次に、Somethingクラスからrオブジェクトを作成して、add_wordメソッドを使って、applebananaを付け加えると、当然リストには2つの単語が入力されています。

その後、Somethingクラスからsオブジェクトを生成して、add_wordメソッドでpeachを加えると、rオブジェクトで加えたapplebananaのあとにpeachが追加されたリストになってしまいます。

つまり、クラス変数を独立して記述する場合は、同じクラスから生成されたオブジェクト(インスタンス)が、そのクラス変数を共有してしまうので、リストの中身が変わるような場合もきっちりと変更されて共有されるということです。

初期化で記述

クラス変数にリストを指定することはあまりしないほうがいいわけですが、同じクラスから生成されたオブジェクト(インスタンス)でリストを分けるには、初期化の際に記述する必要があります。

class Something:

    def __init__(self):
        self.words = []

    def add_word(self, word):
        self.words.append(word)

r = Something()
r.add_word('apple')
r.add_word('banana')
print(r.words)

s = Something()
s.add_word('peach')
print(s.words)

出力結果

['apple', 'banana']
['peach']

このコードであれば、オブジェクトが生成するたびに、__init__によって、wordsが空のリストで生成されるため、異なるオブジェクトで同じリストを共有することにはなりません。

いつも変わったことをしてしまう私なので、次のようなコードでも問題なくできるんじゃないの?なんて思って、試してみました。

    def add_word(self, word):
        self.words = []
        self.words.append(word)

元の3・4行目を削除して、7行目にself.words = []を追加したわけですが、結果はどうだったと思いますか?

賢明なあなたの予想どおり、まったくもって私の理解不足でした。

出力結果は次のような感じです。

['banana']
['peach']

ちょっと考えればわかることなのですが、add_wordメソッドが呼び出されるたびにクラス変数wordsが空のリストになるので、当然のことながら、最初に代入したappleはなくなって2回めにadd_wordで代入したbananaだけがリストに残る結果になりました。

想像力を働かせて

今回の私の失敗も少し想像力を働かせれば、すぐに間違いだと気づくわけですが、やってみることも大切です。

こんな出力になるだろうと考えて実行したときに、予想どおりの出力結果が得られれば、考え方が合っていたということですし、間違っていれば、何がおかしいのか考えることができます。

一番ダメなのは、頭の中だけで「これはこうだろう」とわかった気になることです。

やってみて、意外な結果になったり、正しかったりすることで理解度が深まることは間違いないので、思ったら即実行するようにしましょう!

それでは明日もGood Python!