【初心者向け解説】Pythonのクラス内部の変数のスコープはミュータブルオブジェクトとイミュータブルオブジェクトで異なる。そしてその理由とは?

Pythonのクラス変数のスコープの解説。ミュータブルオブジェクトとイミュータブルオブジェクトとの本質的な違いとは?

今回の記事は、Pythonでクラスを作った場合のクラス変数のスコープの問題です。個人的に「??」と悩んでハマってしまったのでした。

厳密にいえばクラス変数のスコープというネタではないかもしれませんが、関係したネタだと思ってください。

今回は以下の3点がよくわからず悩みました。

  • クラス変数を変更すると、インスタンスオブジェクトにも影響を及ぼすか否か
  • インスタンスオブジェクトのクラス変数を変更すると、元のクラス変数にも影響を及ぼすか否か
  • 同一クラスから作った複数のインスタンスオブジェクトのうち、一部のインスタンスオブジェクトのクラス変数を変更すると他のインスタンスオブジェクトにもその影響を及ぼすか否か

そこで調べてみると以下のことがわかったので、それを備忘録としてまとめておきます。その理解のポイントは次の事項でした。

理解のポイント:Pythonではクラス変数がミュータブルかイミュータブルかで仕組みが異なる

はい、この見出しの通りです。

すなわち、クラス変数のオブジェクトが、ミュータブルオブジェクトかイミュータブルオブジェクトかで動作の仕方が変わります。

簡単にですがざっとPythonでのミュータブルとイミュータブルオブジェクトの違いを確認しておきましょう。

  • 生成後に変更不可能なオブジェクトをイミュータブルオブジェクト(数値型、タプル型、文字列型など)
  • 変更可能なオブジェクトをミュータブルオブジェクト(リスト型、辞書型など)

ただし厳密にいえば生成後に変更が可能か不可能かでは区分はされません。ですが、今はあくまで簡単にこんな感じで区分しておきます。

では具体例を見ていきましょう。

具体例

① クラス変数がイミュータブルな場合(例として数値型オブジェクトを使用)

###クラス変数がイミュータブルな場合(今回は数値型)
class MyClass:
    number = 10

    def __init__(self):
        self.number +=1

MyClass.number #クラス変数numberとして10が出力
c=MyClass()
d=MyClass()
c.number #10+1で11が出力 
d.number #10+1で11が出力 

#パターン1:クラス変数を変更
MyClass.number=100 #クラス変数に100を代入
c.number #11が出力 インスタンスには影響しない
d.number #11が出力 インスタンスには影響しない

#パターン2:片方のインスタンスのクラス変数を変更
c.number=200 #インスタンスのクラス変数に200を代入
d.number #11が出力。別のインスタンスの影響を受けていない

②クラス変数がミュータブルな場合(例としてリスト型オブジェクトを使用)

######クラス変数がリストの場合
class MyClass02:
    new_list=[]

    def show(self):
        return self.new_list

MyClass02.new_list
e=MyClass02()
f=MyClass02()

#パターン1:クラス変数を変更
MyClass02.new_list.append(1) #クラス変数のリストに新要素として数値1を追加
e.new_list #[1]が出力 影響を受けている
f.new_list  #[1]が出力 影響を受けている

#パターン2:片方のインスタンスのクラス変数を変更
e.new_list.append(2)  #新要素として数値2を追加
f.new_list  #[1,2]が出力 別のインスタンスの影響を受けている

ものすごく単純な検証でしかなくて申し訳ないですが、差異がわかりやすいと思います。どうでしょう?

③結果整理

これら2つの場合の結果を整理してみましょう。

クラス変数がイミュータブルな場合ミュータブルな場合
クラス変数の変更インスタンスオブジェクトには影響しない影響する
別のインスタンスオブジェクトのクラス変数の変更他のインスタンスオブジェクトには影響しない影響する

クラス変数がイミュータブルな場合は影響なし、ミュータブルな場合は影響ありと明確に別れました。それではなぜこのような結果になったのでしょうか?

