紛失届
LOST-007
届出人
オーケストラ巡回公演の輸送担当、ムン室長
なくしたもの
楽器ケースの一つに「割れ物注意」のステッカーを貼ったのに、同じ規格で作ったケースすべてにそのステッカーが貼られている。
解決の条件
ステッカーのリストが実際にどこにあるのかを見つけ、ケースごとに自分のステッカーリストを持たせる。
ハヌルソム国際空港 手荷物センター
ド・ミラン班長の机の引き出しには、かばんの規格書がひと束入っています。規格書が一枚あれば、同じ形のかばんをいくつでも作れます。「規格書も一枚の紙、かばんも一つひとつ。どっちも物なんだ」
ムン室長は、バイオリンケースに「割れ物注意」を貼っただけなのに、チェロケースにも同じステッカーが見えるといいます。班長はケースではなく規格書を広げてみます。ステッカーはそこに貼られていました。
class 文は、クラスオブジェクトを作って名前に結び付ける代入です(1限目の def と同じです)。クラスも型を持つオブジェクトで、その型は type です。
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
print(type(Case), isinstance(Case, type))
# <class 'type'> True
b = Case("cello")
print(type(b).__name__, type(b) is Case)
# Case True
print(vars(b))
# {'owner': 'cello'}
print([k for k in vars(Case) if not k.startswith("__")])
# ['size']
print(b.size, "size" in vars(b))
# M False| 何を入れているか | 見る方法 | |
|---|---|---|
| クラス(規格書) | クラス本体で作った名前:クラス属性、メソッド | vars(Case)、Case.__dict__ |
| インスタンス(かばん) | そのかばんだけの属性。たいていは __init__ で self.x = ... として | vars(b)、b.__dict__ |
b の __dict__ には owner しかありません。それなのに b.size は "M" と読めます。__dict__ を見て、なければクラスを見ます。かばんになければ規格書を広げてみる、というわけです。__dict__ は mappingproxy という読み取り専用の窓です。クラス属性を変えたいときは、Case.size = "L" のようにクラスに直接代入します。届出人の規格書です。
class Case:
stickers = []
def __init__(self, owner):
self.owner = owner
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(b.stickers, Case.stickers)
# ['FRAGILE'] ['FRAGILE']
print(a.stickers is b.stickers, "stickers" in vars(a))
# True Falsestickers = [] はクラス本体で一度だけ実行されます。リストは規格書に一つ貼られているだけです。a.stickers は a の __dict__ にないのでクラスで見つかり、append はその一つしかないリストを変えます(かばんの変更)。インスタンスごとに別々に持つべきものは、__init__ で self に付けます。下で三つのやり方を比べてみてください。
タグボード
ステップ 1 / 0
次に実行する行: 0
実習台を準備中…
いま変わったもの
封印されたかばん(イミュータブル)
三つ目のやり方のように、インスタンスに同じ名前で代入すると、クラス属性はそのままで、インスタンスの __dict__ に新しい名前ができます。そのインスタンスでは、クラス属性が隠れます(5限目の、組み込みの名前を隠すのと同じ形です)。
class Case:
size = "M"
a = Case()
b = Case()
a.size = "L"
print(a.size, b.size, vars(a))
# L M {'size': 'L'}
Case.size = "S"
print(a.size, b.size)
# L S
del a.size
print(a.size)
# Sb)からすぐに見えます。del a.size でインスタンスの名前を外すと、またクラスの値が見えます。+= も代入だということを忘れないでください。数値のクラス属性に += を使うと、インスタンスに新しい名前ができます。
class Gate:
count = 0
g = Gate()
g.count += 1
print(g.count, Gate.count, vars(g))
# 1 0 {'count': 1}g.count += 1 は g.count = g.count + 1 です。右辺はクラスから0を読み、左辺は g の __dict__ に1を入れます。すべてのインスタンスで一緒に数える数なら、Gate.count += 1 のようにクラスに直接代入しなければなりません。
ピンの案内
→ クラス本体にリストや辞書を置くと、すべてのインスタンスで共有されます。定数やデフォルト値のように本当に共有するものだけをクラスに置き、かばんごとに違うものは __init__ で self に付けましょう。
b.x を読むときにPythonがたどる道を、検査場の流れ図で見てみます。この回では、2段階目(インスタンスの __dict__)と4段階目(クラスの普通の属性)だけを見れば十分です。1・3・5段階目は10限目で扱います。
下の b は、size をクラスにも、自分の __dict__ にも持っています。
検査場の流れ図
1
class Case:
2
size = "M"
3
stickers = []
4
def __init__(self, owner):
5
self.owner = owner
6
def label(self):
7
return "case of " + self.owner
8
9
b = Case("cello")
10
b.size = "L"
b.
クラスにデータディスクリプタ(__get__ + __set__)がある?
インスタンスの__dict__にある?
クラスに非データディスクリプタ(関数のように__get__だけ)がある?
クラスに普通の属性がある?
__getattr__がある?
AttributeError
結果: b. →
size はクラスにもありますが、2段階目で先に見つかるので 'L' です。「結果」の行のかっこ内の Case.size は、「クラスにも同じ名前がある」という印です。label はクラスにある関数です。関数は3段階目に引っかかる特別なオブジェクトなので、取り出すとただの関数ではなく bound method になります。次の節のテーマです。メソッドは、クラスに入っている普通の関数です。インスタンスを通して取り出すと、Pythonが関数とインスタンスを束ねた新しいオブジェクト、つまりバウンドメソッドを作ってくれます。
class Bag:
def __init__(self, owner):
self.owner = owner
def label(self):
return "bag of " + self.owner
bag = Bag("kim")
print(bag.label is bag.label, bag.label == bag.label)
# False True
print(bag.label.__self__ is bag, bag.label.__func__ is Bag.label)
# True True
print(bag.label(), Bag.label(bag))
# bag of kim bag of kim
print(type(bag.label).__name__, type(Bag.label).__name__)
# method functionbag.label と書くたびに、新しいバウンドメソッドができます。だから is は False です。同じ関数と同じインスタンスを束ねているので、== は True です。__self__ は束ねられたインスタンス、__func__ はクラスにある元の関数です。bag.label() は Bag.label(bag) と同じです。self は、バウンドメソッドが代わりに埋めてくれる最初の引数にすぎません。タグボード
ステップ 1 / 0
次に実行する行: 0
実習台を準備中…
いま変わったもの
封印されたかばん(イミュータブル)
vars、getattr、setattr、hasattr (5分)属性の名前を、実行中に文字列で決めなければならないことがあります。設定ファイルのキーや、画面で選んだ列の名前などです。
class Bag:
def __init__(self, owner):
self.owner = owner
bag = Bag("kim")
setattr(bag, "color", "red")
print(getattr(bag, "color"), hasattr(bag, "weight"), getattr(bag, "weight", 0))
# red False 0
print(vars(bag))
# {'owner': 'kim', 'color': 'red'}
vars(bag)["tag"] = "T1"
print(bag.tag, vars(bag) is bag.__dict__)
# T1 True
bag.weight
# AttributeError: 'Bag' object has no attribute 'weight'setattr(obj, "x", v) は obj.x = v、getattr(obj, "x") は obj.x と同じです。getattr の3番目の引数は、ないときに返すデフォルト値です。hasattr は、実際に getattr してみて AttributeError が出るかどうかで判断します。vars() はスナップショットではなく、本物の __dict__ です。5限目の、関数の中の locals() とは違います。bag.colour = ...)も黙って新しい属性になってしまいます。__slots__ とそのほかの新機能 (5分)__slots__ に属性の名前を書いておくと、インスタンスに __dict__ が作られず、書いた名前だけが使えます。CPythonでは、インスタンスごとの辞書がないぶんメモリも節約できます。
class Slim:
__slots__ = ("owner",)
def __init__(self, owner):
self.owner = owner
s = Slim("kim")
print(s.owner, hasattr(s, "__dict__"))
# kim False
s.color = "red"
# AttributeError: 'Slim' object has no attribute 'color' and no __dict__ for setting new attributes3.13から、クラスには __static_attributes__ があります。クラスのメソッドの中で self.名前 = ... として値を入れた名前を、並べ替えて集めておいてくれます。
class Trunk:
def __init__(self, owner):
self.owner = owner
self.tags = []
def pack(self):
self.weight = 10
print(Trunk.__static_attributes__)
# ('owner', 'tags', 'weight')最後に、3.14のアノテーションの遅延評価(PEP 649、749)です。クラス本体のアノテーションは、クラスを作るときではなく、__annotations__ を初めて読むときに計算されます。そのため、まだ定義していない名前をアノテーションに書いても、クラスは作られます。3.13までは friend: Missing の行で NameError になっていました。
class Partner:
friend: Missing
print("created", Partner.__name__)
# created Partner
class Missing:
pass
print(Partner.__annotations__)
# {'friend': <class '__main__.Missing'>}ステッカーのリストを規格書からはがし、ケースごとに貼ります。共有する値(規格のサイズ)はクラスに残しておきます。
class Case:
size = "M"
def __init__(self, owner):
self.owner = owner
self.stickers = []
a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(a.stickers, b.stickers)
# ['FRAGILE'] []
print(vars(a))
# {'owner': 'violin', 'stickers': ['FRAGILE']}
print(Case.__static_attributes__)
# ('owner', 'stickers')これで「割れ物注意」はバイオリンケースにだけ貼られます。ムン室長が帰ったあと、ピンが13番コンベヤーの幽霊かばんの記録を画面に映します。二つのかばんは同じ規格で作られていて、片方の partner 属性はもう片方を、もう片方の partner はまたこちらを指しています。班長はその行にペンで丸を付けます。
解決報告 — LOST-007。ステッカーのリストがクラス属性だったため、すべてのケースが同じリストを共有していた。__init__ で self.stickers = [] としてケースごとに新しいリストを作って解決。
Case.stickers = [] をそのまま置いたまま a.stickers += ["FRAGILE"] を実行すると、b.stickers と vars(a) はどうなるでしょう?(ヒント:2限目の += と、7-2の += が同時に起こります。)__slots__ = ("owner",) のクラスにクラス属性 size = "M" を置いたら、インスタンスで s.size を読めるでしょうか? s.size = "L" はどうでしょう?getattr で ["owner", "color", "weight"] の三つの属性を順に取り出して表示し、ない属性は "-" と表示するループを書いてみてください。班長の業務手帳
クラスもオブジェクト。type(Case) は type。class 文も名前を結び付ける代入だ。
インスタンスは自分の __dict__ を持つ。読むときはインスタンス → クラスの順。
クラス属性はすべてのインスタンスで共有。ミュータブルなオブジェクトを置くと、一か所の変更が全部から見える。
インスタンスに同じ名前で代入すると、クラス属性を隠す。+= も代入だ。
bag.m は取り出すたびに新しいバウンドメソッド(is は False、== は True)。__self__、__func__。
getattr / setattr / hasattr で名前を文字列として扱う。vars(obj) は本物の __dict__。
__slots__ は新しい属性を防ぐ。__static_attributes__(3.13)、アノテーションの遅延評価(3.14)。
新しい授業・講座のお知らせを受け取る
この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。
R
ReflexKR
触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。
ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する
アフィリエイトと支援について© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
質問と回答
この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。