Pythonイントロスペクション入門

Pythonのオブジェクトを探る方法

イントロスペクションは、プログラムのオブジェクトについて有益な情報を明らかにします。動的なオブジェクト指向プログラミング言語であるPythonは、非常に強力なイントロスペクションをサポートしています。本稿では、最も基本的な形態であるヘルプから、より高度な形態の調査 (inquisition) まで、イントロスペクションの数多くの機能を、実例を示しながら紹介します。

Patrick O’Brien (pobrien@orbtech.com), Python programmer, Orbtech

Patrick O’BrienPatrick O'Brienは、Pythonプログラマーで、コンサルタントと講師をやっています。PyCrustの作者であり、PythonCardプロジェクトの開発にも参加しています。一番最近では、PrevaylerをPythonに移植したPyPerSystチームを指揮しており、引き続き、このプロジェクトを率い、面白い別の分野に取り組んでいます。Patrickおよび彼の作品については、OrbtechのWebサイトで紹介されています。彼のメール・アドレスはpobrien@orbtech.com です。



2002年 12月 01日

イントロスペクションとは ?

日常生活でイントロスペクションというと、内省することを意味します。イントロスペクションとは、自身の考え、感情、動機、行為を吟味することを指します。大哲学者ソクラテスは、その人生の多くを内省に費やし、同時代のアテネ市民にも、同じことを行うことを促しました。ソクラテスは、彼にとって、「内省のない人生など、生きる価値がない」とすら主張しました。(ソクラテスについては、参考文献に示したリンクも参照してください。)

コンピューター・プログラミングでのイントロスペクションとは、対象物について、その素性、カバーする範囲、そして可能な事を判断するために、調査できる機能のことを指します。イントロスペクションによって、プログラマーは、さまざまな柔軟性や制御能力を手に入れることになります。イントロスペクションをサポートしているプログラミング言語を使って作業を行ってみると、ソクラテスと同様、「確認できないオブジェクトなど、実体化 (instantiate) する価値がない」と感じるようになるかもしれません。

本稿では、プログラミング言語Pythonのイントロスペクション機能について紹介します。Pythonのイントロスペクションのサポートは、この言語のいたるところまで、広く、深く行き渡っています。実際、イントロスペクション機能を備えていないPythonなど、想像することさえ難しいほどです。皆さんも、本稿を読み終える頃までには、お手元のPythonのオブジェクトの内部構造を詮索することに、非常に心地好い感じを抱くようになっていることでしょう。

高度な技法に分け入っていく前に、まずは、最も一般的だと考えられる方法で、Pythonのイントロスペクションの調査を開始したいと思います。人によっては、われわれが最初に扱う機能は、「内省的 (introspective)」と呼ぶに値しないと異論を唱える方もあるかもしれません。それらがイントロスペクションの範疇に入るのかどうかについては、議論の余地があることを認めなければならないでしょうが、この記事の目的に関して言えば、われわれにとって関心のあるのは、興味深い問題に対する答を見つけ出すことだけです。

というわけで、調査を始めることにしましょう。Pythonは、対話モードで使います。コマンド・ラインでPythonを始動すると、Pythonシェルに入ります。シェルでは、Pythonのコードを入力し、Pythonのインタープリターから即座に応答を得ることができます。(この記事で示すコマンドは、Python 2.2.2では、問題なく実行されます。それ以前のバージョンを使用している場合、結果が違ったり、エラーが出るかもしれません。最新バージョンは、PythonのWebサイトからダウンロードすることができます [参考文献] 参照)。

リスト1. 対話モードでPythonインタープリターを始動する
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Pythonを起動して、Pythonのプロンプト (>>>) が表示されると、Pythonは、どんなワードを認識するのだろうと思われる方もいらっしゃることと思います。ほとんどのプログラミング言語には、その言語で特別な意味をもつ予約語とかキーワードというものがありますが、Pythonも例外ではありません。何か情報を得るにはhelp とタイプしろと、Pythonが表示しているのに気付いた方もあるかもしれません。多分、キーワードに関するヘルプをPythonに尋ねることができるということでしょう。


Pythonのオンライン・ヘルプ・ユーティリティー

Pythonからの薦めにしたがって、まずは、help とタイプして、キーワードについての手がかりが得られるか確かめてみることにします。

リスト2. Pythonにヘルプを求める
>>> help
Type help() for interactive help, or help(object) for help about object.

どのような種類のオブジェクトがキーワードを含んでいるのかがわかりませんので、何もオブジェクトを指定せずに、help() を試してみることにします。