この点は次の項目を見てください。

Pythonにおけるイミュータブルオブジェクトとミュータブルオブジェクトの決定的な違い

以上のように、クラス内変数がミュータブルオブジェクトなのかイミュータブルオブジェクトなのかで仕組みが異なります。

そこでPythonの内部で一体何がおこっているのか、オブジェクトIDという観点から少し解説したいと思います。

イミュータブルオブジェクトの場合

もう一度、クラス変数でイミュータブルオブジェクトを生成した上①の場合のコードを見ましょう。上①と全く同じコードを再び載せます。

###クラス変数がイミュータブルな場合(今回は数値型)
class MyClass:
    number = 10

    def __init__(self):
        self.number +=1

MyClass.number #クラス変数numberとして10が出力
c=MyClass()
d=MyClass()
c.number #10+1で11が出力 
d.number #10+1で11が出力 

#パターン1:クラス変数を変更
MyClass.number=100 #クラス変数に100を代入
c.number #11が出力 インスタンスには影響しない
d.number #11が出力 インスタンスには影響しない

#パターン2:片方のインスタンスのクラス変数を変更
c.number=200 #インスタンスのクラス変数に200を代入
d.number #11が出力。別のインスタンスの影響を受けていない

ここでのクラス変数のnumberには数値10が代入されていますので数値型オブジェクトになり、数値型はイミュータブルオブジェクトです。

このnumberについてオブジェクトID(識別値)を調べてみましょう。

id(MyClass.number) #4359109248

4359109248という番号が出力されました。

では次に、インスタンスオブジェクトcについて、そのクラス変数のc.numberのオブジェクトIDを調べてみましょう。

id(c.number) #4359109280

さきほどのIDとは異なるが出力されました。

続けて、インスタンスオブジェクトdのクラス変数、d.numberについても調べます。

id(d.number) #4359109280

c.numberと同じIDになりました。

これにより、c.numberとd.numberは同じオブジェクトだと判明しました。一方で、クラス変数MyClass.numberはそれら2つと異なるオブジェクトであるということも判明しました。

そうです、クラス変数がイミュータブルオブジェクトの場合(今回は数値型)は、インスタンスオブジェクト内部のクラス変数と、もともとのそのクラス変数とは異なるオブジェクトなのです。

したがって、もともとのクラス変数の値を次のように変更したとしても、

MyClass.number=100 #クラス変数に100を代入
id(MyClass.number) #4359112128

Python上ではインスタンスオブジェクト内のクラス変数ともともとのクラス変数とは異なるオブジェクトのため、このように後者を変更しても、前者には影響を及ぼなさいのです。

また変更されたクラス変数のオブジェクトIDに着目してください。その値「4359112128」はこれまで登場したIDとはまた別のIDになっています。

すなわち、イミュータブルオブジェクトというのは、その数値・情報を変更できないオブジェクトというよりは、数値・情報を変更するたびに全く別の異なるオブジェクトが新しく作成されて、もともとのオブジェクトには影響を与えないという性質のオブジェクトであると言えます。

箱の中身を変えられるかどうかという次元ではなく、新しい箱そのものを作り出すものと考えるほうが良いでしょう。

以上のようなイミュータブルオブジェクトの性質からすれば、クラス変数の変更が、インスタンス内のクラス変数には影響を与えないということが理解できると思います。

ミュータブルオブジェクトの場合

では逆に、ミュータブルオブジェクトの場合はどうなのか?全く逆の結論となります。同じく上②のコードを再掲します。

######クラス変数がリストの場合
class MyClass02:
    new_list=[]

    def show(self):
        return self.new_list

MyClass02.new_list
e=MyClass02()
f=MyClass02()

#パターン1:クラス変数を変更
MyClass02.new_list.append(1) #クラス変数のリストに新要素として数値1を追加
e.new_list #[1]が出力 影響を受けている
f.new_list  #[1]が出力 影響を受けている

#パターン2:片方のインスタンスのクラス変数を変更
e.new_list.append(2)  #新要素として数値2を追加
f.new_list  #[1,2]が出力 別のインスタンスの影響を受けている

