インスタンス変数とクラス変数
買物かごを二つ作り、一方へ商品を追加したときにどこが変わるか確認します。メソッドのself、同じオブジェクトを指す別名、クラス属性とインスタンス属性の違いを順に扱います。
説明
クラスは、データを保持する属性と、そのデータを扱うメソッドをまとめます。同じクラスから複数のインスタンスを作っても、通常はそれぞれが自分の属性を持ちます。一方、クラス側に置いた値は複数のインスタンスから参照されます。
買物かごを二つ作り、一方へ商品を追加したときにどこが変わるか確認します。メソッドのself、同じオブジェクトを指す別名、クラス属性とインスタンス属性の違いを順に扱います。
1買物かごを表すクラス
__init__で持ち主と空の商品リストを作ります。addは、そのかごの商品リストへ一件追加するメソッドです。
class Basket:買物かごの型を定義
kind = "shopping"クラス側の属性
def __init__(self, owner):インスタンスを初期化
self.owner = owner
self.items = []個別の商品リスト
def add(self, item):対象のかごへ追加
self.items.append(item)
def count(self):商品の件数を返す
return len(self.items)__init__はインスタンスの初期化時に呼ばれます。selfは操作対象のインスタンスです。self.items = []は初期化のたびに新しいリストを作るので、かごごとに別のリストを持ちます。
2同じクラスから二つ作る
あやと健のかごを作り、属性とオブジェクトの同一性を表示します。
a = Basket("あや"); b = Basket("健")独立したかごを生成; print(vars(a), vars(b)それぞれの属性); print(a is b, a.items is b.itemsかごとリストの同一性)属性はそれぞれの持ち主と空リストです。a is bも商品リスト同士のisもFalseで、二つのかごと二つの商品リストは別のオブジェクトです。varsはインスタンスが持つ属性辞書を表示します。
3メソッドの操作対象
aのメソッドで追加した後、クラスの関数へaを明示してもう一件追加します。
a.add("pen")aを対象として追加; Basket.add(a, "book")selfにaを明示して追加; print(a.owner, a.items, a.count())aの状態; print(b.owner, b.items, b.count())bの状態あやのかごだけがpen、bookの2件になります。a.add("pen")ではaがselfとして自動的に渡ります。Basket.add(a, "book")は同じ対象を明示する書き方です。
4別名から同じかごを更新
aliasへaを代入して追加します。代入がかごの複製になるか確認します。
alias = a同じかごへ別名を付ける; alias.add("eraser")別名から更新; print(alias is a同じ対象か確認, a.items, b.items)alias is aはTrueで、aにもeraserが見えます。代入は既存のオブジェクトへ別の名前を結び付けます。別のかごが必要な場合は、新たに生成するか、目的に合ったコピーを行います。
5クラス側の属性を参照
kindは各インスタンスの属性辞書にはありません。クラス側を参照して取得されています。
print(a.kind, b.kind, Basket.kind)インスタンスとクラスから参照; print("kind" in vars(a)個別属性の有無); Basket.kind = "order"クラス側を変更; print(a.kind, b.kind)変更後の参照値最初はすべてshopping、クラスの変更後はaもbもorderになります。この単純なクラスでは、インスタンス自身にkindがなければクラス側の値が使われます。
6一つのインスタンスに同名属性を作る
a.kindへ代入し、さらにクラスのkindを変更します。
a.kind = "gift"aだけの属性を作る; Basket.kind = "checkout"クラス側を再変更; print(a.kind, b.kind, Basket.kind)それぞれの値; print(vars(a)個別属性にkindがあるか確認)aには個別のkindができてgiftを返します。bには個別のkindがないため、クラス側のcheckoutを返します。同じ名前でも、どこに属性を持つかで変更の影響が異なります。
7クラスに可変リストを置いた場合
商品リストをクラス側へ置くと、複数のかごが同じリストを参照します。その違いを確認するため、別のクラスを定義します。
class SharedBasket:共有リストを持つ例
items = []クラスに一つだけ置く
def add(self, item):
self.items.append(item)参照先のリストを変更self.itemsへの代入はなく、クラス側のリストを取得してappendします。appendはそのリスト自体を変更します。
8共有による意図しない混入
二つのかごを作り、片方にだけ追加します。
x = SharedBasket(); y = SharedBasket()二つのインスタンス; x.add("pen")片方から共有リストを変更; print(x.items, y.items, x.items is y.itemsリストが同じか確認); print(vars(x), vars(y)個別属性は作られていない)両方にpenが見え、リストの同一性もTrueです。個別の属性辞書は空のままです。最初のBasketのように__init__内でself.itemsを作れば、各インスタンスのリストとして管理できます。
まとめ
selfはメソッドの操作対象です。同じクラスから作ったインスタンスでも、属性を個別に持たせられます。
単なる代入はオブジェクトを複製しません。別名からの更新も同じ対象へ届きます。
クラス属性に可変オブジェクトを置くと共有されます。個別状態は初期化時にインスタンス属性として作ります。
操作履歴と解説を振り返れます。
Python 3
class Basket:
kind = "shopping"
def __init__(self, owner):
self.owner = owner
self.items = []
def add(self, item):
self.items.append(item)
def count(self):
return len(self.items)a = Basket("あや"); b = Basket("健"); print(vars(a), vars(b)); print(a is b, a.items is b.items){'owner': 'あや', 'items': []} {'owner': '健', 'items': []}
False Falsea.add("pen"); Basket.add(a, "book"); print(a.owner, a.items, a.count()); print(b.owner, b.items, b.count())あや ['pen', 'book'] 2 健 [] 0
alias = a; alias.add("eraser"); print(alias is a, a.items, b.items)True ['pen', 'book', 'eraser'] []
print(a.kind, b.kind, Basket.kind); print("kind" in vars(a)); Basket.kind = "order"; print(a.kind, b.kind)shopping shopping shopping False order order
a.kind = "gift"; Basket.kind = "checkout"; print(a.kind, b.kind, Basket.kind); print(vars(a))gift checkout checkout
{'owner': 'あや', 'items': ['pen', 'book', 'eraser'], 'kind': 'gift'}class SharedBasket:
items = []
def add(self, item):
self.items.append(item)x = SharedBasket(); y = SharedBasket(); x.add("pen"); print(x.items, y.items, x.items is y.items); print(vars(x), vars(y))['pen'] ['pen'] True
{} {}演習完了