リスト3. ヘルプ・ユーティリティーの始動
>>> help()
Welcome to Python 2.2!  This is the online help utility.
If this is your first time using Python, you should definitely check out
the tutorial on the Internet at http://www.python.org/doc/tut/.
Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".
To get a list of available modules, keywords, or topics, type "modules",
"keywords", or "topics".  Each module also comes with a one-line summary
of what it does; to list the modules whose summaries contain a given word
such as "spam", type "modules spam".
help>

答に近づいてきたようです。ヘルプのプロンプトに対して、keywords と入力してみます。

リスト4. キーワードについてヘルプを求める
help> keywords
Here is a list of the Python keywords.  Enter any keyword to get more help.
and                 elif                global              or
assert              else                if                  pass
break               except              import              print
class               exec                in                  raise
continue            finally             is                  return
def                 for                 lambda              try
del                 from                not                 while
help> quit
You are now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)".  Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
>>>

help() とタイプすると、メッセージやいくつかの指示が示された後、ヘルプのプロンプトが現れました。このプロンプトに対してkeywords と入力すると、Pythonのキーワード一覧が示されました。われわれの質問に対する答は得られましたので、ヘルプ・ユーティリティーを終了すると、簡単な別れのメッセージが表示され、またPythonのプロンプトに戻りました。

この例からわかるように、Pythonのオンライン・ヘルプ・ユーティリティーは、さまざまなトピックの情報も表示すれば、特定のオブジェクトについての情報も表示します。このヘルプ・ユーティリティーは、非常に便利なのですが、実は、Pythonのイントロスペクション機能を利用しています。といっても、単にヘルプを使うだけでは、ヘルプがどこから情報を仕入れてきているのかはわかりません。この記事の目的は、Pythonのイントロスペクションの秘密をすべて明らかにすることですので、ヘルプ・ユーティリティーのことはこのくらいにして、先を急ぎましょう。

ヘルプの話を終わりにする前に、ヘルプを利用して、利用可能なモジュールの一覧を確認しておきます。モジュールというのは、Pythonのコードが記述してあるテキスト・ファイルで、名前が.py で終わっているファイルのことです。Pythonのプロンプトに対して、help('modules') とタイプするか、ヘルプのプロンプトに対してmodules と入力すると、以下のリストにその一部を示すように、利用可能なモジュールの長い一覧が表示されます。皆さんのシステムにどんなモジュールが用意されているのかをご自身で確かめてみてください。Pythonが、なぜ「直ぐにでも使用可能な状態」で配布される言語だと言われるのかがわかることと思います。

リスト5. 利用可能なモジュールの一覧 (その一部)
>>> help('modules')
Please wait a moment while I gather a list of all available modules...
BaseHTTPServer      cgitb               marshal             sndhdr
Bastion             chunk               math                socket
CDROM               cmath               md5                 sre
CGIHTTPServer       cmd                 mhlib               sre_compile
Canvas              code                mimetools           sre_constants
	<...>
bisect              macpath             signal              xreadlines
cPickle             macurl2path         site                xxsubtype
cStringIO           mailbox             slgc (package)      zipfile
calendar            mailcap             smtpd
cgi                 markupbase          smtplib
Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose descriptions contain the word "spam".
>>>

sysモジュール

Pythonの本質を窺(うかが)わせる情報を提供するモジュールのひとつに、sys モジュールがあります。このPythonのモジュールを利用するには、それをインポートし、その内容 (変数、関数、クラスなど) をドット (.) 表記によって参照します。sys モジュールには、さまざまな変数や関数が含まれており、これが、現在のPythonインタープリターの中身について面白いことをいろいろと明らかにしてくれます。それを少し調べてみたいと思います。もう一度Pythonを対話モードで実行し、Pythonのコマンド・プロンプトにコマンドを入力してみます。まずは、sys モジュールをインポートします。次に、sys.executable 変数を入力します。この変数には、Pythonインタープリターのパスが含まれています。

リスト6. sysモジュールのインポート
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.executable
'/usr/local/bin/python'

オブジェクトの名前だけからなるコードを1行入力すると、Pythonは、そのオブジェクトが表現しているものを表示してきます。これは、簡単なオブジェクトの場合、だいたい、そのオブジェクトの値になります。上の例では、引用符で囲んだ値が表示されていますので、sys.executable は、おそらく文字列オブジェクトなのだろうという手がかりが得られます。オブジェクトの型をもっと正確に調べる別の方法については後で紹介しますが、Pythonのプロンプトに対して単にオブジェクトの名前をタイプすることが、イントロスペクションの簡便な利用形態です。