クラス変数としてミュータブルオブジェクトの1種のリスト型オブジェクトとして、new_list(空のリスト)を作成しました。

まずこれのオブジェクトIDを調べてみましょう。

id(MyClass02.new_list) #140483736401664

となります。

では、インスタンスオブジェクトeとfのオブジェクトIDはどうなるでしょう。

id(e.new_list) #140483736401664
id(f.new_list) #140483736401664

なんと今回は、クラス変数とインスタンスオブジェクト内のクラス変数が同一のものになりました。

次に、クラス変数を変更して、そのオブジェクトIDを調べてみましょう。

MyClass02.new_list.append(1) #空のリストから、[1]というリストに変更
id(MyClass02.new_list) #140483736401664

クラス変数についても変更前と変更後が同じオブジェクトIDです。これまでの140483736401664と同じ番号のままです。

つまり、ミュータブルオブジェクトはその中身を変更したとしても、あくまでそのオブジェクトの中身だけを変更していることになります。

イミュータブルオブジェクトの場合は代入するたびに新しいオブジェクトが生成されますが、ミュータブルオブジェクトは代入するたびに本当に中身だけを変えるだけなのですね

さらに、クラス変数を変更すると、それをもとに作成された各インスタンスオブジェクトeとfのクラス変数も変更されます。

e.new_list  #[1]
f.new_list #[1]

このように、MyClass02.new_listと同じリスト「 [1] 」が出力されることからそれがわかります。

クラス変数がミュータブルオブジェクトの場合はそれを変更すると、そのクラスから生成された各インスタンスオブジェクトにも影響を与えるとわかります。

もちろん、

id(e.new_list) #140483736401664
id(f.new_list) #140483736401664

このように両方のインスタンスオブジェクトのクラス変数のオブジェクトIDは同じになりますし、MyClass02.new_listのオブジェクトIDとも同じです。

最後に、1つのインスタンスオブジェクトのクラス変数を変更した場合に、他のインスタンスオブジェクトにも影響が及ぶのかを調べてみましょう。

e.new_list.append(2) #eのnew_listを、[1]から[1, 2]へ変更
f.new_list # これも自動的に[1]から[1, 2]になる
id(e.new_list) #140483736401664
id(f.new_list) #140483736401664

このコードからわかるように、片方のインスタンスオブジェクトのクラス変数を変更すると、自動的に他方のインスタンスオブジェクトにもそれが反映されました。

しかし、上述ミュータブルオブジェクトの性質からわかるように両方のインスタンスオブジェクトのクラス変数のオブジェククトIDは全く変わらず同じ「140483736401664」のままです。

クラス変数のスコープについてのまとめ

ここでも再度、上の表を再掲します。

クラス変数がイミュータブルな場合ミュータブルな場合
クラス変数の変更インスタンスオブジェクトには影響しない影響する
別のインスタンスオブジェクトのクラス変数の変更他のインスタンスオブジェクトには影響しない影響する

厳密にいうと、今回の話はPtyhonのクラス変数のスコープそれ自体の話ではなく、本質的にはPythonにおけるミュータブルオブジェクトとイミュータブルオブジェクトの違いという話になるでしょうか。

とにかくPythonのクラス変数を設定するとき、使うときは以上のようにそれがミュータブルオブジェクトなのかイミュータブルオブジェクトなのかに注意して考え方を分ける必要があります。

やっぱりプログラミングは独学だとわかりにくいですよね、そこでスクールや動画による解説を利用しましょう!

Tech Academy

厚切りジェイソンのCMでおなじみのプログラミングスクール。自宅でオンライン受講OK。現役のプロが一人一人に専属メンターになってくれ、質問すればすぐに回答。転職保証あり

Udemy

世界最大級のオンライン学習動画サービス。AI・データサイエンスなど最先端のプログラミング講座からビジネススキル講座まで10万以上の講座から選び放題。講師に掲示板から直接に質問もできる。