簡単に言えば、属性は動的に生成される可能性があるため、Python でオブジェクトのすべての属性のリストを取得する方法はありません。極端な例として、次のクラスを考えてみます。
>>> class Spam(object):
... def __getattr__(self, attr):
... if attr.startswith('x'):
... return attr[1:]
>>> spam = Spam()
>>> spam.xeggs
'eggs'
インタープリターがすべての属性のリストを誰かが把握できたとしても、そのリストは無限になります。
単純なクラスの場合spam.__dict__
は、多くの場合十分です。__slots__
動的属性、 に基づく属性、クラス属性、C 拡張クラス、上記のほとんどから継承された属性、およびその他のあらゆる種類のものを処理しません。しかし、それは少なくとも何かであり、時にはそれはあなたが望むものです. 最初の概算では、それは正確に で明示的に割り当てたもの__init__
であり、それ以外のものではありません。
人間の可読性を目的とした「すべて」を目的としたベスト エフォートの場合は、 を使用しますdir(spam)
。
プログラムで使用するための「すべて」を目的としたベスト エフォートの場合は、 を使用しますinspect.getmembers(spam)
。(実際、この実装はdir
CPython 2.x のラッパーにすぎませんが、もっと多くのことができます — 実際、CPython 3.2+ ではそうです。)
これらは両方とも、処理できない幅広いものを処理し、にあるが見たくない__dict__
ものをスキップする可能性があります。__dict__
しかし、それらはまだ本質的に不完全です。
何を使用しても、キーだけでなく値も簡単に取得できます。__dict__
またはを使用している場合getmembers
、それは簡単です。__dict__
通常、これは、または目的dict
のために十分に近い動作をするものであり、キーと値のペアを明示的に返します。を使用している場合は、非常に簡単に取得できます。dict
getmembers
dir
dict
{key: getattr(spam, key) for key in dir(spam)}
最後にもう 1 つ: 「オブジェクト」という用語は少しあいまいです。「から派生したクラスのobject
任意のインスタンス」、「クラスの任意のインスタンス」、「新しいスタイルのクラスの任意のインスタンス」、または「任意の型の任意の値」(モジュール、クラス、関数など) を意味します。 .)。dir
and getmembers
on はほぼ何でも使用できます。それが何を意味するかの正確な詳細は、ドキュメントに記載されています。
getmembers
最後にもう 1 つ: が ) のようなものを返すことに気付くかもしれませんが、これにはおそらく関心がありません。結果は単なる名前と値のペアであるため、メソッドや変数など('__str__', <method-wrapper '__str__' of Spam object at 0x1066be790>
を削除したいだけの場合は、簡単。しかし、「メンバーの種類」でフィルタリングしたいことがよくあります。この関数はフィルター パラメーターを受け取りますが、ドキュメントはその使用方法をうまく説明していません (さらに、記述子がどのように機能するかを理解している必要があります)。基本的に、フィルターが必要な場合は、通常、 、、または関数の組み合わせで構成されます。__dunder__
_private
getmembers
callable
lambda x: not callable(x)
lambda
inspect.isfoo
したがって、これは関数として書きたいと思うかもしれません。
def get_public_variables(obj):
return [(name, value) for name, value
in inspect.getmembers(obj, lambda x: not callable(x))
if not name.startswith('_')]
これをカスタム IPython %magic 関数に変換するか、単に %macro を作成するか、通常の関数のままにして明示的に呼び出すことができます。
__repr__
コメントで、%magic 関数などを作成しようとする代わりに、これを関数にパッケージ化できるかどうかを尋ねました。
すべてのクラスが単一のルート クラスから継承されている場合、これは素晴らしいアイデアです。__repr__
すべてのクラスで機能する単一のものを作成できます (または、それらの 99% で機能する場合__repr__
は、残りの 1% でそれをオーバーライドできます)。その後、インタープリターまたは印刷でオブジェクトを評価するたびにそれらを出して、あなたが望むものを手に入れましょう。
ただし、次の点に注意してください。
Python には、理由から (オブジェクトの__str__
場合に得られるものprint
) と(対話型プロンプトでオブジェクトを評価しただけの場合に得られるもの) の両方があります。__repr__
通常、前者は人間が判読できる優れた表現ですが、後者は、入力可能eval
(または対話型プロンプトに入力可能) であるか、タイプを区別するのに十分な簡潔な山かっこ形式です。オブジェクトのアイデンティティ。
これはルールではなく単なる慣例なので、自由に破ることができます。ただし、それを壊す場合でもstr
、 /repr
の区別を利用したい場合があります。たとえば、makerepr
はすべての内部構造の完全なダンプを提供しstr
、便利な public 値のみを表示します。
repr
もっと真剣に、値がどのように構成されているかを考慮する必要があります。たとえば、あなたprint
またはrepr
の場合list
、実質的に を取得します'[' + ', '.join(map(repr, item))) + ']'
。これは、複数行でかなり奇妙に見えるでしょうrepr
。また、IPython に組み込まれているような、ネストされたコレクションをインデントしようとする任意の種類のプリティ プリンターを使用すると、さらに悪化します。結果はおそらく判読不能になることはなく、pretty-printer が提供するはずの利点が台無しになるだけです。
表示したい特定のものについては、すべて非常に簡単です。このようなもの:
def __repr__(self):
lines = []
classes = inspect.getmro(type(self))
lines.append(' '.join(repr(cls) for cls in classes))
lines.append('')
lines.append('Attributes:')
attributes = inspect.getmembers(self, callable)
longest = max(len(name) for name, value in attributes)
fmt = '{:>%s}: {}' % (longest, )
for name, value in attributes:
if not name.startswith('__'):
lines.append(fmt.format(name, value))
lines.append('')
lines.append('Methods:')
methods = inspect.getmembers(self, negate(callable))
for name, value in methods:
if not name.startswith('__'):
lines.append(name)
return '\n'.join(lines)
ここで最も難しいのは、属性名を右揃えにすることです。(そして、これはテストされていないコードなので、おそらく間違っていると思います…) 他のすべては簡単であるか、楽しいものです (さまざまなフィルターで遊んで、getmembers
それらが何をするかを確認します)。