sys モジュールの便利な属性について、さらにいくつか調べてみたいと思います。

platform 変数は、いま、どのオペレーティング・システムの上にいるのかを教えてくれます。

リスト7. sys.platform属性
>>> sys.platform
'linux2'

Pythonの現在のバージョンは、文字列またはタプルとして表示させることができます (タプルは、一連のオブジェクトからなっています)。

リスト8. sys.version属性とsys.version_info属性
>>> sys.version
'2.2.2 (#1, Oct 28 2002, 17:22:19) \n[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)]'
>>> sys.version_info
(2, 2, 2, 'final', 0)

maxint 変数は、整数のとりうる最大値を表します。

リスト9. sys.maxint属性
>>> sys.maxint
2147483647

argv 変数は、コマンド・ライン引数が指定された場合の、その引数のリストです。1番目の要素argv[0] は、実行されているそのスクリプトのパスです。Pythonを対話モードで実行した場合、この値は、空文字列となります。

リスト10. sys.argv属性
>>> sys.argv
['']

PyCrustなどの別のPythonシェルを実行すると (PyCrustの詳しいことについては、参考文献のリンクを参照してください)、以下のような表示になります。

リスト11. PyCrustでのsys.argv属性
>>> sys.argv[0]
'/home/pobrien/Code/PyCrust/PyCrustApp.py'

path 変数は、モジュール検索パス、すなわちPythonがモジュールのインポート時にモジュールの検索対象とするディレクトリーのリストです。1個目の空文字列'' は、カレント・ディレクトリーを表します。

リスト12. sys.path属性
>>> sys.path
['', '/home/pobrien/Code',
'/usr/local/lib/python2.2',
'/usr/local/lib/python2.2/plat-linux2',
'/usr/local/lib/python2.2/lib-tk',
'/usr/local/lib/python2.2/lib-dynload',
'/usr/local/lib/python2.2/site-packages']

modules 変数は、現在ロードされているすべてのモジュールについて、モジュール名とモジュール・オブジェクトを対応付ける辞書です。このリストからわかるように、Pythonは、いろいろなモジュールをデフォルトでロードします。

リスト13. sys.modules属性
>>> sys.modules
{'stat': <module 'stat' from '/usr/local/lib/python2.2/stat.pyc'>,
'__future__': <module '__future__' from '/usr/local/lib/python2.2/__future__.pyc'>,
'copy_reg': <module 'copy_reg' from '/usr/local/lib/python2.2/copy_reg.pyc'>,
'posixpath': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>,
'UserDict': <module 'UserDict' from '/usr/local/lib/python2.2/UserDict.pyc'>,
'signal': <module 'signal' (built-in)>,
'site': <module 'site' from '/usr/local/lib/python2.2/site.pyc'>,
'__builtin__': <module '__builtin__' (built-in)>,
'sys': <module 'sys' (built-in)>,
'posix': <module 'posix' (built-in)>,
'types': <module 'types' from '/usr/local/lib/python2.2/types.pyc'>,
'__main__': <module '__main__' (built-in)>,
'exceptions': <module 'exceptions' (built-in)>,
'os': <module 'os' from '/usr/local/lib/python2.2/os.pyc'>,
'os.path': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>}

keywordモジュール

Pythonのキーワードについての質問に戻りましょう。ヘルプは、キーワードの一覧を示してくれましたが、ヘルプ情報の一部は、ソースに直接書き込まれていることが分かります。キーワードの一覧の場合も、ソースに書き込まれているので、これは、あまり「内省的」だとは言えません。Pythonの標準ライブラリーに含まれているどれかのモジュールから、キーワード一覧を直接入手できないか調べてみたいと思います。Pythonのプロンプトに対してhelp('modules keywords') とタイプすると、以下のような表示が得られます。

リスト14. キーワードに関するモジュールについて、ヘルプを求める
>>> help('modules keywords')
Here is a list of matching modules.  Enter any module name to get more help.
keyword - Keywords (from "graminit.c")

keyword モジュールにキーワードが含まれているようです。keyword.py ファイルをテキスト・エディターでオープンすると、Pythonが、キーワードの一覧を、keyword モジュールのkwlist 属性として公開していることがわかります。また、keyword モジュールのコメントには、このモジュールがPython自身のソース・コードを基に自動的に生成され、キーワードの一覧が正確で完全であることを保証しているということが書かれています。

