16

装飾された再帰関数がどのように機能するかを理解するのに苦労しています。次のスニペットの場合:

def dec(f):
    def wrapper(*argv):
        print(argv, 'Decorated!')
        return(f(*argv))
    return(wrapper)

def f(n):
    print(n, 'Original!')
    if n == 1: return(1)
    else: return(f(n - 1) + n)

print(f(5))
print

dec_f = dec(f)
print(dec_f(5))
print

f = dec(f)
print(f(5))

出力は次のとおりです。

(5, 'Original!')
(4, 'Original!')
(3, 'Original!')
(2, 'Original!')
(1, 'Original!')
15

((5,), 'Decorated!')
(5, 'Original!')
(4, 'Original!')
(3, 'Original!')
(2, 'Original!')
(1, 'Original!')
15

((5,), 'Decorated!')
(5, 'Original!')
((4,), 'Decorated!')
(4, 'Original!')
((3,), 'Decorated!')
(3, 'Original!')
((2,), 'Decorated!')
(2, 'Original!')
((1,), 'Decorated!')
(1, 'Original!')
15

最初のものは f(n) を出力するので、当然 f(n) が再帰的に呼び出されるたびに 'Original' を出力します。

2 番目のものは def_f(n) を出力するため、n がラッパーに渡されると、f(n) が再帰的に呼び出されます。ただし、ラッパー自体は再帰的ではないため、「Decorated」は 1 つだけ出力されます。

3 つ目は私を困惑させます。これはデコレータ @dec を使用するのと同じです。装飾された f(n) がラッパーを 5 回も呼び出すのはなぜですか? def_f=dec(f) と f=dec(f) は、2 つの同一の関数オブジェクトにバインドされた 2 つのキーワードにすぎないように見えます。装飾された関数に装飾されていない関数と同じ名前が付けられている場合、何か他のことが起こっていますか?

ありがとう!

4

6 に答える 6

9

あなたが言ったように、最初のものは通常どおり呼び出されます。

2 番目のものは、dec_f と呼ばれる f の修飾バージョンをグローバル スコープに配置します。Dec_f が呼び出されるため、"Decorated!" が出力されますが、dec に渡される f 関数内では、dec_f ではなく f 自体を呼び出します。名前 f は検索され、ラッパーなしでまだ定義されているグローバルスコープで見つかります。そのため、それ以降は f のみが呼び出されます。

3re の例では、修飾されたバージョンを名前 f に割り当てるため、関数 f 内で名前 f が検索され、グローバル スコープで検索され、修飾されたバージョンである f が検出されます。

于 2012-05-25T16:19:02.370 に答える
5

Python での割り当てはすべて、名前をオブジェクトにバインドするだけです。あなたが持っているとき

f = dec(f)

fあなたがしているのは、名前を の戻り値にバインドすることですdec(f)。その時点でf、元の関数を参照しなくなります。元の関数はまだ存在し、 new によって呼び出されfますが、元の関数への名前付き参照はもうありません。

于 2012-05-25T16:13:59.187 に答える
1

関数は と呼ばれるものを呼び出しますf。これは、python が囲んでいるスコープで検索されます。

ステートメントまでf = dec(f)fラップされていない関数にまだバインドされているため、それが呼び出されます。

于 2012-05-25T16:15:13.747 に答える
0

コードを少し変更しました

def dec(func):
    def wrapper(*argv):
        print(argv, 'Decorated!')
        return(func(*argv))
    return(wrapper)

def f(n):
    print(n, 'Original!')
    if n == 1: return(1)
    else: return(f(n - 1) + n)

print(f(5))
print

dec_f = dec(f)
print(dec_f(5))
print

f = dec(f)
print(f(5))

これにより、ここで物事が少し明確になると思います。ラッパー関数は、実際には func オブジェクトを囲みスコープから閉じます。したがって、ラッパー内の func へのすべての呼び出しは元の f を呼び出しますが、f 内の再帰呼び出しは装飾されたバージョンの f を呼び出します。

func.__name__これは、ラッパーのf.__name__内側と関数の内側を印刷するだけで実際に確認できますf

于 2017-08-10T08:19:38.577 に答える