Python / dir() / オブジェクトの属性名を調べる方法

Pythonのdir(object)は、属性の名前を文字列のリストで返します。メソッドや属性値そのものを返したり実行したりする機能ではありません。名前順に並びますが、オブジェクトが内容を変更できるため、アクセス可能な全属性の完全な一覧とは限りません。

返るのはメソッドではなく名前

属性はオブジェクトに結び付いたデータや動作で、メソッドは呼び出せる動作です。文字列のupperは名前、text.upper()が実際の呼び出しです。dir()の公式資料は対話的な探索の便利さを目的とする範囲を説明しています。

例はPython 3だけで動きます。全体をUTF-8のdir_example.pyに保存し、python dir_example.py、環境によってはpython3で実行します。後の例も同じファイルの内容を置き換えて独立して実行できます。

text = "hello"
names = dir(text)
print(type(names).__name__)
print(all(isinstance(name, str) for name in names))
print("upper" in names)
print(names == sorted(names))
print(text.upper())

期待される出力:

list
True
True
True
HELLO

各要素は文字列です。upperを見つけただけで大文字変換は行われず、最後の行で明示的に呼び出します。全属性の一覧はPythonのバージョンで変わり得るため、ここでは必要な名前の有無を確認します。

モジュールやリストでも探索する

モジュールには関数や定数などの名前があります。dir(math)のsqrtはmath.sqrt()で呼び出します。リストのappendも見つけただけでは変更せず、対象リストで呼び出して初めて追加されます。

import math

print("sqrt" in dir(math))
print(math.sqrt(9))
items = []
print("append" in dir(items))
items.append("a")
print(items)
True
3.0
True
['a']

名前だけでは引数や戻り値はわかりません。help(math.sqrt)やモジュールの公式文書を確認します。先頭のアンダースコアは命名規則を表す場合がありますが、外部アクセスを防ぐセキュリティ境界ではありません。

dir()とvars()の違い

dir(instance)には直接保存された名前に加え、クラスや継承元の名前も含まれ得ます。vars(object)は__dict__を返すので直接保存した属性と値の確認に便利ですが、全オブジェクトが辞書を持つわけではありません。

class User:
    role = "reader"

    def __init__(self, name):
        self.name = name

    def label(self):
        return f"{self.role}:{self.name}"

user = User("Ada")
print([name for name in dir(user) if name in ("name", "role", "label")])
print(vars(user))
print(user.label())
['label', 'name', 'role']
{'name': 'Ada'}
reader:Ada

nameはインスタンスに保存され、roleとlabelはクラスから得るため結果が異なります。このvars(user)はコピーではなく実際の属性辞書なので、探索目的で不用意に変更しません。__slots__を使う一部のクラスなど、辞書のないオブジェクトではTypeErrorになる場合があります。

引数なしでは現在の名前空間を調べる

dir()は現在のローカル名前空間を返します。全組み込み関数やインストール済みパッケージの一覧ではありません。独立した新しいスクリプトでは定義したmarkerはありますが、組み込みのabsは直接含まれません。

marker = 7
print("marker" in dir())
print("abs" in dir())
True
False

対話シェルやノートブックでは以前の実行で作った名前が残るため、結果が変わり得ます。呼び出す場所と残っている変数を確認します。

独自__dir__()と動的属性の限界

オブジェクトは__dir__()で一覧を変え、__getattr__()などで属性を動的に提供できます。そのため一覧にない属性へアクセスできたり、表示名の取得に失敗したりします。

class Dynamic:
    def __getattr__(self, name):
        if name == "answer":
            return 42
        raise AttributeError(name)

    def __dir__(self):
        return ["visible"]

item = Dynamic()
print(dir(item))
print("answer" in dir(item))
print(getattr(item, "answer"))
print(hasattr(item, "visible"))
['visible']
False
42
False

この例はvisibleだけを表示しますが、実際に提供する値はanswerです。dir()は探索や補完に使い、必要な動作は文書と実際の振る舞いで確認します。getattr()・hasattr()は属性取得のコードを実行し得て、dir()も独自処理を呼べるため、副作用がない検査と決めつけません。