リスト15. keywordモジュールのキーワード一覧
>>> import keyword
>>> keyword.kwlist
['and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else',
'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield']

dir() 関数

モジュールを見つけ出し、インポートするのは比較的簡単ですが、それぞれのモジュールがどんな内容だったかを憶えておくのは、それほど簡単ではありません。モジュールを探し出すのに、いつもいつもソース・コードで確認するのでは大変です。幸い、Pythonには、組み込みのdir() 関数を利用して、モジュール (あるいは、その他のオブジェクト) の内容を確認する方法が用意されています。

dir() 関数は、多分、Pythonのすべてのイントロスペクションのメカニズムの中で最もよく知られているものです。この関数は、指定されたオブジェクトが備えている属性名の一覧をソートして返します。オブジェクトを何も指定しない場合、dir() は、現在のスコープ内の名前を返します。dir()keyword モジュールを指定すると何が明らかになるのかを調べてみます。

リスト16. keywordモジュールの属性
>>> dir(keyword)
['__all__', '__builtins__', '__doc__', '__file__', '__name__',
'iskeyword', 'keyword', 'kwdict', 'kwlist', 'main']

先に調べたsys モジュールは、どうでしょうか。

リスト17. sysモジュールの属性
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
'__stdin__', '__stdout__', '_getframe', 'argv', 'builtin_module_names',
'byteorder', 'copyright', 'displayhook', 'exc_info', 'exc_type', 'excepthook',
'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
'getrecursionlimit', 'getrefcount', 'hexversion', 'last_traceback',
'last_type', 'last_value', 'maxint', 'maxunicode', 'modules', 'path',
'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
'version', 'version_info', 'warnoptions']

引数を指定しないと、dir() は、現在のスコープ内の名前を返します。以前にインポートしたkeywordsys が一覧の中で、どんな出現順序になっているかに注意してください。モジュールをインポートすると、現在のスコープに、そのモジュールの名前が追加されます。

リスト18. 現在のスコープ内の名前
>>> dir()
['__builtins__', '__doc__', '__name__', 'keyword', 'sys']

先に、dir() 関数は組み込み関数であると言いましたが、それは、この関数を使うためにモジュールをインポートする必要はないということを意味します。Pythonは、とくに何もしなくても、組み込み関数を認識します。上のリストでは、dir() 呼び出しで__builtins__ という名前が返されているのがわかります。おそらく、これには、何か関係があるのでしょう。Pythonのプロンプトに__builtins__ と入力し、このことについて何か面白いことがわかるか調べてみたいと思います。

リスト19. __builtins__は、何を表している ?
>>> __builtins__
<module '__builtin__' (built-in)>

このリストから、__builtins__ は、__builtin__ という名前のモジュール・オブジェクトに結合されている現在のスコープに含まれる名前のようです。(モジュールは、値が1個だけの単純なオブジェクトではありませんので、Pythonは、モジュールに関する情報を山形カッコで囲んで表示します。) ディスク内で__builtin__.py というファイルを探してみても、そんなファイルは見つかりません。このモジュール・オブジェクトは、Pythonインタープリターによって、何も存在しない状態から創り出されます。というのも、このオブジェクトは、Pythonインタープリターが常時参照する項目を含んでいるからです。物理的なファイルは存在しませんが、dir() 関数にこのオブジェクトを渡してやることで、このオブジェクトに含まれているすべての組み込み関数、エラー・オブジェクト、およびその他いくつかの属性を確認することができます。

リスト20. __builtins__モジュールの属性
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FloatingPointError', 'IOError', 'ImportError', 'IndentationError',
'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError',
'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError',
'OverflowError', 'OverflowWarning', 'ReferenceError', 'RuntimeError',
'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__',
'abs', 'apply', 'bool', 'buffer', 'callable', 'chr', 'classmethod', 'cmp',
'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict',
'dir', 'divmod', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int',
'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list',
'locals', 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow',
'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round',
'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'unichr',
'unicode', 'vars', 'xrange', 'zip']

dir() 関数は、文字列、整数、リスト、タプル、辞書、関数、カスタム・クラス、クラス・インスタンス、クラス・メソッドなど、すべてのオブジェクト・タイプに適用することができます。dir() に文字列オブジェクトを指定し、Pythonが何を返してくるか見てみましょう。リストからわかるように、単純なPythonの文字列であっても、数多くの属性を備えています。

