再帰関数書くのに、def で定義した関数名書くのが面倒臭い。
何が面倒臭いって、
JavaScript の arguments.callee みたいに、名前指定せず実行中の関数呼ぶ機能が欲しい。
何が面倒臭いって、
import inspect def factorial_a(n): if not isinstance(n, int): raise ValueError(f"{inspect.currentframe().f_code.co_name}() only accepts integral values") if n < 0: raise ValueError(f"{inspect.currentframe().f_code.co_name}() not defined for negative values") return 1 if n < 1 else factorial_a(n-1) * n;みたいな関数名を factorial_b にした場合に、再帰の部分も書き換えないといけない。
JavaScript の arguments.callee みたいに、名前指定せず実行中の関数呼ぶ機能が欲しい。
「python recurse itself without name」や「python arguments.callee」でググったら以下のページを見つけた。
なんか、decorator の @wrapper 構文使うと関数 wrap 出来るらしくて、第一パラメータに self 入れといてそこに arguments.callee 相当の値(つまり、当該の関数自身)を渡すような細工を出来るっぽい。
その際、functools.wraps を使うのが定番な模様。
とりあえず、以下の関数定義してデコレータとして使うと、第1引数が callee 扱いになる。
2022-06-23: 追記
これ、よく考えたら inspect も不要だった。
おぉ、凄い書き易いぞ!
ただ、これ wrap されるので、呼び出し時に微妙にオーバーヘッドかかるのが嫌ってのはあるかもしれないけど
/2022-06-23: 追記
inspect.currentframe().f_code.co_name を eval() して
デフォルト引数に
と思ったが、def factorial() で関数する時点ではまだ factorial が定義されてないので参照できなかった。
じゃ、関数内で最初に代入すればと思ったが
- stackoverflow
- 2015-10-19: [[Python - can function call itself without explicitly using name?>https://stackoverflow.com/q/33205600]
- 2014-01-19: Reference to the function from inside itself (like arguments.callee in JavaScript)? [duplicate]
なんか、decorator の @wrapper 構文使うと関数 wrap 出来るらしくて、第一パラメータに self 入れといてそこに arguments.callee 相当の値(つまり、当該の関数自身)を渡すような細工を出来るっぽい。
その際、functools.wraps を使うのが定番な模様。
とりあえず、以下の関数定義してデコレータとして使うと、第1引数が callee 扱いになる。
# @reference https://stackoverflow.com/a/35951133 from functools import wraps def recfun(f): @wraps(f) def _f(*a, **k): return f(_f, *a, **k) return _f
import inspect @recfun def factorial(self, n): '''Calculate n!.''' if not isinstance(n, int): raise ValueError(f"{inspect.currentframe().f_code.co_name}() only accepts integral values") if n < 0: raise ValueError(f"{inspect.currentframe().f_code.co_name}() not defined for negative values") return 1 if n < 1 else self(n-1) * n;とりあえず、これが正解っぽい。
2022-06-23: 追記
これ、よく考えたら inspect も不要だった。
@recfun def factorial(self, n): '''Calculate n!.''' if not isinstance(n, int): raise ValueError(f"{self.__name__}() only accepts integral values") if n < 0: raise ValueError(f"{self.__name__}() not defined for negative values") return 1 if n < 1 else self(n-1) * n;
おぉ、凄い書き易いぞ!
ただ、これ wrap されるので、呼び出し時に微妙にオーバーヘッドかかるのが嫌ってのはあるかもしれないけど
/2022-06-23: 追記
inspect.currentframe().f_code.co_name を eval() して
callee = eval(inspect.currentframe().f_code.co_name)みたいにする方法もあるみたいだが、再帰の度に評価されるの辛い。
デフォルト引数に
def factorial(n, _callee = factorial): '''Calculate n!.''' if not isinstance(n, int): raise ValueError(f"{_callee.__name__}() only accepts integral values") if n < 0: raise ValueError(f"{_callee.__name__}() not defined for negative values") return 1 if n < 1 else _callee(n-1) * n;ってするのは、2か所書き換え必要なのでちょっと面倒だけど、書き換える箇所は近くなるので悪くないのでは?
と思ったが、def factorial() で関数する時点ではまだ factorial が定義されてないので参照できなかった。
じゃ、関数内で最初に代入すればと思ったが
def factorial(n): '''Calculate n!.''' callee = factorial if not isinstance(n, int): raise ValueError(f"{callee.__name__}() only accepts integral values") if n < 0: raise ValueError(f"{callee.__name__}() not defined for negative values") return 1 if n < 1 else callee(n-1) * n;みたいになるので、docstring が長いと、書き換え必要な個所が遠くなってしまうので嬉しくない感じ。
タグ
コメントをかく