【Python】inspectモジュールでオブジェクト(クラス、関数等)の詳細情報を取得する


投稿日 2023年11月25日 >> 更新日 2023年12月8日

概要

今回はPythonの標準パッケージである「inspect」モジュールを使用して、オブジェクトの詳細情報を取得していきます。

オブジェクトとは「モジュール・クラス・関数・インスタンス」などのことを言います。

オブジェクトの詳細情報というのは、オブジェクト内で定義されているクラス名だったり関数名だったり、クラス属性やインスタンス属性の値、そしてクラスメソッドや関数の引数といった情報を言います。

ライブラリのモジュールを使用していると中身が気になることがありますが、そんな時にinspectモジュールを使うとオブジェクト内に定義されているソースコードまで取得することができるので便利です。

オブジェクトがクラスなのか、メソッドなのか、関数なのか、属性なのかを判断できれば問題解決もスムーズになるかと思います。

実行環境

実行環境
Windows Subsystem for Linux
Python 3.7.0

inspectモジュールについて

inspectモジュールはPythonの標準パッケージに含まれているモジュールの1つで、ある特定のオブジェクトに対して活動中であるオブジェクト(モジュール、クラス、メソッド、関数、インスタンス)の名前だったり、ファイルの場所、ソースコード等の詳細情報を取得することができます。

「inspect:インスペクト」の意味をGoogleで検索したところ、生成AIは以下のような結果を表示してくれました。

  • 何かが適切な状態であることを確認したり、問題点を調べたりするために、注意深く調べる
  • 視察する、査察する、検閲する
  • 欠陥などがないかを点検する、検査する
  • 建物や組織などを公的または正式に視察する、立ち入り検査や調査をする、監査する、閲兵する
  • 審査する

上記の意味のように、よく分かっていないオブジェクトを注意深く調べられるモジュールなんだという事が伺われます。

inspectモジュールの使い方

inspectを使う場合は、inspectモジュール自体を取り込むか、そのモジュール内で定義されているクラスや関数を取り込むかです。

まだモジュール内で定義されているオブジェクトが分からないのでモジュールを取り込んでいきます。

# import inspect
import inspect as isp

「inspect」と打ち込むのが面倒な場合は「isp」として取り込みます。

inspectモジュールに定義されているオブジェクトを調べるに当たってさっそくinspectを使いたいところですが、Pythonの組み込み関数でオブジェクト内で定義されているオブジェクトを取得できる関数が既にあります。

それが「dir()」と「vars()」です。

dir()の戻り値はリスト形式でvars()の戻り値は辞書形式として情報を取得できます。

dir()の引数にオブジェクトを与えると有効なオブジェクトをアルファベット順のリスト形式で返してくれます。

import inspect as isp

type(dir())
# <class 'list'>

for d in dir(isp):
    print(d)

# ArgInfo
# ...
# warnings

vars()の引数にオブジェクトを与えると、オブジェクト名とその値を辞書形式で返してくれます。

import inspect as isp

type(vars())
# <class 'dict'>

for key, item in vars(isp).items():
    print(key, ':', item)

# __name__ : inspect
# ...
# _main : <function _main at 0x7ff1c368b620>

組み込み関数の「dir()」と「vars()」はオブジェクトが持っているオブジェクトやその値を取得できましたが、inspectモジュールでは抽出したいオブジェクトを取得したりオブジェクトのソースコードを取得したりと細かい操作を実行することができます。

getmembersでオブジェクトの情報を取得する

inspectモジュールの「getmembers()」にオブジェクトを与えると、そのオブジェクト内で定義されているオブジェクトの情報を取得することができます。

戻り値はオブジェクト名とそのデータ型をペアにしたリスト内タプルによって返されます。

import inspect as isp
from inspect import getmembers

type(getmembers(isp))
# <class 'list'>

for get in getmembers(isp):
    print(get)

# ('ArgInfo', <class 'inspect.ArgInfo'>)
# ...
# ('warnings', <module 'warnings' from '/home/lib/python3.7/warnings.py'>)