リスト21. 文字列の属性
>>> dir('this is a string')
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
'__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
'__hash__', '__init__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
'__new__', '__reduce__', '__repr__', '__rmul__', '__setattr__', '__str__',
'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs',
'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind',
'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']

皆さんは、以下の例を自分で試してみて、何が返されてくるか調べてみてください。なお、文字# は、コメントの始まりを表します。コメントの始まりから、その行の終わりまでは、すべて、Pythonから無視されます。

リスト22. dir() に他のいろいろなオブジェクトを指定してみる
dir(42)   # Integer (and the meaning of life)
dir([])   # List (an empty list, actually)
dir(())   # Tuple (also empty)
dir({})   # Dictionary (ditto)
dir(dir)  # Function (functions are also objects)

Pythonのイントロスペクション機能の動的な性質を明らかにするために、dir() にカスタム・クラスおよびいくつかのクラス・インスタンスを指定した例を見てみたいと思います。対話モードで、独自のクラスを定義し、そのクラスのインスタンスをいくつか作成し、それらの中の1個のインスタンスだけに一義的な属性を追加してみて、これらのことをPythonが問題なく処理するか確かめてみます。結果は、以下のとおりです。

リスト23. カスタム・クラス、クラス・インスタンス、および属性をdir() に指定する
>>> class Person(object):
...     """Person class."""
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
...     def intro(self):
...         """Return an introduction."""
...         return "Hello, my name is %s and I'm %s." % (self.name, self.age)
...
>>> bob = Person("Robert", 35)   # Create a Person instance
>>> joe = Person("Joseph", 17)   # Create another
>>> joe.sport = "football"       # Assign a new attribute to one instance
>>> dir(Person)      # Attributes of the Person class
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'intro']
>>> dir(bob)         # Attributes of bob
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name']
>>> dir(joe)         # Note that joe has an additional attribute
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
'__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name', 'sport']
>>> bob.intro()      # Calling bob's intro method
"Hello, my name is Robert and I'm 35."
>>> dir(bob.intro)   # Attributes of the intro method
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__get__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__repr__', '__setattr__', '__str__', 'im_class', 'im_func', 'im_self']

文書化文字列

dir() の例をたくさん示してきた中で、__doc__ という属性に気付かれた方もあるのではないでしょうか。この属性は、オブジェクトを説明するコメントが入っている文字列です。これは、Pythonで、文書化文字列 (documentation string) あるいはdocstringと呼ばれているもので、以下のような働きをします。モジュール、クラス、メソッドおよび関数の定義の1行目の文が文字列である場合、その文字列は、そのオブジェクトの__doc__ 属性として、オブジェクトに結合されます。たとえば、__builtins__ オブジェクトのdocstringを見てみることにします。docstringsには、よく、改行 (\n) が埋め込まれていますので、ここでは、Pythonのprint 文を使って、出力を読みやすくします。

リスト24. モジュールのdocstring
>>> print __builtins__.__doc__   # Module docstring
Built-in functions, exceptions, and other objects.
Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.

Pythonは、ここでも、Pythonシェルで対話しながら定義されたクラスやメソッドのdocstringsを管理しています。Person クラスおよびintro メソッドのdocstringsを眺めてみます。

リスト25. クラスとメソッドのdocstrings
>>> Person.__doc__         # Class docstring
'Person class.'
>>> Person.intro.__doc__   # Class method docstring
'Return an introduction.'

docstringsは、このように役に立つ情報を示してくれますので、Python開発環境の多くは、オブジェクトのdocstringsを自動的に表示する方法をいろいろと用意しています。今度は、dir() 関数のdocstringを調べてみます。

リスト26. 関数のdocstring
>>> print dir.__doc__   # Function docstring
dir([object]) -> list of strings
Return an alphabetized list of names comprising (some of) the attributes
of the given object, and of attributes reachable from it:
No argument:  the names in the current scope.
Module object:  the module attributes.
Type or class object:  its attributes, and recursively the attributes of
    its bases.
Otherwise:  its attributes, its class's attributes, and recursively the
    attributes of its class's base classes.

Pythonオブジェクトへの質問

これまで何度も「オブジェクト」という言葉を使ってきましたが、この言葉の定義は行っていませんでした。プログラミング環境でのオブジェクトは、現実世界の物体 (object) と非常によく似ています。実際の物体は、一定の形や大きさや重さなどの性質を備えています。また、実際の物体は、環境に反応したり、他の物体との間で相互作用したり、何かの仕事をしたりすることができます。コンピューターのオブジェクトは、文書やスケジュールやビジネス処理などの抽象的な物も含めて、現実世界でわれわれの周りにある物体をモデル化しようというものです。

