よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【dirとinspect】Pythonライブラリの属性、メソッド一覧を調べる方法

はじめに

たとえばNumPyで最小値や最大値を取得したいとき、numpy配列オブジェクト.min()numpy配列オブジェクト.max()を使うと出来ます。

import numpy as np

# 配列
arr = np.array([1,2,3,4,5])
print(arr)

# 配列の最小値
print(arr.min())

# 配列の最大値
print(arr.max())
[1 2 3 4 5]
1
5



Pythonでは、オブジェクトの後ろに「.なんとか」を付ければ、オブジェクトの色々な値や操作ができるようになっています。

Numpyにはmin()やmax()以外にもメソッドが沢山ある。Pandasにも沢山ある。文字列やリストなどにも沢山ある。

今回の趣旨は、
「どんなメソッドがあるのか、一覧で表示できないだろうか?」
です。



実行環境

  • Android
  • Termux (F-Droid版 version0.117)
  • Python 3.10.1
  • Jupyter Notebook 6.4.6



目次



dir() オブジェクトの全ての属性(メソッドやプロパティ)を表示する

dir()関数は標準で利用できます。オブジェクトの持つ全ての属性(メソッドやプロパティ)をリスト形式で返します。

文字列オブジェクトを例として、やってみます。

# 適当にオブジェクトを用意(文字列)
obj = "ド,レ,ミ"

# dir()関数でメソッド一覧表示
dir(obj)
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isascii',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'removeprefix',
 'removesuffix',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

メソッド名がリストで大量に出てきました!

単なる文字列オブジェクトに対して、これだけあるとは…。数えてみます。

# メソッドの個数
len(dir(obj))
80

ドゥフゥッ、80個もある。

上級者などがプログラミング上達の極意を語る中で「丸暗記しようとするな」とよく言っていますけど、文字列の属性だけで80個もあるわけですから、言語全体としてとても覚えられる量ではない事が分かります。
そうかと言ってまるっきり覚えないなら検索も出来ないという両刃の剣っていう。



さて、↑で取得したメソッドのリストの最後から2番目にupperという名前のメソッドがあるようです。どんなものかヘルプを見てみます。

# upperメソッドのヘルプ
help(obj.upper)
Help on built-in function upper:

upper() method of builtins.str instance
    Return a copy of the string converted to uppercase.

「アッパーケースにコンバートされた文字列のコピーを返す」とある。アッパーケースとは大文字のこと。カタカナの大文字は無いのでアルファベット限定の使用かな。使ってみます。

# 小文字の文字列にメソッドを適用
"abc".upper()
'ABC'

大文字に変わった。lowerというメソッド名もあるが、そちらは小文字に変換するもの。



標準ライブラリinspectで調べる

dir()関数以外で、inspectという標準ライブラリを使う方法もあります。

