彷徨えるフジワラ

年がら年中さまよってます

Python 関数の引数形式を調べる

Mercurial の 2.3 版における内部 API の改変により、collapse が動かなくなっているとの報告が。

結局 revlog クラスの "def descendants(self, *revs)" メソッドが "def descendants(self, revs)" へとシグネチャ変更されたことが原因だったらしいことが判明したので、対応修正は近日中にでもリリースされるものと思われる。

で、この変更への対応を collapse エクステンション側で実現するとなると:

  • Mercurial のバージョン情報を元に切り替える
  • mercurial.revlog.descendants の定義情報を元に切り替える

あたりが想定されるが、さすがに前者は格好悪いので、実際の修正は恐らく後者で実現されることだろう。
じゃぁ、実際に後者で実現するとして、Python だとどうやるんだっけ?ということで、何となく以前も調べた記憶があるのだけれど、思い出す取っ掛かりすら残って無かったので、改めて調べてみる&後日の自分のためにメモっておくことに。

配布物添付のドキュメントを適当に漁る事暫し…… "inspect - Inspect live objects" あたりだよなぁ、と見当を付けて掘り下げてみると:

引数宣言における "*" 付き引数の有無を知りたい

という用途で見た場合、以下の2つの方法が妥当そう。

  • func オブジェクトから "func_code" 属性経由で取得した code オブジェクトの、"co_argcount" の値と、"co_varnames" の要素数が一致しない場合は、可変引数ありと判定
  • func オブジェクトを引数に、inspect モジュールの "getargspec()" を呼び出し、結果 tuple の第2要素(添え字的には1)が非 None なら、可変引数ありと判定

前者は:

code オブジェクトの "co_argcount" 属性値は、"*"/"**" 付き引数の数を含まない

という性質を利用したものだが、対象関数が "**" 付き引数を持つ可能性が(今後も)無いことを仮定できなければ、判定の信頼性は怪しくなってしまう。

一方後者の方法では、"*"/"**" 付き引数の情報を別々に扱っているので、判定の信頼性は高いのだが、以下のオーバヘッドを考えると、細かい性能が気になるシチュエーションでの採用は微妙かもしれない。

  • inspect モジュールの読み込みが必須
  • "getargspec()" 自体が、そこそこの量の処理を要する

collapse での修正の場合、(1) Mercurial の (おそらく常に読み込まれるであろう) localrepo モジュールで既に inspect を import していることにより、読み込みオーバヘッドはほぼ無視できるし、(2) 内部 API の仕様は今後どうなるかわからないので、後者の方法が妥当かな?

ちなみに、"inspect.getargspec()" の戻り値は、Python 2.6 以降なら "named tuple" なるものになっていて、例えば "inspect.getargspec()[1]" は "inspect.getargspec().varargs" という方法でもアクセスできる模様。

"inspect.getargspec()" 戻り値の named tuple 化自体は 2.6 からの様だけど、named tuple 自体は比較的古くからある機能っぽい。

今調べてみたら、『初めての Python』(僕の持っているのは第2版)には説明が無いけど、『エキスパート Python プログラミング』にはちょっとだけ説明があった。