3秒でわかる
オブジェクトにぶら下がっている名前付きの値のこと。関係のあるデータをひとつのまとまりとして持ち歩き、対応の崩れを防ぐために使います。
もう少し詳しく
どういうものか
属性は、オブジェクトに名前を付けてぶら下げてある値です。Python では user.name のようにドットでたどります。関数がぶら下がっていればメソッド、それ以外の値なら属性と呼び分けることが多いものの、Python の内部では同じ仕組みで管理されています。
属性には、ひとつひとつのインスタンスが別々に持つインスタンス属性と、クラス全体で共有されるクラス属性があります。前者は __init__ の中で self.name のように代入して作り、後者はクラスの直下に書きます。
なぜ必要か
名前と年齢と所属を別々の変数で持つと、人数が増えたときに対応が崩れます。names[3] と ages[3] が同じ人を指している保証は、書き手の注意力しかありません。属性としてひとつのオブジェクトにまとめておけば、値どうしのつながりが構造として保証されます。
具体例
class User:
species = "human" # クラス属性
def __init__(self, name):
self.name = name # インスタンス属性
a = User("田中")
b = User("佐藤")
print(a.name, b.name) # 田中 佐藤
print(a.species, b.species) # human human
User.species = "person" # 全員に効く
a.species = "alien" # a だけに新しい属性が生える
print(a.species, b.species) # alien personつまずきやすいところ
上の最後の 3 行が、いちばん引っかかる箇所です。クラス側を書き換えると全インスタンスに反映されますが、インスタンス側に代入すると、そのインスタンス専用の属性が新しく作られ、以後クラス側の変更は届かなくなります。読むときはクラス属性、書くときはインスタンス属性、と覚えると挙動を説明できます。
もうひとつ多いのが、クラス属性に空のリストを置いてしまう誤りです。共有されているので、片方のインスタンスで append すると全員のリストに増えます。ひとりひとつ持たせたい入れ物は、必ず __init__ の中で作ってください。
存在しない属性にアクセスすると AttributeError になります。打ち間違いのほか、__init__ を通らない経路でオブジェクトを作った場合にも起こります。
似た用語との違い
ローカル変数はその関数を抜けると消えますが、属性はオブジェクトが生きている限り残ります。どちらも名前と値の組ですが、寿命と、誰から見えるかが違います。