このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【Python入門】オブジェクト属性の仕組み

オブジェクト属性の仕組み

 オブジェクトには「属性(attribute)」と呼ばれる仕組みがあり、クラスの外部からでも自由に追加したり、値を変更・取得したりできます。Pythonのオブジェクト指向機能は、この「属性」の仕組みに基づいて実現されています。データ属性やメソッドが、実はオブジェクトに格納された属性の1つとして扱われるのです。
 ここでは、属性の追加・更新・取得・削除の方法や、属性名一覧の確認方法、属性を限定するスロット(__slots__)について解説します。まずは、属性がPythonオブジェクトに柔軟性をもたらす原動力であり、そこにどんな利点と注意点があるのかを見ていきましょう。

プログラムのダウンロード

 「ダウンロード」から実行できるサンプルプログラムがダウンロードできます。ファイルは、ESET Endpoint Securityでウイルスチェックをしておりますが、ダウンロードとプログラムの実行は自己責任でお願いいたします。

1.属性の追加・更新・取得・削除

1.1.setattr・getattr・hasattr・delattr の役割

 属性は通常、「ドット記法 (オブジェクト.属性名 = 値)」を使って設定や取得をしますが、Pythonには組み込み関数も用意されています。以下は代表的な組み込み関数の一覧です。

関数動作
setattr(obj, name, value)「obj」に「name」という属性を追加または更新して「value」を設定
getattr(obj, name)「obj」の「name」属性の値を取得
hasattr(obj, name)「obj」の「name」属性が存在するかをTrue/Falseで返す。
delattr(obj, name)「obj」から「name」という属性を削除

1.2.実践例:簡易クラスを使った属性操作

class B:  # 空のクラス定義
    pass

def demo_attribute_functions():
    print("◆ 属性の操作デモ ◆")

    b_obj = B()  # オブジェクトを生成
    
    # (1) 属性「x」に値123を設定
    setattr(b_obj, 'x', 123)
    # (2) 属性「x」を取得・表示
    print("属性xの値:", getattr(b_obj, 'x'))
    # (3) 属性「x」が存在するか
    print("属性xはあるか:", hasattr(b_obj, 'x'))
    
    # (4) 属性「x」を削除
    delattr(b_obj, 'x')
    # (5) 属性「x」が存在するか
    print("属性xはあるか:", hasattr(b_obj, 'x'))

if __name__ == "__main__":
    demo_attribute_functions()

実行結果

◆ 属性の操作デモ ◆
属性xの値: 123
属性xはあるか: True
属性xはあるか: False

解説

  1. b_obj = B()で空クラスのインスタンスを生成。
  2. setattr(b_obj, 'x', 123)で新しい属性「x」を作り、値に123を設定。
  3. getattr(b_obj, 'x')で取得し、123が出力される。
  4. delattr(b_obj, 'x')で属性「x」を削除し、もう一度hasattrを呼ぶとFalseに。

実行結果

◆ 属性の操作デモ ◆
属性xの値: 123
属性xはあるか: True
属性xはあるか: False

2.属性名一覧を調べる:dir と vars

2.1.dir関数

 dir(obj)は、対象オブジェクトやクラスが持つすべての属性名(メソッド名を含む)を文字列のリストとして返します。内部の特殊属性まで表示されるため、学習やデバッグ時に役立ちます。

2.2.vars関数

 vars(obj)は、オブジェクトに紐づくユーザ定義の属性名と値のペアを辞書として返します。スーパークラスなどの情報までは含まないので、純粋に「このインスタンスが新たに持っている属性は何か」を確認するときに便利です。

def demo_dir_vars():
    print("◆ 属性名一覧を調べるデモ ◆")

    class C:
        pass

    c_obj = C()
    setattr(c_obj, 'y', 999)
    
    print("dir(c_obj)の結果:", dir(c_obj))
    print("vars(c_obj)の結果:", vars(c_obj))

if __name__ == "__main__":
    demo_dir_vars()

実行結果

◆ 属性名一覧を調べるデモ ◆
dir(c_obj)の結果: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', 
'__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', 
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', 
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', 
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y']
vars(c_obj)の結果: {'y': 999}

 実行すると、dir(c_obj)には__class____str__などのシステム的な名前も含まれ、vars(c_obj){'y': 999}といった結果が出力されます。

3.スロット(slots)による属性追加の制限

3.1.属性の自由度と制限

 Pythonのオブジェクトは、基本的には後からでも自由に属性を追加できますが、意図しない属性が紛れ込むとバグに繋がる恐れがあります。そこで__slots__を使うと、どんな属性を持てるかを事前に限定できます。

3.2.スロットの宣言方法と使用例

class D:
    __slots__ = ['alpha', 'beta']  # この2つの属性だけ追加できる
    
    def __init__(self, alpha, beta):
        self.alpha = alpha
        self.beta = beta

def demo_slots():
    print("◆ スロットのデモ ◆")
    d_obj = D(10, 20)
    print("alpha:", d_obj.alpha)
    print("beta:", d_obj.beta)
    
    # 新しい属性gammaを追加してみる → エラー
    try:
        d_obj.gamma = 30
    except AttributeError as e:
        print("エラー発生:", e)

if __name__ == "__main__":
    demo_slots()

実行結果

◆ スロットのデモ ◆
alpha: 10
beta: 20
エラー発生: 'D' object has no attribute 'gamma'

解説

  • __slots__ = ['alpha', 'beta'] とすることで、alphabeta 以外の属性を追加しようとするとAttributeErrorが発生。
  • スロットを使うとメモリ効率が若干向上するというメリットもあるが、クラスの柔軟性は下がるので必要な場面をよく考える。

まとめ

  • Pythonのオブジェクトは、「属性(attribute)」という仕組みによってデータやメソッドを保持しており、setattr / getattr / hasattr / delattr 関数またはドット記法で操作可能。
  • dir はクラスやインスタンスが持つすべての属性名、vars はユーザ定義の属性と値を返す。
  • __slots__を定義すると、オブジェクトが持てる属性をあらかじめ決めて、追加を制限することができる。

 こうした属性の管理機構を理解すれば、Pythonのオブジェクト指向やクラス周りの仕組みをより深く把握できます。自分でクラスを設計するときは、これらの組み込み関数や__slots__をうまく使い分けて、意図しない属性操作や不必要なメモリ浪費を防ぎつつ、柔軟性を確保したクラスを書くようにしてください。