「inspect = isp」自体は「モジュール = Pythonファイル」なので、ファイル内で定義されているオブジェクト達(クラスや関数や変数)が列挙されて取得できます。

試しにクラスと関数を作成してinspectのgetmembersにかけてみたいと思います。

※以下はインタラクティブモードで実行しています


class Test:
    class_attr = 'クラス属性'
    def __init__(self):
        self.instance_attr = 'インスタン属性'
    def test_method():
        result = '{}と{}'.format(self.class_attr, self.instance_attr)
        return result


def test_func():
    return 'function'

Testクラスにはクラス直下に定義しているクラス変数(class_attr)と、特殊メソッドの初期値としてインスタンス変数(self.instance_attr)を定義し、インスタンスメソッド(test_method())を定義しています。

そして関数として「test_func()」を定義しています。

Testクラスの「クラスオブジェクト」と「インスタンスオブジェクト」をinspectのgetmembers()にかけてみます。

from inspect import getmembers


cls_members = getmembers(Test)  # クラスオブジェクト
inst_members = getmembers(Test())  # インスタンスオブジェクト

for index, cls in enumerate(cls_members):
    print(index, cls)

# 0 ('__class__', <class 'type'>)
# 1 ('__delattr__', <slot wrapper '__delattr__' of 'object' objects>)
# 2 ('__dict__', mappingproxy({'__module__': '__main__', 'class_attr': 'クラス属性', '__init__': <function Test.__init__ at 0x7f3f80d92268>, 'test_method': <function Test.test_method at 0x7f3f80d922f0>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}))
# ...
# 26 ('class_attr', 'クラス属性')
# 27 ('test_method', <function Test.test_method at 0x7f3f80d922f0>)


for index, inst in enumerate(inst_members):
    print(index, inst)

# 0 ('__class__', <class '__main__.Test'>)
# 1 ('__delattr__', <method-wrapper '__delattr__' of Test object at 0x7f3f80da6f60>)
# 2 ('__dict__', {'instance_attr': 'インスタンス属性'})
# ...
# 26 ('class_attr', 'クラス属性')
# 27 ('instance_attr', 'インスタンス属性')
# 28 ('test_method', <bound method Test.test_method of <__main__.Test object at 0x7f3f80da6f60>>)

Testクラスの「クラス(cls_members)」と「インスタンス(inst_members)」では、異なった情報を取得しています。

クラスオブジェクトをインスタンスオブジェクトにすることにより、特殊メソッドで初期化された属性値なども取得できるので、調べる際には注意が必要です。

次に「test_func()」関数をgetmembersにかけていきます。

from inspect import getmembers

func_members = getmembers(test_func)  # 関数
result_members = getmembers(test_func())  # 関数の実行結果


for index, func in enumerate(func_members):
    print(index, func)

# 0 ('__annotations__', {})
# 1 ('__call__', <method-wrapper '__call__' of function object at 0x7f3f80d921e0>)
# 2 ('__class__', <class 'function'>)
# ...
# 34 ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x55a8c90ef560>)


for index, result in enumerate(result_members):
    print(index, result)

# 0 ('__add__', <method-wrapper '__add__' of str object at 0x7f3f80d8bea0>)
# 1 ('__class__', <class 'str'>)
# ...
# 67 ('rsplit', <built-in method rsplit of str object at 0x7f3f80d8bea0>)
# 68 ('rstrip', <built-in method rstrip of str object at 0x7f3f80d8bea0>)
# 69 ('split', <built-in method split of str object at 0x7f3f80d8bea0>)
# 70 ('splitlines', <built-in method splitlines of str object at 0x7f3f80d8bea0>)
# 71 ('startswith', <built-in method startswith of str object at 0x7f3f80d8bea0>)
# 72 ('strip', <built-in method strip of str object at 0x7f3f80d8bea0>)
# 73 ('swapcase', <built-in method swapcase of str object at 0x7f3f80d8bea0>)
# 74 ('title', <built-in method title of str object at 0x7f3f80d8bea0>)
# 75 ('translate', <built-in method translate of str object at 0x7f3f80d8bea0>)
# 76 ('upper', <built-in method upper of str object at 0x7f3f80d8bea0>)
# 77 ('zfill', <built-in method zfill of str object at 0x7f3f80d8bea0>)