現実世界の物体と同様、コンピューターのオブジェクトは、複数のものが、共通の性質を共有しつつ、一方でそれぞれが少しずつ独自性を備えている、ということがあります。書店にある書籍で考えてみると、本の1冊1冊には、汚れがあったり、破れがあったり、それぞれに固有な識別番号が付けられていたりします。1冊1冊の本は、固有な物体でありながら、同じ表題の本は、すべて、同じひな型 (template) を元とするインスタンス (コピー) にすぎず、元のひな型のほとんどの性質を保持しています。

オブジェクト指向のクラスやクラス・インスタンスについても同じことが言えます。たとえば、Pythonのすべての文字列に、上でdir() 関数によって明らかにしたような属性が賦与されています。先ほどの例で、われわれは、独自のPerson クラスを定義しましたが、このクラスは、Personの個々のインスタンスを作成するときのひな型の役割を果たします。個々のインスタンスは、それぞれ独自の名前と年齢を表す値を備えていますが、自分自身を世に送り出す能力は、すべてのインスタンスが共有しています。これが、オブジェクト指向です。

ですから、コンピューター用語におけるオブジェクトとは、1個の識別性と値を備え、一定の型からなり、一定の性質を備え、一定のやり方で振る舞う物のことを指します。また、オブジェクトは、1個以上の親クラスから属性の多くを継承します。Pythonでは、キーワードと特殊記号 (+-***/%<> などの演算子) 以外のすべてのものがオブジェクトです。また、Pythonには、豊富なオブジェクト型が用意されています。文字列、整数、浮動小数点、リスト、タプル、辞書、関数、クラス、クラス・インスタンス、モジュール、ファイルなどです。

引数として関数に渡されたきたものなど、任意のオブジェクトを扱う場合、そのオブジェクトについて知りたいことがいくつか出てきたりします。そこで以下では、Pythonのオブジェクトに以下のような質問に答えてもらう方法を紹介します。

  • オブジェクトの名前は ?
  • どんな種類のオブジェクトか ?
  • どんな範囲をカバーしているか ?
  • どんな事ができるか ?
  • 親は何 ?

名前

すべてのオブジェクトに名前があるわけではありませんが、名前のあるオブジェクトの場合、名前は、__name__ 属性に保存されています。名前は、オブジェクトから引き出せるが、そのオブジェクトを参照する変数からは引き出せないことに注意してください。次の例は、このことを示したものです。

リスト27. 名前に何が入っている ?
$ python
Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir()                # The dir() function
['__builtins__', '__doc__', '__name__']
>>> directory = dir      # Create a new variable
>>> directory()          # Works just like the original object
['__builtins__', '__doc__', '__name__', 'directory']
>>> dir.__name__         # What's your name?
'dir'
>>> directory.__name__   # My name is the same
'dir'
>>> __name__             # And now for something completely different
'__main__'

モジュールには名前があります。Pythonインタープリター自体は、トップ・レベルあるいはメインのモジュールとみなされます。Pythonを対話モードで実行すると、ローカル変数__name__ には、'__main__' という値が割り当てられます。同様に、Pythonのモジュールを、別のモジュールにインポートするのではなく、コマンド・ラインから実行した場合、その__name__ 属性には、そのモジュールの実際の名前ではなく、'__main__' という値が割り当てられます。このように、モジュールは、それ自身の__name__ の値を見て、自分自身がどのような使われ方をしているのか、すなわち、別のプログラムのサポートとして使われているのか、あるいはコマンド・ラインからメイン・アプリケーションとして実行されているのかを自分自身で判断することができます。したがって、Pythonのモジュールでは、以下のような定型パターン (idiom) が非常によく使われます。

リスト28. 実行かインポートかの判定
if __name__ == '__main__':
    # Do something appropriate here, like calling a
    # main() function defined elsewhere in this module.
    main()
else:
    # Do nothing. This module has been imported by another
    # module that wants to make use of the functions,
    # classes and other useful bits it has defined.

type() 関数を使えば、オブジェクトが文字列なのか整数なのか、もっと別の種類のオブジェクトなのかを調べることができます。この関数は、型オブジェクトを返してきますので、それをtypes モジュールで定義されている型と比較することができます (リスト29)。

