Python 闭包/修饰函数的变量赋值 -

Python closures / variable assignment of decorated functions -

提问人:chris 提问时间:5/3/2023 最后编辑:chris 更新时间:5/4/2023 访问量:71

问:

我正在尝试学习 python 中的闭包和装饰器。

我知道,在我下面的代码中,变量 fn 已被分配给一个“cell”对象,一旦该对象作为参数传递给函数“outer”并调用 outer,它本身将引用函数对象。my_func

我不明白为什么,当从函数内部调用(修饰函数)时,它会调用(即修饰函数)而不是“原始”未修饰的my_func函数(这是作为参数传递给“外部”的参数)fninnerinner

我没有预料到装饰会导致我上面提到的单元格现在引用 的“不同版本”,从控制台输出中显示的是发生的事情。my_funcmy_func

因此,我对下面的代码有两个问题:

  1. 在作业中,作业左手边的“my_func”是什么?我知道这是一个名为“my_func”的变量,它掩盖了函数(但被定义为的原始函数仍然存在)。my_func = outer(my_func)my_funcmy_func
  2. 为什么现在似乎是参考,而不是通过的。也许它实际上并没有这样做,我误解了一些事情。fninnermy_func

任何帮助将不胜感激。

我编写了以下代码:

def my_func():
  print(f"This function's name is '{my_func.__name__}'. Its ID is {hex(id(my_func))}")
  return None

def outer(fn):
  def inner():
    print(f"The function that is about to be called is called '{fn.__name__}'. Its ID is {hex(id(fn))}")
    return fn() 
  return inner


print(f"I have created a function called '{my_func.__name__}'. Its ID is {hex(id(my_func))}")

my_func = outer(my_func)

print(f"The function has been decorated. The variable 'my_func' refers to a function object called '{my_func.__name__}'. Its ID is {hex(id(my_func))}\n")

my_func()

以下内容将打印到控制台:

I have created a function called 'my_func'. Its ID is 0x7f3c040cbb50
The function has been decorated. The variable 'my_func' refers to a function object called 'inner'. Its ID is 0x7f3c040f4040

The function that is about to be called is called 'my_func'. Its ID is 0x7f3c040cbb50
This function's name is 'inner'. Its ID is 0x7f3c040f4040

我所期待的是:

I have created a function called 'my_func'. Its ID is 0x7f3c040cbb50
The function has been decorated. The variable 'my_func' refers to a function object called 'inner'. Its ID is 0x7f3c040f4040

The function that is about to be called is called 'my_func'. Its ID is 0x7f3c040cbb50
The function that is about to be called is called 'my_func'. Its ID is 0x7f3c040cbb50

我承认这是“问题”的原因,但我不明白为什么。我原以为闭合 fn 会继续引用“原始”未修饰的功能。我不明白为什么赋值似乎会“改变”fn 引用的对象。my_func = outer(my_func)my_funcmy_funcouter(my_func)

Python 函数 变量闭 包装饰

评论

0赞 Mechanic Pig 5/3/2023
为什么您希望最后一行的输出来自而不是原始的输出?innermy_func
0赞 chris 5/3/2023
我没有 - 这就是我感到困惑的原因!
0赞 Mechanic Pig 5/3/2023
我的意思是,你预期输出的最后一行的形式是“即将被调用的函数被称为{func_name}...”,这是 中的输出形式,不是吗?inner

答:

0赞 Balaïtous 5/3/2023 #1

当您修饰函数时,它被函数替换。my_funcinner

装修后,有.__my_func__.name == "inner"

若要调整函数名称,请使用 functools.wraps 修饰器。 允许将修饰函数的元数据传输到封闭函数。functools.wraps

def outer(fn):
    @functools.wraps(fn)
    def inner():
        print(...)
        return fn() 
    return inner

更多解释:

在声明中:

def my_func():
    print(f"Name is '{my_func.__name__}'. ID is {hex(id(my_func))}")
    return None

创建一个模块变量,该变量包含对函数对象的引用。 引用此模块变量。每次执行函数时,都会解析指向的引用。my_func{my_func.__name__}my_func

my_func = outer(my_func)

在此之后,变量包含对新创建的函数的引用。举例来说,该函数已被内部函数所取代。my_funcinnermy_func

创建内部函数时,它捕获引用(闭包)作为局部变量。所指的引用永远不会改变,并将永远保持原来的功能。my_funcfnfn

每次调用时,它都会创建一个新的唯一函数。outerinner

执行最后一次打印时:

print(f"Name is {my_func.__name__}...")

my_func引用新函数 所以是 。my_func.__name__"inner"

在下面的示例中,我们可以看到由 changes 和 new 函数托管的引用捕获了对旧函数的引用:my_func

>>> def my_func():
...     pass
... 
>>> hex(id(my_func))
'0x7ffb218c0430'
>>> my_func = outer(my_func)
>>> hex(id(my_func))
'0x7ffb218c0310'
>>> my_func.__closure__
(<cell at 0x7ffb218b7c10: function object at 0x7ffb218c0430>,)

评论

0赞 chris 5/3/2023
谢谢。我特别没有使用包装,因为仅仅作为理解正在发生的事情的过程的一部分,我不想“看透”装饰的功能。您能详细说明一下您所说的“被替换”的功能是什么意思吗?我不明白 fn 如何“知道”它现在应该引用内部。“my_func”似乎仍然独立于“内在”存在于记忆中。
0赞 slothrop 5/4/2023
最初创建的函数对象确实仍然存在于内存中 - 即使变量名称不再引用它。当计算该函数的 print 语句中的 f 字符串时,变量名称(在 f 字符串的大括号内使用)的计算结果为变量名称当前引用的函数对象,而不是该名称最初引用的函数对象。def my_funcmy_funcmy_funcmy_funcmy_func
0赞 chris 5/8/2023
谢谢你们俩。我可以清楚地看到,我在代码的第二行使用了对“my_func”的引用,就好像它是对名为 my_func 的函数对象的引用,而不是变量 my_func(有点像“self”或“this”)——这不会产生我期望的结果。顺便说一句,有没有办法从函数的主体中引用函数及其属性?再次感谢你
0赞 slothrop 5/9/2023
顺便说一句,有没有办法从函数的主体中引用函数及其属性?不是以一种超级好的方式,但你可以尝试一些事情。请参见:stackoverflow.com/questions/4492559/...
0赞 slothrop 5/9/2023
另一方面,如果你不需要函数对象,而只想要当前函数最初定义的名称,那就更简单了:给你这个名字。这样一来,你就可以从你的打印语句中得到你最初期望的行为(尽管我知道你问题中的示例是学习更多关于闭包的一种手段,而不是它本身的用例!inspect.stack()[0].function