getattr()ガイド:動的属性アクセスでPythonを“使いこなす”
getattr()とは、オブジェクトから属性を文字列で柔軟かつ安全に取得するための組み込み関数である。
なぜgetattr()を知るべきか
ドット演算子による属性アクセスは読みやすいが、属性名が実行時に決まる場面では力不足になる。たとえば、設定ファイルで「使う戦略名」を受け取り、その名前に一致するメソッドを呼びたい。ここでgetattr()を使うと、getattr(obj, "method_name")()
のように動的ディスパッチが書ける。反射(リフレクション)を安全に扱える点も魅力で、デフォルト値を指定すれば存在しない属性にも優雅に対処できる。
基本のシグネチャと挙動
getattr(obj, name[, default])
は、obj.name
と同等の取得を行う。default
を省略し、かつ属性が見つからない場合は AttributeError
を送出する。default
を与えると例外を出さずにその値を返す。プロパティ、デスクリプタ、__getattr__
/__getattribute__
といったフックの挙動も尊重されるため、通常の属性アクセスと同じ規約で動く。
class Greeter:
prefix = "Hi"
def greet(self, name): return f"{self.prefix}, {name}!"
g = Greeter()
print(getattr(g, "prefix")) # => "Hi"
print(getattr(g, "greet")("Ada")) # => "Hi, Ada!"
print(getattr(g, "missing", None)) # => None(例外を出さない)
代表的ユースケース
- 動的ディスパッチ:コマンド名→メソッド呼び分け。プラグインやCLIで便利。
- 設定とオブジェクトのバインド:文字列キーで属性を切り替え、挙動を差し替える。
- シンプルなDI(依存性注入):実装名を渡して実体を引く。テスト時はモック名に差し替える。
- シリアライズ支援:モデルのフィールド名リストを回して値を抽出。
- Web/MLパイプライン:ステージ名やレイヤ名で操作を選ぶ。学習オプションを文字列から解決。
class Commands:
def add(self, a, b): return a + b
def mul(self, a, b): return a * b
def run(cmd_name, *args):
fn = getattr(Commands(), cmd_name, None)
if callable(fn): return fn(*args)
raise ValueError(f"unknown command: {cmd_name}")
類似手段との違い(同階層の比較)
手段 | 主目的 | エラー時 | 複数属性 | コール可能取得 | 遅延解決 | 型安全性 |
---|---|---|---|---|---|---|
obj.attr |
静的アクセス | 例外 | 不可 | 可 | 通常 | 高(静的解析が効きやすい) |
getattr(obj, "name", d) |
動的アクセス | 既定値/例外 | 不可 | 可 | 通常 | 低〜中(文字列依存) |
hasattr(obj, "name") |
存在確認 | 例外なし | 不可 | 非該当 | 通常 | 中 |
operator.attrgetter("a.b") |
取得関数生成 | 例外 | 可能(カンマ区切り) | 可 | 通常 | 中 |
vars(obj) |
__dict__ 取得 |
KeyError(辞書操作次第) | 可能 | 非該当 | 通常 | 中 |
__getattr__ |
最終手段の解決 | 実装次第 | 不可 | 可 | 高(動的合成) | 実装依存 |
__getattribute__ |
全アクセスのフック | 実装次第 | 不可 | 可 | 最高(すべて介入) | 実装難度高 |
備考
-
operator.attrgetter("a.b")
はドットでネストに対応。attrgetter("x", "y")
で複数同時取得が可能。 -
vars()
はクラスが__slots__
を使っていると欠けることがある。プロパティ等は見えない。
ベストプラクティス
-
外部入力はホワイトリストで:ユーザー入力の属性名をそのまま解決しない。許可名だけを列挙し、
if name in allowed
で判定してからgetattr
。 - デフォルト値を積極活用:存在しない可能性があるなら、例外より既定値で処理を続行するほうが堅牢。
-
callable
で実行可否を確認:得た属性が関数とは限らない。呼び出すなら必ずcallable(attr)
を確認。 -
attrgetter
でパイプラインを簡潔に:map(attrgetter("name"), objects)
のように関数として渡せると、コードが宣言的になる。 -
静的解析と仲直り:mypy/pyrightは文字列ベースの属性解決が苦手。
TypedDict
やProtocol
、Literal
で属性名を絞る、もしくはgetattr(obj, name)
の直後にassert hasattr(obj, name)
相当の型絞り込みを使う。
from typing import Literal, Protocol, Callable
class HasOp(Protocol):
def add(self, a: int, b: int) -> int: ...
def mul(self, a: int, b: int) -> int: ...
OpName = Literal["add", "mul"]
def apply(op: OpName, a: int, b: int, impl: HasOp) -> int:
fn: Callable[[int, int], int] = getattr(impl, op)
return fn(a, b)
罠と注意点
-
綴りミスが実行時まで露見しない:テスト不足だと本番で落ちる。
Literal
/Enum
で選択肢を限定する。 -
__getattr__
が影に隠すバグ:存在しない属性にも値を返す実装だと、綴りミスが沈む。ロギングや明示的な例外にする。 -
セキュリティ:
__class__
やダンダー属性(__
で始まる)へのアクセスは原則禁止にする。フィルタを先に。 -
パフォーマンス微妙差:ループの内側で頻繁に解決するなら、事前にローカルに束縛するか、
attrgetter
を一度だけ作って再利用する。
from operator import attrgetter
getter = attrgetter("score")
total = 0
for user in users: # 大量反復を想定
total += getter(user) # 繰り返しの名前解決を回避
hasattr
と例外設計
hasattr(obj, name)
は内部的に getattr
を使い、AttributeError
を握りつぶして False
を返す。ただし、属性取得時に別の例外が起きてもFalseになる罠がある(プロパティでDBアクセスして例外、など)。安全に判定したい場合は、getattr(obj, name, sentinel)
で番兵値(sentinel)を使い、同値比較で判断するほうが透明だ。
_sentinel = object()
value = getattr(obj, "price", _sentinel)
if value is _sentinel:
... # 未定義
else:
... # 正常値
ダイナミックなAPI設計の型付け
属性名を公開インターフェースの一部にするなら、Enum
/Literal
で表現してドキュメント化しよう。IDE補完とバリデーションが効く。さらに、Protocol
で実装側の最小要件を固定すると、交換可能な部品として安全に差し替えられる。
from enum import Enum
class Op(Enum):
ADD = "add"
MUL = "mul"
def run(op: Op, a: int, b: int, target):
fn = getattr(target, op.value)
return fn(a, b)
メタプログラミングとの接続
getattr
は setattr
/delattr
と対で覚えると強い。属性の列挙は dir()
、実体の辞書は vars()
。クラス定義を動的に構築するメタクラスや、プラグイン自動登録のレジストリとも相性が良い。
関連関数 | 役割 | 典型用途 |
---|---|---|
getattr |
取得 | 名称→属性の解決、ディスパッチ |
setattr |
設定 | モック差し替え、テスト注入 |
delattr |
削除 | 実験的機能の一時無効化 |
dir |
列挙 | 利用可能な属性の提示 |
vars |
辞書 |
__dict__ による直アクセス |
実戦レシピ集
- 簡易ルーター:ビュー関数名をURLから解決。
- 設定の段階的上書き:優先度順に属性を引き、最初に見つかった値を採用。
-
モデル→辞書変換:フィールド名リストを回し、
getattr
で値を抜く。 -
MLパイプライン:層名→処理の解決。
attrgetter
でスコア集計。
def first_found(obj, *names, default=None):
for n in names:
v = getattr(obj, n, None)
if v is not None:
return v
return default
まとめ
getattr() は「名前で行う属性アクセス」を正道で提供する。ドットアクセスの可読性と動的性の両方を保ちつつ、既定値、attrgetter
、型付けの工夫を合わせれば、運用と安全性を両立できる。過度に魔術化せず、公開インターフェースとして“選べる名前”を明示し、テストで補強するのがプロダクション品質への近道だ。最後の実務チェックリスト: ①外部入力は必ず許可リストで検証、②番兵値で未定義を判定、③大量反復ではattrgetterを再利用。④型はLiteral/Enumで絞り、Protocolで互換を担保。推奨。