3

自分自身を参照する関数が必要です。たとえば、再帰的であること。

だから私はそのようなことをします:

def fib(n):
    return n if n <= 1 else fib(n-1)+fib(n-2)

ほとんどの場合、これで問題ありfibませんが、実際にはそれ自体を参照していません。fib囲みブロック内のバインディングを参照します。したがって、何らかの理由fibで再割り当てされると、壊れます。

>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fib
TypeError: 'int' object is not callable

可能であれば、これが(内部からfib)発生しないようにするにはどうすればよいですか?私の知る限りfib、関数定義が完全に実行される前にの名前は存在しません。回避策はありますか?

実際に発生する可能性のある実際のユースケースはありません。純粋な好奇心から質問しています。

4

4 に答える 4

7

私はこれのためのデコレータを作ります

from functools import wraps

def selfcaller(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(wrapper, *args, **kwargs)
    return wrapper

そして、それを次のように使用します

@selfcaller
def fib(self, n):
    return n if n <= 1 else self(n-1)+self(n-2)

これは実際には、固定小数点コンビネータ(または Y コンビネータ)を定義する読みやすい方法です。

fix = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg)))

利用方法:

fib = fix(lambda self: lambda n: n if n <= 1 else self(n-1)+self(n-2))

また:

@fix
def fib(self):
    return lambda n: n if n <= 1 else self(n-1)+self(n-2)

ここでのバインディングは仮パラメーターで行われるため、問題は発生しません。

于 2013-05-29T19:16:06.447 に答える
1

誰かがこれに対するマクロベースの解決策を私に求めたので、ここにあります:

# macropy/my_macro.py
from macropy.core.macros import *

macros = Macros()

@macros.decorator()
def recursive(tree, **kw):
    tree.decorator_list = []

    wrapper = FunctionDef(
        name=tree.name,
        args=tree.args,
        body=[],
        decorator_list=tree.decorator_list
    )

    return_call = Return(
        Call(
            func = Name(id=tree.name),
            args = tree.args.args,
            keywords = [],
            starargs = tree.args.vararg,
            kwargs = tree.args.kwarg
        )
    )

    return_call = parse_stmt(unparse_ast(return_call))[0]

    wrapper.body = [tree, return_call]

    return wrapper

これは次のように使用できます。

>>> import macropy.core.console
0=[]=====> MacroPy Enabled <=====[]=0
>>> from macropy.my_macro import macros, recursive
>>> @recursive
... def fib(n):
...     return n if n <= 1 else fib(n-1)+fib(n-2)
...
>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
>>> x
21

それは基本的に hus787 が与えたラッピングを正確に行います:

  • return fib(...)元の関数の引数リストを...
  • def古いものと同じ名前、同じ引数、同じ decorator_list で新しい を作成します
  • 新しい functiondef の本体に、古い関数を配置し、その後にreturnステートメントを続けます。
  • デコレーターの元の機能を取り除きます (代わりにラッパーをデコレートしたいと思います)

ごみは、何かを機能させるための簡単なハックです(実際には、関数のパラメーターリストからASTをparse_stmt(unparse_ast(return_call))[0]コピーしてASTで使用することはできません)が、それは単なる詳細です。argumentCall

実際にそれを行っていることを示すために、print unparse_astステートメントを追加して、変換された関数がどのように見えるかを確認できます。

@macros.decorator()
def recursive(tree, **kw):
    ...
    print unparse_ast(wrapper)
    return wrapper

上記のように実行すると、印刷されます

def fib(n):

    def fib(n):
        return (n if (n <= 1) else (fib((n - 1)) + fib((n - 2))))
    return fib(n)

まさにあなたが望むもののように見えます! 複数の引数、kwargs、デフォルトなどを使用して、どの関数でも機能するはずですが、テストするのが面倒です。AST での作業は少し冗長で、MacroPyはまだ非常に実験的なものですが、かなりうまくできていると思います。

于 2013-05-31T20:10:25.040 に答える