参考 [https://docs.python.org/ja/3/library/inspect.html?highlight=inspect#module-inspect:title]



inspect は、活動中のオブジェクト (モジュール、クラス、メソッド、関数、トレースバック、フレームオブジェクト、コードオブジェクトなど) から情報を取得する関数を定義しており、クラスの内容を調べたり、メソッドのソースコードを取得したり、関数の引数リストを取り出して整形したり、詳細なトレースバックを表示するのに必要な情報を取得したりするために利用できます。

このモジュールの機能は4種類に分類することができます。型チェック、ソースコードの情報取得、クラスや関数からの情報取得、インタープリタのスタック情報の調査です。

getmembers()を使うと属性が取得できるようだ。

# メソッド一覧用
import inspect

# 適当にオブジェクト(辞書型)を用意
obj_dic = {"one":"あ", "two":"い", "three":"う"}

# メンバー取得
inspect.getmembers(obj_dic)
[('__class__', dict),
 ('__class_getitem__', <function dict.__class_getitem__>),
 ('__contains__', <function dict.__contains__(key, /)>),
 ('__delattr__',
  <method-wrapper '__delattr__' of dict object at 0x758e3f2340>),
 ('__delitem__',
  <method-wrapper '__delitem__' of dict object at 0x758e3f2340>),
 ('__dir__', <function dict.__dir__()>),
 ('__doc__',
  "dict() -> new empty dictionary\ndict(mapping) -> new dictionary initialized from a mapping object's\n    (key, value) pairs\ndict(iterable) -> new dictionary initialized as if via:\n    d = {}\n    for k, v in iterable:\n        d[k] = v\ndict(**kwargs) -> new dictionary initialized with the name=value pairs\n    in the keyword argument list.  For example:  dict(one=1, two=2)"),
 ('__eq__', <method-wrapper '__eq__' of dict object at 0x758e3f2340>),
 ('__format__', <function dict.__format__(format_spec, /)>),
 ('__ge__', <method-wrapper '__ge__' of dict object at 0x758e3f2340>),
 ('__getattribute__',
  <method-wrapper '__getattribute__' of dict object at 0x758e3f2340>),
 ('__getitem__', <function dict.__getitem__>),
 ('__gt__', <method-wrapper '__gt__' of dict object at 0x758e3f2340>),
 ('__hash__', None),
 ('__init__', <method-wrapper '__init__' of dict object at 0x758e3f2340>),
 ('__init_subclass__', <function dict.__init_subclass__>),
 ('__ior__', <method-wrapper '__ior__' of dict object at 0x758e3f2340>),
 ('__iter__', <method-wrapper '__iter__' of dict object at 0x758e3f2340>),
 ('__le__', <method-wrapper '__le__' of dict object at 0x758e3f2340>),
 ('__len__', <method-wrapper '__len__' of dict object at 0x758e3f2340>),
 ('__lt__', <method-wrapper '__lt__' of dict object at 0x758e3f2340>),
 ('__ne__', <method-wrapper '__ne__' of dict object at 0x758e3f2340>),
 ('__new__', <function dict.__new__(*args, **kwargs)>),
 ('__or__', <method-wrapper '__or__' of dict object at 0x758e3f2340>),
 ('__reduce__', <function dict.__reduce__()>),
 ('__reduce_ex__', <function dict.__reduce_ex__(protocol, /)>),
 ('__repr__', <method-wrapper '__repr__' of dict object at 0x758e3f2340>),
 ('__reversed__', <function dict.__reversed__()>),
 ('__ror__', <method-wrapper '__ror__' of dict object at 0x758e3f2340>),
 ('__setattr__',
  <method-wrapper '__setattr__' of dict object at 0x758e3f2340>),
 ('__setitem__',
  <method-wrapper '__setitem__' of dict object at 0x758e3f2340>),
 ('__sizeof__', <function dict.__sizeof__>),
 ('__str__', <method-wrapper '__str__' of dict object at 0x758e3f2340>),
 ('__subclasshook__', <function dict.__subclasshook__>),
 ('clear', <function dict.clear>),
 ('copy', <function dict.copy>),
 ('fromkeys', <function dict.fromkeys(iterable, value=None, /)>),
 ('get', <function dict.get(key, default=None, /)>),
 ('items', <function dict.items>),
 ('keys', <function dict.keys>),
 ('pop', <function dict.pop>),
 ('popitem', <function dict.popitem()>),
 ('setdefault', <function dict.setdefault(key, default=None, /)>),
 ('update', <function dict.update>),
 ('values', <function dict.values>)]

また沢山でてきました。リスト形式になっています。
dir()がプロパティ名やメソッド名だけだったのと違い、こちらはちょっとした使い方も載っています。



__reversed__という名前がある。使ってみます。

# 適用
obj_dic.__reversed__()
<dict_reversekeyiterator at 0x758ded0400>

キーのリバースしたイテレーターを返すのかな?

# 繰り返しで取り出す
for i in obj_dic.__reversed__():
    print(i)

print()

print("元のオブジェクト\n", obj_dic)
three
two
one

元のオブジェクト
 {'one': 'あ', 'two': 'い', 'three': 'う'}

やはり、キーが逆順で出てきている。



次はupdateメソッドのヘルプを見てみる。

help(obj_dic.update)
Help on built-in function update:

update(...) method of builtins.dict instance
    D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
    If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
    If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
    In either case, this is followed by: for k in F:  D[k] = F[k]
# 元の辞書
print(obj_dic)

print()

# 追加する辞書
F = {1:"壱", 2:"弐", 3:"参"}
print(F)

print()

# アップデート後
obj_dic.update(F)
print(obj_dic)
{'one': 'あ', 'two': 'い', 'three': 'う'}

{1: '壱', 2: '弐', 3: '参'}

{'one': 'あ', 'two': 'い', 'three': 'う', 1: '壱', 2: '弐', 3: '参'}



他のオブジェクトでも調べてみよう

# オブジェクト生成用
import numpy as np
import pandas as pd



Numpyのオブジェクトを調べる

これも大量に出てきそうなので、数を限定して表示します。

# Numpy配列のオブジェクト生成
np_obj = np.array([1,2,3])


# 属性一覧
lst = inspect.getmembers(np_obj)

# 属性の個数
print(len(lst))

print(type(lst))

print()

# 適当に6個取得
for i,v in enumerate(lst):
    if 100 <= i <= 105:
        print(v)
        print("="*20)
162
<class 'list'>

('byteswap', <built-in method byteswap of numpy.ndarray object at 0x758b6be430>)
====================
('choose', <built-in method choose of numpy.ndarray object at 0x758b6be430>)
====================
('clip', <built-in method clip of numpy.ndarray object at 0x758b6be430>)
====================
('compress', <built-in method compress of numpy.ndarray object at 0x758b6be430>)
====================
('conj', <built-in method conj of numpy.ndarray object at 0x758b6be430>)
====================
('conjugate', <built-in method conjugate of numpy.ndarray object at 0x758b6be430>)
====================

一番下のconjugateというメソッド名は聞いたこともない。共役や結合という意味らしい。共役複素数を出力するメソッドのようだ。1-2jなら1+2jを出力するみたいな。

help(np.conjugate)
Help on ufunc:

conjugate = <ufunc 'conjugate'>
    conjugate(x, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj])

    Return the complex conjugate, element-wise.

    The complex conjugate of a complex number is obtained by changing the
    sign of its imaginary part.

    Parameters
    ----------
    x : array_like
        Input value.
    out : ndarray, None, or tuple of ndarray and None, optional
        A location into which the result is stored. If provided, it must have
        a shape that the inputs broadcast to. If not provided or None,
        a freshly-allocated array is returned. A tuple (possible only as a
        keyword argument) must have length equal to the number of outputs.
    where : array_like, optional
        This condition is broadcast over the input. At locations where the
        condition is True, the `out` array will be set to the ufunc result.
        Elsewhere, the `out` array will retain its original value.
        Note that if an uninitialized `out` array is created via the default
        ``out=None``, locations within it where the condition is False will
        remain uninitialized.
    **kwargs
        For other keyword-only arguments, see the
        :ref:`ufunc docs <ufuncs.kwargs>`.

    Returns
    -------
    y : ndarray
        The complex conjugate of `x`, with same dtype as `y`.
        This is a scalar if `x` is a scalar.

    Notes
    -----
    `conj` is an alias for `conjugate`:

    >>> np.conj is np.conjugate
    True

    Examples
    --------
    >>> np.conjugate(1+2j)
    (1-2j)

    >>> x = np.eye(2) + 1j * np.eye(2)
    >>> np.conjugate(x)
    array([[ 1.-1.j,  0.-0.j],
           [ 0.-0.j,  1.-1.j]])