リスト29. 私は、あなたの型と同じですか ?
>>> import types
>>> print types.__doc__
Define names for all type symbols known in the standard interpreter.
Types that are part of optional modules (e.g. array) are not listed.
>>> dir(types)
['BufferType', 'BuiltinFunctionType', 'BuiltinMethodType', 'ClassType',
'CodeType', 'ComplexType', 'DictProxyType', 'DictType', 'DictionaryType',
'EllipsisType', 'FileType', 'FloatType', 'FrameType', 'FunctionType',
'GeneratorType', 'InstanceType', 'IntType', 'LambdaType', 'ListType',
'LongType', 'MethodType', 'ModuleType', 'NoneType', 'ObjectType', 'SliceType',
'StringType', 'StringTypes', 'TracebackType', 'TupleType', 'TypeType',
'UnboundMethodType', 'UnicodeType', 'XRangeType', '__builtins__', '__doc__',
'__file__', '__name__']
>>> s = 'a sample string'
>>> type(s)
<type 'str'>
>>> if type(s) is types.StringType: print "s is a string"
...
s is a string
>>> type(42)
<type 'int'>
>>> type([])
<type 'list'>
>>> type({})
<type 'dict'>
>>> type(dir)
<type 'builtin_function_or_method'>

識別番号

先に、すべてのオブジェクトが識別番号と型と値を1つずつ備えていると述べました。注意すべきことは、1個以上の変数が同一のオブジェクトを参照している場合があるが、それと同じように、格好は似ているが (同じ型と値を備えているが)、それぞれ別個の識別番号を備えているオブジェクトを複数の変数が参照している場合もあるということです。この、オブジェクトの識別番号という考え方は、オブジェクトに変更を加える場合、たとえば、以下の例のように、blistclist の2つの変数がともに同じリスト・オブジェクトを参照しているときに、リストに項目をアペンドするというような場合、とくに重要な意味をもちます。この例からわかるように、id() 関数は、指定されたオブジェクトに対する一義的な識別番号 (identifier) を返してきます。

リスト30. Bourne ...
>>> print id.__doc__
id(object) -> integer
Return the identity of an object.  This is guaranteed to be unique among
simultaneously existing objects.  (Hint: it's the object's memory address.)
>>> alist = [1, 2, 3]
>>> blist = [1, 2, 3]
>>> clist = blist
>>> clist
[1, 2, 3]
>>> blist
[1, 2, 3]
>>> alist
[1, 2, 3]
>>> id(alist)
145381412
>>> id(blist)
140406428
>>> id(clist)
140406428
>>> alist is blist    # Returns 1 if True, 0 if False
0
>>> blist is clist    # Ditto
1
>>> clist.append(4)   # Add an item to the end of the list
>>> clist
[1, 2, 3, 4]
>>> blist             # Same, because they both point to the same object
[1, 2, 3, 4]
>>> alist             # This one only looked the same initially
[1, 2, 3]

属性

先に、オブジェクトは属性を備えており、dir() 関数が、そうした属性の一覧を返してくることを説明しました。しかし、ただ単に、属性が存在するかどうかを判定したいだけのときもあります。そして、オブジェクトが当該属性を備えているのであれば、その属性を読み出したい、という場合がよくあります。これらのことは、以下の例のように、hasattr() 関数およびgetattr() 関数を使って処理することができます。

リスト31. 属性がある。属性を読み出す。
>>> print hasattr.__doc__
hasattr(object, name) -> Boolean
Return whether the object has an attribute with the given name.
(This is done by calling getattr(object, name) and catching exceptions.)
>>> print getattr.__doc__
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.
>>> hasattr(id, '__doc__')
1
>>> print getattr(id, '__doc__')
id(object) -> integer
Return the identity of an object.  This is guaranteed to be unique among
simultaneously existing objects.  (Hint: it's the object's memory address.)

呼び出し可能オブジェクト

何らかの動作が可能なオブジェクト (関数とメソッド) は、呼び出すことができます。オブジェクトが呼び出し可能かどうかは、callable() 関数で判定することができます。

リスト32. 何かできますか ?
>>> print callable.__doc__
callable(object) -> Boolean
Return whether the object is callable (i.e., some kind of function).
Note that classes are callable, as are instances with a __call__() method.
>>> callable('a string')
0
>>> callable(dir)
1

インスタンス

type() 関数は、オブジェクトの型を教えてくれましたが、isinstance() 関数を使えば、オブジェクトが特定の型またはカスタム・クラスのインスタンスであるかどうかを調べることもできます。

