【问题标题】:Can somebody explain why the Python Interpreter doesn't get confused in this instance of using decorators?有人可以解释为什么 Python 解释器在这种使用装饰器的情况下不会感到困惑吗?
【发布时间】:2020-07-14 23:05:51
【问题描述】:

我正在尝试从 link 中了解装饰器。

我有两个简单的文件 a.py 和 b.py

a.py 代码如下:

def f1(input_func):
    def inner_func():
        input_func()
        input_func()
    return inner_func

b.py 代码如下:

import a
def saying():
    print('Hello')
saying = a.f1(saying)
saying()

我的问题是,对于上述实例,解释器如何知道我正在调用指向 a.f1(saying)saying 对象,而不是指向说 (print('Hello')) 定义的对象?

【问题讨论】:

    标签: python-3.x python-decorators


    【解决方案1】:

    TLDR:您正在覆盖变量名,因此解释器将简单地使用最新的定义。一旦你运行了saying = a.f1(saying),就没有办法直接调用你原来的saying,除非你把它保存在另一个变量中。

    如果你想要更长的解释......

    垃圾回收

    通常,当您像这样覆盖变量时,内存中的旧值/位置会被垃圾收集器完全擦除。但是,在您的情况下,原始saying 保存在内存中,因为它在调用f1(saying) 时使用。除非您在重新分配之前将其保存在另一个变量中,否则您将无法再次访问它。

    让我们使用id 函数查看内存引用,以更好地了解发生了什么。

    如果你运行这段代码:

    def f1(input_func):
        def inner_func():
            input_func()
            input_func()
        return inner_func
    
    def saying():
        print('Hello')
    
    print("saying address:", hex(id(saying)))
    saying()
    print("---")
    
    saying = f1(saying)
    print("new saying address = f1(saying):", hex(id(saying)))
    saying()
    

    你会得到这样的东西:

    saying address: 0x7fa960295820
    Hello
    ---
    new saying address = f1(saying): 0x7fa9602958b0
    Hello
    Hello
    

    可以看到两个函数保存在不同的内存地址,saying现在指向了新地址。

    如果我们提前保存第一个地址,我们可以使用this technique来恢复原来的功能。在实践中你永远不会想要这样做(将原始 saying 简单地保存在不同的变量中会非常容易),但仅用于教学目的:

    import gc
    
    # ... define f1 and saying as before
    
    def object_by_id(id_):
        for obj in gc.get_objects():
            if id(obj) == id_:
                return obj
        raise Exception("Not found")
    
    origId = id(saying)
    print("saying address:", hex(origId))
    saying()
    print("---")
    
    saying = f1(saying)
    print("new saying address = f1(saying):", hex(id(saying)))
    saying()
    print("---")
    
    saying = object_by_id(origId)
    print("saying address after recovery: ", hex(id(saying)))
    if id(saying) == origId:
      print( "Matches original!")
    saying()
    

    输出:

    saying address: 0x7f0c2db4a820
    Hello
    ---
    new saying address = f1(saying): 0x7f0c2db4a8b0
    Hello
    Hello
    ---
    saying address after recovery:  0x7f0c2db4a820
    Matches original!
    Hello
    

    所以你可以看到原来的功能确实已经恢复了。同样,这只是因为 saying = f1(saying) 的新值在其定义中包含对原始函数的调用。

    但是,如果对f1 的调用不包括saying,而是调用另一个函数,会发生什么情况?然后原来的saying 就永久丢失了。您可以通过将这个 sn-p 添加到前面代码的末尾来看到这一点:

    def otherFunc():
      print(1)
    
    saying = f1(otherFunc)
    print("new saying address = f1(otherFunc):", hex(id(saying)))
    saying()
    print("---")
    
    saying = object_by_id(origId) # Exception: Not found
    

    没有变量引用原来的saying,因此垃圾收集器将其删除,并且根本无法恢复。

    Demo

    【讨论】:

    • 谢谢。这是非常详细和有用的。
    【解决方案2】:

    当你做任务时

    saying = a.f1(saying)
    

    saying 的这一点开始,指向a.f1(saying) 评估的内容,在本例中是修饰的saying 函数。

    如果您要交换 py.b 的最后两行,那么 saying() 仍然会调用未修饰的 saying 函数。

    【讨论】:

      猜你喜欢
      • 2012-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-06
      • 2016-06-01
      • 2011-01-24
      相关资源
      最近更新 更多