インスタンスメソッドからクラス属性へアクセスする時の注意点を解説します。 インスタンスが作られるたびに、クラス属性 counter を1つ増やしたいとします。 以下のようなコードを書いてみました。

class Foo:

    counter = 1000

    def __init__(self):
        print("Old counter:", self.counter)
        self.counter += 1
        print("New counter:", self.counter)


f = Foo()
print("Updated counter:", f.counter)

f = Foo()
print("Updated counter:", f.counter)

これを実行してみると以下のような出力が出ます。

Old counter: 1000
New counter: 1001
Updated counter: 1001
Old counter: 1000
New counter: 1001
Updated counter: 1001

期待していた結果と違います。 確かに、1000 が 1001 に更新されていますが、更新が蓄積されず新しいインスタンスが作られても、相変わらず Old counter の値は1000のままです。なぜこのようになるのでしょうか?

Pythonの言語仕様には次のように書かれています。

Class instances
(...)
A class instance has a namespace implemented as a dictionary which is 
the first place in which attribute references are searched.When an 
attribute is not found there, and the instances class has an attribute
by that name,the search continues with the class attributes.
(...)

つまり、インスタンスの属性を探して見つからなかった場合は、クラス属性が探されるというわけです。

self.counter += 1という文は self.counter = self.counter + 1 と等価です。右辺ではクラス属性が参照され ていますが、左辺では新しいインスタンス属性が定義されていることになります。 一見同じようなものに見えますが、実は違うものなのです。

インスタンスメソッドからクラス属性へアクセス(代入)するには __class__ を経由することで可能になります。

        self.__class__.counter += 1

このように変更して実行すると

Old counter: 1000
New counter: 1001
Updated counter: 1001
Old counter: 1001
New counter: 1002
Updated counter: 1002

となり、期待していた結果が得られました。

野中 哲

エンジニア・エバンジェリスト

2016年3月に入社。NECで衛星通信の制御用ソフト開発、アップルでMacOSのローカリゼーション、AppleShareファイルサーバの開発等に従事。プライベートではRuby,Haskellなどのプログラミングとラグビー観戦を好む。最近の興味はSwiftでiOSアプリを開発すること。FAA自家用パイロットライセンス所有。