リスト33. あなたは、この仲間ですか ?
>>> print isinstance.__doc__
isinstance(object, class-or-type-or-tuple) -> Boolean
Return whether an object is an instance of a class or of a subclass thereof.
With a type as second argument, return whether that is the object's type.
The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
isinstance(x, A) or isinstance(x, B) or ... (etc.).
>>> isinstance(42, str)
0
>>> isinstance('a string', int)
0
>>> isinstance(42, int)
1
>>> isinstance('a string', str)
1

サブクラス

先に、カスタム・クラスのインスタンスは、そのクラスの属性を継承すると述べましたが、クラスのレベルでは、クラスを別のクラスを使って定義することができ、さらに、階層的に属性を継承することになります。Pythonは、多重継承 (multiple inheritance) もサポートしています。すなわち、1個のクラスを、複数の親クラスを使って定義し、複数の親クラスから属性を継承するようにすることもできます。あるクラスが別のクラスを継承しているかどうかは、issubclass() 関数で調べることができます。

リスト34. あなたは、私の母ですか ?
>>> print issubclass.__doc__
issubclass(C, B) -> Boolean
Return whether class C is a subclass (i.e., a derived class) of class B.
>>> class SuperHero(Person):   # SuperHero inherits from Person...
...     def intro(self):       # but with a new SuperHero intro
...         """Return an introduction."""
...         return "Hello, I'm SuperHero %s and I'm %s." % (self.name, self.age)
...
>>> issubclass(SuperHero, Person)
1
>>> issubclass(Person, SuperHero)
0
>>>

質問の時間

上の節で取り上げたいろいろな問い合わせ手法を組み合わせて、まとめにしたいと思います。そのために、interrogate() という独自の関数を定義し、関数に渡されてきたオブジェクトについての情報をいろいろと表示するようにします。以下がそのコードで、その使用例もいくつか示してあります。

リスト35. 誰も予想していない
>>> def interrogate(item):
...     """Print useful information about item."""
...     if hasattr(item, '__name__'):
...         print "NAME:    ", item.__name__
...     if hasattr(item, '__class__'):
...         print "CLASS:   ", item.__class__.__name__
...     print "ID:      ", id(item)
...     print "TYPE:    ", type(item)
...     print "VALUE:   ", repr(item)
...     print "CALLABLE:",
...     if callable(item):
...         print "Yes"
...     else:
...         print "No"
...     if hasattr(item, '__doc__'):
...         doc = getattr(item, '__doc__')
... 	doc = doc.strip()   # Remove leading/trailing whitespace.
... 	firstline = doc.split('\n')[0]
... 	print "DOC:     ", firstline
...
>>> interrogate('a string')     # String object
CLASS:    str
ID:       141462040
TYPE:     <type 'str'>
VALUE:    'a string'
CALLABLE: No
DOC:      str(object) -> string
>>> interrogate(42)             # Integer object
CLASS:    int
ID:       135447416
TYPE:     <type 'int'>
VALUE:    42
CALLABLE: No
DOC:      int(x[, base]) -> integer
>>> interrogate(interrogate)    # User-defined function object
NAME:     interrogate
CLASS:    function
ID:       141444892
TYPE:     <type 'function'>
VALUE:    <function interrogate at 0x86e471c>
CALLABLE: Yes
DOC:      Print useful information about item.

上の例からわかるように、interrogate() 関数は、自分自身を対象とすることもできます。これ以上に内省的なものはありません。


まとめ

イントロスペクションがこんなに簡単で役に立つものであると、誰が認識していたでしょうか。ただし、最後に注意を喚起しておく必要があります。イントロスペクションの結果を知識と勘違いしないでください。経験を積んだPythonプログラマーは、いつでも、自分の知らないことがいっぱいあり、したがって、ちっとも賢くないということを知っています。プログラミングという行為からは、答よりも疑問のほうがたくさん生み出されます。唯一、Pythonで良い点は、この記事で見てきたように、Pythonがそうした疑問に答えてくれるということです。私について言えば、Pythonが装備しているこれらのことについて皆さんの理解を手助けしたことで、私に報償が必要だなどと思う必要はありません。Pythonでプログラミングすること自体が、報償だからです。Pythonプログラマー (Pythonians) が私に求めることすべてが、世の中が私に施してくれる無料の糧なのです。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=230157
ArticleTitle=Pythonイントロスペクション入門
publish-date=12012002