np.conjugate(1-2j)
(1+2j)

なるほど。

Pandasデータフレームのメソッド一覧を調べる

# 適当にオブジェクトを生成
df = pd.DataFrame(
    [[1,2,3],
     [4,5,6],
     [7,8,9]],
    columns=["a","b","c"]
)

df
a b c
0 1 2 3
1 4 5 6
2 7 8 9
# メンバーリスト
df_methods = inspect.getmembers(df)

# メンバーの個数
print(len(df_methods))

435

むちゃ有る。上級者が「丸暗記で覚えようとするな」と言うわけだ。メソッド一つに1ページ割くとして、Pandasだけで分厚い本が1冊書けるぞ。

限定して表示。

# 適当に6個取り出す
for i, m in enumerate(df_methods):
    if 360 <= i <= 365:
        print(i, m)
        print("="*20)
360 ('reindex', <bound method DataFrame.reindex of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================
361 ('reindex_like', <bound method NDFrame.reindex_like of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================
362 ('rename', <bound method DataFrame.rename of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================
363 ('rename_axis', <bound method NDFrame.rename_axis of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================
364 ('reorder_levels', <bound method DataFrame.reorder_levels of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================
365 ('replace', <bound method DataFrame.replace of    a  b  c
0  1  2  3
1  4  5  6
2  7  8  9>)
====================



クラスを作成し、属性の一覧を見てみる

ここからは、実際にクラスを作って見てみます。dir()inspect.getmembers()を自作クラスやメソッドに適用した方が理解できるだろう。

生年月日から満年齢や生まれた曜日を取得します。

# インポート
from datetime import date

# クラスの宣言
class Birthday:
    """生年月日から満年齢と生まれた曜日を取得するクラス"""
    
    # コンストラクタ(初期化メソッド)
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    # 満年齢計算メソッド
    def old(self):
        """生年月日から満年齢を計算する"""     
        today = date.today()
        birthday = date(self.year, self.month, self.day)
        return (int(today.strftime("%Y%m%d")) - int(birthday.strftime("%Y%m%d"))) // 10000
    
    # 曜日取得メソッド
    def weekday(self):
        """生年月日の曜日を取得する"""        
        day = date(self.year, self.month, self.day)
        return day.strftime('%A'), day.strftime('%a')
        

##### 実行 #####

# インスタンス
bd = Birthday(2000,1,1)

print(bd.year, bd.month, bd.day)
print("満年齢 : ", bd.old())
print("生まれた曜日 : ", bd.weekday())
2000 1 1
満年齢 :  21
生まれた曜日 :  ('Saturday', 'Sat')



↑の例で、Birthdayクラスは

  • __init__
  • old
  • weekday

の3つのメソッドと、

  • year
  • month
  • day

の3つのプロパティ、インスタンス変数を持っているのは分かります。

dir()とinspectで確認します。

dir(bd)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'day',
 'month',
 'old',
 'weekday',
 'year']
inspect.getmembers(bd)
[('__class__', __main__.Birthday),
 ('__delattr__',
  <method-wrapper '__delattr__' of Birthday object at 0x75895df850>),
 ('__dict__', {'year': 2000, 'month': 1, 'day': 1}),
 ('__dir__', <function Birthday.__dir__()>),
 ('__doc__', '生年月日から満年齢と生まれた曜日を取得するクラス'),
 ('__eq__', <method-wrapper '__eq__' of Birthday object at 0x75895df850>),
 ('__format__', <function Birthday.__format__(format_spec, /)>),
 ('__ge__', <method-wrapper '__ge__' of Birthday object at 0x75895df850>),
 ('__getattribute__',
  <method-wrapper '__getattribute__' of Birthday object at 0x75895df850>),
 ('__gt__', <method-wrapper '__gt__' of Birthday object at 0x75895df850>),
 ('__hash__', <method-wrapper '__hash__' of Birthday object at 0x75895df850>),
 ('__init__',
  <bound method Birthday.__init__ of <__main__.Birthday object at 0x75895df850>>),
 ('__init_subclass__', <function Birthday.__init_subclass__>),
 ('__le__', <method-wrapper '__le__' of Birthday object at 0x75895df850>),
 ('__lt__', <method-wrapper '__lt__' of Birthday object at 0x75895df850>),
 ('__module__', '__main__'),
 ('__ne__', <method-wrapper '__ne__' of Birthday object at 0x75895df850>),
 ('__new__', <function object.__new__(*args, **kwargs)>),
 ('__reduce__', <function Birthday.__reduce__()>),
 ('__reduce_ex__', <function Birthday.__reduce_ex__(protocol, /)>),
 ('__repr__', <method-wrapper '__repr__' of Birthday object at 0x75895df850>),
 ('__setattr__',
  <method-wrapper '__setattr__' of Birthday object at 0x75895df850>),
 ('__sizeof__', <function Birthday.__sizeof__()>),
 ('__str__', <method-wrapper '__str__' of Birthday object at 0x75895df850>),
 ('__subclasshook__', <function Birthday.__subclasshook__>),
 ('__weakref__', None),
 ('day', 1),
 ('month', 1),
 ('old',
  <bound method Birthday.old of <__main__.Birthday object at 0x75895df850>>),
 ('weekday',
  <bound method Birthday.weekday of <__main__.Birthday object at 0x75895df850>>),
 ('year', 2000)]



両者とも自動でいろんな属性が作られているようです。ドキュメントを見て今回は終わりにします。

# クラスのドキュメント
print("◆ bd.__doc__\n", bd.__doc__)
print("="*10)

# メソッドのドキュメント
print("◆ bd.old.__doc__ \n", bd.old.__doc__)
print('='*10)

print("◆ bd.weekday.__doc__ \n", bd.weekday.__doc__)
◆ bd.__doc__
 生年月日から満年齢と生まれた曜日を取得するクラス
==========
◆ bd.old.__doc__ 
 生年月日から満年齢を計算する
==========
◆ bd.weekday.__doc__ 
 生年月日の曜日を取得する



おわりに

Pandasの属性だけで435個もある。その内いくつのメソッドを知っていて、使いこなせているだろうか。全然使えていないな! 全部覚えるのも無理だ。ならば、「如何に効率的に目的に沿うメソッドを検索できるか」が開発の一つの重要なポイントになる。
ドキュメントを集めてSphinxなどで逆引き辞典的なものがローカルで構築できれば便利そうなんだがなぁ…と、しばしば思う。良さげなツールがもしかしたら既にあるのかも。



以上です。