「test_func()」関数の実行前では関数オブジェクトからアクセスできるメソッドなどを取得でき、実行結果からは、Stringクラスのインスタンスオブジェクトからアクセスできるメソッドなどを取得しています。

signatureでオブジェクトの引数名を取得する

inspectモジュールの「signature()」を使用する事で、オブジェクトに定義されている引数を取得することができます。

試しにinspectモジュールにある「getmembers()」の引数を取得してみます。

※signatureの引数にはオブジェクトを与える

from inspect import getmembers, signature

sig = signature(getmembers)
print(sig)
# (object, predicate=None)

「getmembers()」の引数は「getmembers(object, predicate=None)」であることが分かりました。
「getmembers」の第二引数である「predicate」にinspectモジュールにある「is○○」を与えることによって、特定のオブジェクトのタイプを抽出して取得できるという事ですね。

クラスやクラスメソッドの引数も取得することができるので、自作クラスを作成して試してみます。

class Test:
    def __init__(self, name='default'):
        self.name = name
    def test_method(self, n=0):
        result = '{}の身長は{}cmです'.format(self.name, n)
        return result

Testクラスで定義されているメソッドで必要となる引数を「signature()」を使って取得します。

from inspect import signature

sig_test_cls = signature(Test)
print(sig_test_cls)
# (name='default')

sig_test_method = signature(Test().test_method)
print(sig_test_method)
# (n=0)

result = Test('マイケル').test_method(180)
print(result)
# マイケルの身長は180cmです

「signature()」でTestクラスを調べた結果、初期値に名前らしき文字列型を与えて、「test_method」のメソッドの引数には整数型を与えればよいことが分かりました。

特定のオブジェクト(モジュール、クラス、メソッド、関数など)を抽出する

あるオブジェクトがモジュールなのか、クラスなのか、それとも関数なのかオブジェクトのタイプを知りたくなった時、inspectモジュールに「is~」から始まる関数の引数に目的のオブジェクトを与えると、ブール値による判定でオブジェクトのタイプを取得することができます。

以下の例は、「モジュール・クラス・関数」の判定を行う実装です。

import inspect as isp
from inspect import ismodule, isclass, isfunction

# モジュールの判定
print(ismodule(isp))
# True

# クラスの判定
print(isclass(isclass))
# False

print(isfunction(isfunction))
# True

inspectモジュールの「getmembers()」と「is~()」を組み合わせることで、特定のオブジェクトを抽出することができます。

signatureでオブジェクトの引数名を取得する」で説明した「signature()」でオブジェクトの引数を取得することができましたが、「getmembers()」の第二引数の「predicate」に「is~()」を渡すことにより抽出することができます。

print(signature(getmembers))
# (object, predicate=None)

「getmembers()」と「is~()」を使ってinspectモジュールにある関数オブジェクトを抽出していきます。

import inspect as isp
from inspect import getmembers, isfunction

for get in getmembers(isp, isfunction):
    # オブジェクトの先頭に「is」が入っている関数だけ取得
    if 'is' in get[0][:2]:  # get[0][:2]==is
    print(get)

