Python inspect.signature()とは?関数の引数を調べる方法

inspect.signature()は、関数がどんな引数を受け取るかを調べる関数です。

引数の名前・既定値・種類をプログラムから扱える形で取得できます。


signature()の基本

import inspect

def greet(name, greeting="こんにちは", *args, times=1, **kwargs):
    return f"{greeting}、{name}さん"

sig = inspect.signature(greet)
print(sig)
print(type(sig).__name__)

実行結果:

(name, greeting='こんにちは', *args, times=1, **kwargs)
Signature

定義そのままの文字列になります。ヘルプ表示やエラーメッセージにそのまま使えます。


引数を1つずつ調べる

for name, p in sig.parameters.items():
    print(f"{name:10} kind={p.kind.name:22} default={p.default!r}")

実行結果:

name       kind=POSITIONAL_OR_KEYWORD  default=<class 'inspect._empty'>
greeting   kind=POSITIONAL_OR_KEYWORD  default='こんにちは'
args       kind=VAR_POSITIONAL         default=<class 'inspect._empty'>
times      kind=KEYWORD_ONLY           default=1
kwargs     kind=VAR_KEYWORD            default=<class 'inspect._empty'>

parametersは順序を保った辞書です。定義順に並びます。


既定値がないときはinspect._empty

ここが分かりにくい点です。既定値がない引数のdefaultはNoneではありません。

p = sig.parameters["name"]

print(p.default is None)
print(p.default is inspect.Parameter.empty)

実行結果:

False
True

Noneを既定値にした引数と区別するためです。

def f(a, b=None): pass

for name, p in inspect.signature(f).parameters.items():
    has = p.default is not inspect.Parameter.empty
    print(name, "既定値あり:", has, "値:", p.default)

実行結果:

a 既定値あり: False 値: <class 'inspect._empty'>
b 既定値あり: True 値: None

if p.default:と書いてはいけません。Noneも0も偽になってしまいます。


5種類のkind

kind 書き方
POSITIONAL_ONLY def f(a, /)
POSITIONAL_OR_KEYWORD 普通の引数
VAR_POSITIONAL *args
KEYWORD_ONLY *より後ろ
VAR_KEYWORD **kwargs

KEYWORD_ONLYはキーワードでしか渡せない引数です。上の例のtimesがこれにあたります。*argsより後ろに書いたためです。


型ヒントも取れる

def add(a: int, b: int = 0) -> int:
    return a + b

sig = inspect.signature(add)
print(sig)
print("戻り値:", sig.return_annotation)
print("aの型:", sig.parameters["a"].annotation)

実行結果:

(a: int, b: int = 0) -> int
戻り値: <class 'int'>
aの型: <class 'int'>

型ヒントがそのまま取得できます。

FastAPIなどのフレームワークは、この情報を使ってリクエストの値を自動で変換・検証しています。

型ヒントについては「Python typingモジュールとは?型ヒントの書き方を初心者向けに解説」を参照してください。


bindで引数を検証する

inspect.signature(greet).bind()

実行結果:

TypeError: missing a required argument: 'name'

関数を実行せずに、引数が正しいか確認できます。

b = inspect.signature(greet).bind("田中")
b.apply_defaults()
print(b.arguments)

実行結果:

{'name': '田中', 'greeting': 'こんにちは', 'args': (), 'times': 1, 'kwargs': {}}

apply_defaults()を呼ぶと既定値が埋まります。

実際に渡される値の全体像が分かるため、ログやキャッシュのキー生成に使えます。


デコレータでの注意点

import functools
import inspect

def deco(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@deco
def greet(name, greeting="こんにちは"):
    return f"{greeting}、{name}さん"

print(inspect.signature(greet))

実行結果:

(*args, **kwargs)

元の引数の情報が消えています。包んだwrapperの定義が見えているためです。

functools.wrapsを付けると解決します。

def deco(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

実行結果:

(name, greeting='こんにちは')

デコレータには必ず@functools.wrapsを付けてください。「Python functools.wraps()とは?デコレータで関数情報を保つ方法」で解説しています。


組み込み関数では失敗することがある

print(inspect.signature(len))

inspect.signature(print)

実行結果(Python 3.9):

(obj, /)
ValueError: no signature found for builtin <built-in function print>

len()は取得できるのにprint()はできません。

C言語で実装された関数のうち、引数の情報が埋め込まれていないものは取得できません。どちらになるかは関数ごとに違います。

try:
    sig = inspect.signature(func)
except ValueError:
    sig = None
    print("シグネチャを取得できません")

外部から受け取ったオブジェクトを扱うなら、必ずtryで囲んでください。


signature()のまとめ

  • 引数の名前・既定値・種類・型ヒントを取得できる
  • 既定値なしはNoneではなくParameter.empty
  • kindで*argsやKEYWORD_ONLYを判別できる
  • bind()で実行せずに引数を検証
  • デコレータは@functools.wrapsが必須
  • 組み込み関数ではValueErrorになることがある

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら