一覧PG-13-1 · オブジェクトとクラス

インスタンス変数とクラス変数

買物かごを二つ作り、一方へ商品を追加したときにどこが変わるか確認します。メソッドの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

1
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)
2
a = Basket("あや"); b = Basket("健"); print(vars(a), vars(b)); print(a is b, a.items is b.items)
{'owner': 'あや', 'items': []} {'owner': '健', 'items': []}
False False
3
a.add("pen"); Basket.add(a, "book"); print(a.owner, a.items, a.count()); print(b.owner, b.items, b.count())
あや ['pen', 'book'] 2
健 [] 0
4
alias = a; alias.add("eraser"); print(alias is a, a.items, b.items)
True ['pen', 'book', 'eraser'] []
5
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
6
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'}
7
class SharedBasket: items = [] def add(self, item): self.items.append(item)
8
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
{} {}

演習完了