# ('isabstract', <function isabstract at 0x7fb9473edbf8>)
# ('isasyncgen', <function isasyncgen at 0x7fb9473ed730>)
# ('isasyncgenfunction', <function isasyncgenfunction at 0x7fb9473ed6a8>)
# ('isawaitable', <function isawaitable at 0x7fb9473ed8c8>)
# ('isbuiltin', <function isbuiltin at 0x7fb9473edae8>)
# ('isclass', <function isclass at 0x7fb9473ed1e0>)
# ('iscode', <function iscode at 0x7fb9473eda60>)
# ('iscoroutine', <function iscoroutine at 0x7fb9473ed840>)
# ('iscoroutinefunction', <function iscoroutinefunction at 0x7fb9473ed620>)
# ('isdatadescriptor', <function isdatadescriptor at 0x7fb9473ed378>)
# ('isframe', <function isframe at 0x7fb9473ed9d8>)
# ('isfunction', <function isfunction at 0x7fb9473ed510>)
# ('isgenerator', <function isgenerator at 0x7fb9473ed7b8>)
# ('isgeneratorfunction', <function isgeneratorfunction at 0x7fb9473ed598>)
# ('isgetsetdescriptor', <function isgetsetdescriptor at 0x7fb9473ed488>)
# ('ismemberdescriptor', <function ismemberdescriptor at 0x7fb9473ed400>)
# ('ismethod', <function ismethod at 0x7fb9473ed268>)
# ('ismethoddescriptor', <function ismethoddescriptor at 0x7fb9473ed2f0>)
# ('ismodule', <function ismodule at 0x7fb947f042f0>)
# ('isroutine', <function isroutine at 0x7fb9473edb70>)
# ('istraceback', <function istraceback at 0x7fb9473ed950>)

自作のクラスを作成してクラスメソッドを抽出していきます。

class Test:
    def __init__(self):
        pass
    def method_1(self):
        pass
    def method_2(self):
        pass

メソッドを抽出したい場合は、「getmembers()」の引数にインスタンスオブジェクトを与える必要があります。

注意

クラスオブジェクトとして与えてしまうとメソッドにアクセスできる範囲外となってしまうので、メソッドではなく関数となります。

from inspect import getmembers, ismethod, isfunction

get_method = getmembers(Test(), ismethod)  # メソッドを抽出
get_func = getmembers(Test, isfunction)  # 関数を抽出

# インスタンス化したクラスから抽出
for get in get_method:
    print(get)

('__init__', <bound method Test.__init__ of <__main__.Test object at 0x7fb947f06160>>)
('method_1', <bound method Test.method_1 of <__main__.Test object at 0x7fb947f06160>>)
('method_2', <bound method Test.method_2 of <__main__.Test object at 0x7fb947f06160>>)

# クラスオブジェクトから抽出
for get in get_func:
    print(get)

('__init__', <function Test.__init__ at 0x7fb9473776a8>)
('method_1', <function Test.method_1 at 0x7fb947377730>)
('method_2', <function Test.method_2 at 0x7fb9473777b8>)

オブジェクトで定義されているソースコードの情報を取得する

オブジェクトのファイルの在り処やソースコード、コメント等の情報を取得したい場合はinspectモジュールにある「get○○()」を使用します。

以下はinspectモジュールの「getmembers()」の情報を取得しています。

from inspect import getmembers, getfile, getmodule, getdoc, getsource

# オブジェクトが定義されているファイルの場所
print(getfile(getmembers))
# /home/kenno/.pyenv/versions/3.7.0/lib/python3.7/inspect.py

# オブジェクトが定義されているモジュール名
print(getmodule(getmembers))
# <module 'inspect' from '/home/kenno/.pyenv/versions/3.7.0/lib/python3.7/inspect.py'>

# オブジェクトのソースコードを取得
print(getsource(getmembers))
# def getmembers(object, predicate=None):
#     """Return all members of an object as (name, value) pairs sorted by name.
#     Optionally, only return members that satisfy a given predicate."""
#     if isclass(object):
#         mro = (object,) + getmro(object)
#     else:
#         mro = ()
#     ...
#     ...
#     results.sort(key=lambda pair: pair[0])
#     return results

# オブジェクト内のドックストリングを取得
print(getdoc(getmembers))
# Return all members of an object as (name, value) pairs sorted by name.
# Optionally, only return members that satisfy a given predicate.

オブジェクトの振る舞いや使い方が分からなかった時、inspectモジュールを使ってサクッと抽出することができるので便利です。

それでは以上となります。

最後までご覧いただきありがとうございました。